EMHASS: An Energy Management for Home Assistant

Just working my way through the PVLib settings.

I have two SolarEdge 7 kW 3 phase inverters model SE7K-AUBTEBEU4
and 18.5 kW of panels (50x370W Winaico panels) arranged in four strings east/ west facing on 10 deg tilts.

I have checked in the pvlib library and cannot see these models:

How can I specify generic system parameters on the add-on configuration dialog/ yaml?

Hi, if you cannot find your exact models then just pick one with similar nominal power sizes for both the inverter and the panels. You should be able to find this. This is not ideal but it will still be much better than simpler PV modeling approaches. For our purposes PV panels IV curves and power inverters efficiencies curves are fairly similar from one model to another.
These libraries are regularly updated and they add the new inverters and panels models. So let’s hope that they include yours in a next update.

BTW v0.1.38 is released: Release EMHASS add-on v0.1.38 · davidusb-geek/emhass-add-on · GitHub

Hi, in your case you should be almost good with this model from the PVLib CEC database: SolarEdge Technologies Ltd : SE7600A-US [240V]

There are two important things that will be considered when modeling the PV array:

  1. The inverter efficiency. In this case if you choose a similar inverter from the same manufacturer and in a similar power range it should be good enough. The efficiency curve as a function of the inverter power will be fairly similar.

  2. The maximum AC output from the inverter. This should be a little bit more problematic because a different max nominal power will mean that the simulation may throw a higher (or lower) power output that you may actually get on your real system. I can see that for the present model on the CEC database it will differ from yours on roughly 625W. That may not be much and for our purposes sufficiently low to get some good usable results from this simulation. I may introduce a user given max PV output parameter to the add-on configuration to deal with these special cases.

1 Like

Thanks,

I ended up with that model. The trick is getting all the special characters and replacing them with ‘_’.

I have two inverters each with one string of 25 panels on each, challenge is I have 21 panels East facing and 29 West facing, so it doesn’t exactly line up with my strings as SolarEdge uses optimisers so each panel more or less runs independently. You can see here inverter 1 is not reporting.

I have Winaico 370W mono panels so I just picked something close and set up with 21 & 29 panels on each string, which seems to give pretty close results.

pv_module_model: Advance_Power_API_M370,Advance_Power_API_M370
pv_inverter_model: >-
  SolarEdge_Technologies_Ltd___SE7600A_US__208V_,SolarEdge_Technologies_Ltd___SE7600A_US__208V_
surface_tilt: 10,10
surface_azimuth: 90,270
modules_per_string: 29,21
strings_per_inverter: 1,1
1 Like

This emhass module is now on its version >> v0.3.11: Release EMHASS version 0.3.11 · davidusb-geek/emhass · GitHub

Lots of improvements since v0.3.0… A new docker standalone mode is now fully functional.

I have started using ApexCharts card - A highly customizable graph card to visualise some of the EMHASS data as an alternative to the local UI.

@kc_au @ThirtyDursty @madpilot

image

type: custom:apexcharts-card
span:
  start: minute
header:
  show: true
  title: EMHASS Forecasts
  show_states: true
  colorize_states: true
now:
  show: true
  label: now
series:
  - entity: sensor.p_pv_forecast
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_pv_forecast];
      });
  - entity: sensor.p_batt_forecast
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_batt_forecast];
      });
  - entity: sensor.p_load_forecast
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_load_forecast];
      });
  - entity: sensor.p_deferrable0
    curve: stepline
    stroke_width: 1
    show:
      in_header: before_now
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable0];
      });
  - entity: sensor.p_deferrable1
    curve: stepline
    stroke_width: 1
    show:
      in_header: before_now
    data_generator: |
      return entity.attributes.deferrables_schedule.map((entry) => {
        return [new Date(entry.date), entry.p_deferrable1];
      });

Works well for my energy provider as well:

image

type: custom:apexcharts-card
experimental:
  color_threshold: true
graph_span: 24h
span:
  start: minute
header:
  show: true
  title: Amber Electricty Forecast
  show_states: true
  colorize_states: true
series:
  - entity: sensor.amber_general_forecast
    float_precision: 2
    show:
      in_header: after_now
    color_threshold:
      - value: 0
        color: cyan
      - value: 0.16
        color: green
      - value: 0.2
        color: yellow
      - value: 0.3
        color: red
    name: price kwh
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.start_time), entry.per_kwh];
      });
yaxis:
  - min: 0
    max: 1
    decimals: 2
    apex_config:
      forceNiceScale: true

My EMHASS config file:

web_ui_url: 0.0.0.0
hass_url: empty
long_lived_token: empty
costfun: profit
optimization_time_step: 30
historic_days_to_retrieve: 2
method_ts_round: first
set_total_pv_sell: false
lp_solver: COIN_CMD
lp_solver_path: /usr/bin/cbc
sensor_power_photovoltaics: sensor.apf_generation_entity
sensor_power_load_no_var_loads: sensor.power_load_no_var_loads
number_of_deferrable_loads: 4
list_nominal_power_of_deferrable_loads:
  - nominal_power_of_deferrable_loads: 1500
  - nominal_power_of_deferrable_loads: 5500
  - nominal_power_of_deferrable_loads: 11500
  - nominal_power_of_deferrable_loads: 2400
list_operating_hours_of_each_deferrable_load:
  - operating_hours_of_each_deferrable_load: 4
  - operating_hours_of_each_deferrable_load: 2
  - operating_hours_of_each_deferrable_load: 2
  - operating_hours_of_each_deferrable_load: 1
list_peak_hours_periods_start_hours:
  - peak_hours_periods_start_hours: '02:54'
  - peak_hours_periods_start_hours: '17:24'
list_peak_hours_periods_end_hours:
  - peak_hours_periods_end_hours: '15:24'
  - peak_hours_periods_end_hours: '20:24'
list_treat_deferrable_load_as_semi_cont:
  - treat_deferrable_load_as_semi_cont: true
  - treat_deferrable_load_as_semi_cont: true
  - treat_deferrable_load_as_semi_cont: false
  - treat_deferrable_load_as_semi_cont: false
load_peak_hours_cost: 0.1907
load_offpeak_hours_cost: 0.1419
photovoltaic_production_sell_price: 0.065
maximum_power_from_grid: 30000
list_pv_module_model:
  - pv_module_model: Advance_Power_API_M370
  - pv_module_model: Advance_Power_API_M370
list_pv_inverter_model:
  - pv_inverter_model: SolarEdge_Technologies_Ltd___SE7600A_US__208V_
  - pv_inverter_model: SolarEdge_Technologies_Ltd___SE7600A_US__208V_
list_surface_tilt:
  - surface_tilt: 18
  - surface_tilt: 10
list_surface_azimuth:
  - surface_azimuth: 90
  - surface_azimuth: 270
list_modules_per_string:
  - modules_per_string: 29
  - modules_per_string: 21
list_strings_per_inverter:
  - strings_per_inverter: 1
  - strings_per_inverter: 1
set_use_battery: true
battery_discharge_power_max: 5264
battery_charge_power_max: 5000
battery_discharge_efficiency: 0.95
battery_charge_efficiency: 0.95
battery_nominal_energy_capacity: 13500
battery_minimum_state_of_charge: 0.05
battery_maximum_state_of_charge: 1
battery_target_state_of_charge: 0.1
nominal_power_of_deferrable_loads: 5500,1500,7000,3000
operating_hours_of_each_deferrable_load: 10,16,3,1
peak_hours_periods_start_hours: 02:54,17:24
peak_hours_periods_end_hours: 15:24,20:24
pv_module_model: Advance_Power_API_M370,Advance_Power_API_M370
pv_inverter_model: >-
  SolarEdge_Technologies_Ltd___SE7600A_US__208V_,SolarEdge_Technologies_Ltd___SE7600A_US__208V_
surface_tilt: 10,10
surface_azimuth: 90,270
modules_per_string: 29,21
strings_per_inverter: 1,1
4 Likes

Here are some of my configurations to get EMHASS working with Amber Electric (Australia) Custom Component.

@madpilot @kc_au @ThirtyDursty

The magic happens in the shell commands:

shell_command:
  dayahead_optim: "curl -i -H \"Content-Type: application/json\" -X POST -d '{}' http://localhost:5000/action/dayahead-optim"
  publish_data: "curl -i -H \"Content-Type: application/json\" -X POST -d '{}' http://localhost:5000/action/publish-data "
  post_amber_forecast: "curl -i -H 'Content-Type: application/json' -X POST -d '{\"prod_price_forecast\":{{(
          state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)
          }},\"load_cost_forecast\":{{(
          state_attr('sensor.amber_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)
          }}}' http://localhost:5000/action/dayahead-optim"
  post_emhass_forecast: "curl -i -H 'Content-Type: application/json' -X POST -d '{\"prod_price_forecast\":{{(
          state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)
          }},{{states('sensor.solcast_24hrs_forecast')}},\"load_cost_forecast\":{{(
          state_attr('sensor.amber_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)
          }}}' http://localhost:5000/action/dayahead-optim"

  post_mpc_optim: "curl -i -H \"Content-Type: application/json\" -X POST -d '{\"load_cost_forecast\":{{(
          ([states('sensor.amber_general_price')|float(0)] +
          state_attr('sensor.amber_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)[:48])
          }}, \"prod_price_forecast\":{{(
          ([states('sensor.amber_feed_in_price')|float(0)] +
          state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)[:48]) 
          }}, \"prediction_horizon\":{{min(48,
          (state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list|length)+1)
          }},\"soc_init\":{{states('sensor.powerwall_charge')|float(0)/100}},\"soc_final\":0.05,\"def_total_hours\":[8,3,2,2]}' http://localhost:5000/action/naive-mpc-optim"

But let me unpack this for you first, I recommend you do a lot of work in the Developer Tools Template first.

{{state_attr('sensor.p_batt_forecast', 'forecasts')|map(attribute='p_batt_forecast')|list|length}}
{{state_attr('sensor.p_batt_forecast', 'forecasts')|map(attribute='p_batt_forecast')|list}}

"soc_init":{{states('sensor.powerwall_charge')|float(0)/100}}
"prediction_horizon":{{min(48,state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list|length+1)}}
"load_cost_forecast":{{([states('sensor.amber_general_price')|float] + 
                         state_attr('sensor.amber_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)[:48]}}
"prod_price_forecast":{{([states('sensor.amber_feed_in_price')|float] + 
                         state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)[:48]}}
"load_now": {{(states('sensor.powerwall_load_now')|float(0)*1000)|round(0)}}
"pv_now": {{(states('sensor.powerwall_solar_now')|float(0)*1000)|round(0)}}
"def_total_hours [EV 11 kW]": {{ state_attr('sensor.duka_charging_rate_sensor','time_left')|int(2)}}
"def_total_hours [Pool Filter 1.4 kW]": 4
"def_total_hours [HVAC humidity 4 kW]": {{max(0,(state_attr('climate.family','current_humidity')|int(0) - 60)/10)|int(0)}}
"def_total_hours [HVAC temp 4 kW]": {{max(0,(state_attr('climate.bedroom','current_temperature')|float(0) -
                                        state_attr('climate.daikin_ap46401','temperature')|float))}}
"def_total_hours [HVAC No People 4 kW]": {{states('zone.home')}}
"def_total_hours [Pool Heater 5 kW]": {{states('input_number.pool_temperature_set_point')|int - 
                                   states('input_number.pool_temperature')|int}}

curl -i -H "Content-Type: application/json" -X POST -d '{"prod_price_forecast":{{(
             (state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list) )
          }},"load_cost_forecast":{{(
             (state_attr('sensor.amber_general_forecast', 'forecasts') |map(attribute='per_kwh')|list) )
          }},"prediction_horizon":{{(state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list|length)
          }},"soc_init":{{states('sensor.powerwall_charge')|float(0)/100
          }},"soc_final":0.05,"def_total_hours":[4,5,3,1]}' http://localhost:5000/action/naive-mpc-optim

  post_mpc_optim: "curl -i -H \"Content-Type: application/json\" -X POST -d '{\"prod_price_forecast\":{{(
          [states('sensor.amber_feed_in_price')|float(0)] +
          (state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list)[:47]) 
          }},\"load_cost_forecast\":{{(
          [states('sensor.amber_general_price')|float(0)] +
          (state_attr('sensor.amber_general_forecast', 'forecasts') |map(attribute='per_kwh')|list)[:47])
          }},\"prediction_horizon\":{{min(48,
          (state_attr('sensor.amber_feed_in_forecast', 'forecasts')|map(attribute='per_kwh')|list|length)+1)
          }},\"soc_init\":{{states('sensor.powerwall_charge')|float(0)/100}},\"soc_final\":0.05,\"def_total_hours\":[4,5]}' http://localhost:5000/action/naive-mpc-optim"

My Automations, either a single day-ahead optimisation or MPC running every minute.

alias: EMHASS day-ahead optimization
description: ''
trigger:
  - platform: time_pattern
    minutes: '25'
    hours: '5'
condition: []
action:
  - service: shell_command.post_amber_forecast
    data: {}
  - service: shell_command.publish_data
    data: {}
mode: single
alias: EMHASS MPC optimization
description: ''
trigger:
  - platform: time_pattern
    minutes: /1
condition: []
action:
  - service: shell_command.post_mpc_optim
    data: {}
  - service: shell_command.publish_data
    data: {}
mode: single
alias: p_deferable1 automation
description: ''
trigger:
  - platform: numeric_state
    entity_id: sensor.p_deferrable1
    above: '0.1'
    for:
      hours: 0
      minutes: 2
      seconds: 0
  - platform: numeric_state
    entity_id: sensor.p_deferrable1
    for:
      hours: 0
      minutes: 2
      seconds: 0
    below: '0.1'
condition: []
action:
  - choose:
      - conditions:
          - condition: numeric_state
            entity_id: sensor.p_deferrable1
            above: '0.1'
        sequence:
          - type: turn_on
            device_id: 03a766ec3275e2c80020c5f5a3d8e603
            entity_id: switch.pool_heater_pump
            domain: switch
          - service: notify.mobile_app_pixel_6
            data:
              message: Pool Heater on
    default:
      - type: turn_off
        device_id: 03a766ec3275e2c80020c5f5a3d8e603
        entity_id: switch.pool_heater_pump
        domain: switch
      - service: notify.mobile_app_pixel_6
        data:
          message: Pool Heater off
mode: single
alias: p_batt automation
description: ''
trigger:
  - platform: numeric_state
    entity_id: sensor.p_batt_forecast
    below: '-4000'
condition: []
action:
  - choose:
      - conditions:
          - condition: numeric_state
            entity_id: sensor.p_batt_forecast
            below: '-3000'
        sequence:
          - service: notify.mobile_app_pixel_6
            data:
              title: p_batt alert {{states('sensor.p_batt_forecast')}} - mode:backup
              message: d{{states('sensor.amber_general_price')}} $/kWh
          - service: tesla_gateway.set_operation
            data:
              real_mode: backup
      - conditions:
          - condition: numeric_state
            entity_id: sensor.p_batt_forecast
            above: '4900'
        sequence:
          - service: tesla_gateway.set_operation
            data:
              real_mode: autonomous
              backup_reserve_percent: 1
          - service: notify.mobile_app_pixel_6
            data:
              title: >-
                p_batt alert {{states('sensor.p_batt_forecast')}} -
                mode:autonomous
              message: d{{states('sensor.amber_general_price')}} $/kWh
    default:
      - service: notify.mobile_app_pixel_6
        data:
          title: >-
            p_batt alert {{states('sensor.p_batt_forecast')}} -
            mode:self_consumption
          message: d{{states('sensor.amber_general_price')}} $/kWh
      - service: tesla_gateway.set_operation
        data:
          real_mode: self_consumption
          backup_reserve_percent: 2
mode: single
6 Likes

Automation to get Tesla Powerwall Gateway to discharge to grid at 0700 and 1700 for 60 minutes (during sunrise/ sunset peak) for maximum FIT and to help the grid when under stress.

I recommend getting the excellent TeslaPy libray talking to your powerwall first. (You need the cache.json anyway)

Update: this can all now be controlled with Tesla Custom Integration

You need to do some setup first:

Tonight during the price spike event I had Home Assistant switch PW2 to Time Based Control and my battery started exporting at 5 kW, until 1800 when HA switched the PW2 back to self powered mode and stopped exporting. Needs the following TOU settings to be pre-loaded to PW2, but I find the highest FIT has been occuring around this time regularly so I can automate with HA to control the mode switching.

I do the following to discharge PW2 at 5 kW during peak FIT between 5pm - 6pm.

Setup time of use settings and prices per the attached screenshot.

Switch battery to Self-Powered and it will store and consume excess solar to match household production and load.

Switch to Time-Based Control and the Battery will charge from the grid during off peak window and discharge to the grid during peak (and sometimes mid peak).

So at 6am this morning TBC mode discharged the battery to get maximum FIT. At 1030 it started consuming excess solar and charging from the grid (upto 5 kW). At 1330 it (generally) stops charging from the grid but still charges off excess solar. At 1700 ideally your battery is at 100% and it will start discharging at 5kW as it tries to empty battery before next off peak window at 1800 to gain maximum FIT for the day.

The next step is very important, before 1800 switch back to self-consumption mode otherwise it will start charging from the grid at a very expensive price. There are some new advanced grid controls that would mitigate this.

You can edit the price schedule in the app to extend discharging window. Best I have done is $60 credit during price spike event. But be aware changing the schedule doesn’t take effect immediately I find changes take effect after the next quarter hour block (IE minute 0,15,30 or 45).

If you are uncomfortable with what the battery is doing, put it back into self-powered mode and it should settle within seconds.

You can tell battery to charge at any time by increasing reserve % (albeit slower at 3.3 kW).

I find this method extends the charging/ discharging windows for longer than SmartShift, if that is what you want. Works well with Amber pricing being so different at different times.

Home Assistant/ python scripts allow you to change between modes; self_consumption, backup (100% reserve) and autonomous (time based control) or set reserve % directly, so you can automate the above process based off your own rules. Home Assistant doesn’t allow you to change your tou windows.

This is actually quite helpful as it is supplying extra capacity to the grid when grid is under stress and you gain access to higher FIT and the financial benefits.

3 Likes

Nice setup, thanks for the sharing. This can be really helpful for Amber AU users :+1:

1 Like

Nice graphics also with the ApexCharts! There is also the option of the Plotly card from HACS. The local UI is using the Plolty library for Python charts… That local UI is just intended for debugging when setting up emhass.

HI @markpurcell , I cant send you enough Thank You’s and if I ever meet you, beers are on me!

I will have a few questions, but here is my first:

Your calling “sensor.solcast_24hrs_forecast”. What value are you populating here? I am using the HACS Solcast Integration and my options are Forecast Today, Forecast Today Remaining or Forecast Tomorrow. All returns a single instance value ie/ 40KW for today. Are you derving this value in YAML or how are you collecting this info? Are you using the core Solcast HACS or are you using another version?

2 Likes

Ah, you found that one…

I would focus on just getting amber buy/sell prices first and use the inbuild pvlib estimation for solar production which is pretty good. post_amber_forecast

You don’t need to use all of those scripts at the same time, rather they are different ways to populate the future forecasts.

I have a vision of submitting both actual current and forecast pv production, which is where that is going.

Unfortunately the solcast integration only provides 60 minute resolution and I wanted to run emhass on 30 minute resolution to match the amber pricing windows.

A lot of great discussion and visualisation for SolCast here: REST API command/platform fails on POST to external URL (solcast) - #132 by safepay

Something like this to grab the solcast data direct, I still need to convert kW to W and get into correct sensor (note sensors have 255 character limits - which can be exceeded when having a full list of 24 hrs - hence the use of attributes):

- platform: rest
  name: "Solcast Forecast Data"
  json_attributes:
    - forecasts
  resource: https://api.solcast.com.au/rooftop_sites/SOLCAST_RESOURCE_ID/forecasts?format=json&api_key=SOLCAST_API_KEY&hours=24
  method: GET
  value_template: "{{ value_json.forecasts[0].pv_estimate|round(2) }}"
  unit_of_measurement: "W"
  device_class: power
  scan_interval: 00:30
  force_update: true

Today’s plan. Feed-in price stays above 60¢/ kWh all day (peaks at $15/ kWh tonight), which is higher than the 40¢ / kWh general price tomorrow morning!

EMHASS daily optimisation is saying to shift all deferrable loads to tomorrow to maximise battery charging (to export over tonight’s peak @ $10-$15) and maximise exports during the day @ 60¢.

A lot of interest in EMHASS from the Australian Amber users facebook group as we try and optimise our way through a crazy electricity market. Redirecting...

3 Likes

Nice discussion. I’m currently working on a hopefully better load forecast module. I’m a data scientist, so now that EMHASS seems quite stable the next step is to improve those forecast. The naive persistence approach is basic but robust. I’m testing to see what kind of model could we use to obtain better load forecasts, better than the naive method. This is currently narrowed to RNN (Recurrent neural networks) and LSTM ( Long short-term memory) models. They seem to give some nice results. I’m working on this right now.

4 Likes

@davidusb A small feature request would be to put totals on the columns:
cost_profit cost_fun_profi
or like a 24 hour forecast of cost/profit so you can see how the model is going to perform as a whole for the period.

1 Like

Hi, yes this can be done. An additional table that sums up the results shown in the table. The total cost is already been printed on the add-on logger though. This result is printed every time that an optimization routine is launched.

That m it’s would be useful to have the sum of cost brought through into HA, or just bring cost_profit and others with attributes, when could then be summed inside HA.

apexcharts will sum data series, so it would be good to put in the header.

Could I also ask you consider bringing the other variables across, like SOC and p_grid as that can help with control.

This can be done for p_grid.

For the SOC this is already done. Aren’t you able to see sensor.soc_batt_forecast with its future values as attributes?

Advanced a bit on new load forecasting methods.

Here is a quite decent result for the N-BEATS model:

3 Likes

The pattern of life.

Interesting to see how each day is similar but different.

Of course the Mon-Fri pattern is often similar but different to the Sat-Sun pattern, so it would be interesting to see how the forecasting model accommodates that.

My observation of the current EMHASS forecasting is that not surprisingly if yesterday was abnormal (ie higher or lower consumption) that then flows into the current optimisation. Your approach with N-BEATS and other advanced models will address this.

One suggestion from me is to include the now actual power load value into the modelling. (sensor.power_load_no_var_loads)

As you are aware I am running the mpc optimisation updated every minute. Into that model I am injecting the buy/ sell values, and using method_ts_round first, I include the current now value plus the subsequent forecast values for future timeslots.

Given EMHASS already had access to the now values for load (sensor.power_load_no_var_loads) and production (sensor.power_photovoltaics), it would seem to make sense when using method_ts_round first to use the actual now values when developing the forecasts.

In practice I would see these now values replacing the now values in any EMHASS forecast and subsequent calculations. For example I can see in the optimisation results many examples when EMHASS sets the battery power value to match baseload or PV production values, to produce a net zero effect. Pvlib at that instant maybe forecasting 3000W production and then EMHASS calculates p_batt_forecast to -3000W to balance. But sensor.power_photovoltaics may be 2000W, thus p_batt_forecast should be calculated to match at -2000W, because if we set to -3000W we need to draw that additional 1000W power from the grid.

I am aware I can inject the now values myself, as I am doing with the buy/ sell values. But if I generate my own forecasts for PV production including the now value, I loose access to the benefits of pvlib and if I generate my own load forecasts including the now value I would loose access to N-BEATS or other advanced modelling.

I hope that makes sense, but happy to discuss further if useful.