EMHASS: An Energy Management for Home Assistant

I have made this. this might work:

var pGridForecast = msg.payload || 0;

var pBattForecast = msg.pBattForecast || 0;

var inverter1SOC = msg.inverter1_battery_soc || 0;

if (inverter1SOC > 90) {

    msg.payload = 40; // Skip updating status

} else if (inverter1SOC < 35 && pGridForecast > 0 && pBattForecast === 0) {

    msg.payload = 0; // Update status to 0

} else if (pGridForecast > 0 && pBattForecast === 0) {

    msg.payload = 5; // Update status to 5

} else {

    msg.payload = 40; // Update status to 40

}

msg.pBattForecast = pBattForecast;

return msg;

noderedflow:

[{"id":"d14f79f6.89f5b8","type":"server-state-changed","z":"b8d78ce2.c7d748","name":"P-grid-forecast","server":"b5e5456a.81935","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.p_grid_forecast","entityidfiltertype":"exact","outputinitially":true,"state_type":"num","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":210,"y":160,"wires":[["d71a7e46.4e16b"]]},{"id":"b3621522.a38938","type":"server-state-changed","z":"b8d78ce2.c7d748","name":"P-batt-forecast","server":"b5e5456a.81935","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.p_batt_forecast","entityidfiltertype":"exact","outputinitially":true,"state_type":"num","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":180,"y":280,"wires":[["d71a7e46.4e16b"]]},{"id":"d71a7e46.4e16b","type":"function","z":"b8d78ce2.c7d748","name":"Sjekk betingelser","func":"var pGridForecast = msg.payload || 0;\nvar pBattForecast = msg.pBattForecast || 0;\nvar inverter1SOC = msg.inverter1_battery_soc || 0;\n\nif (inverter1SOC > 90) {\n    msg.payload = 40; // Skip updating status\n} else if (inverter1SOC < 35 && pGridForecast > 0 && pBattForecast === 0) {\n    msg.payload = 0; // Update status to 0\n} else if (pGridForecast > 0 && pBattForecast === 0) {\n    msg.payload = 5; // Update status to 5\n} else {\n    msg.payload = 40; // Update status to 40\n}\n\nmsg.pBattForecast = pBattForecast;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":220,"wires":[["72c3abf8.3efb7c"]]},{"id":"72c3abf8.3efb7c","type":"api-call-service","z":"b8d78ce2.c7d748","name":"Oppdater status","server":"b5e5456a.81935","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_text.emhass_deye_charge_amps"],"data":"{\"value\":\"{{payload}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","output_location":"","output_location_type":"none","x":780,"y":220,"wires":[[]]},{"id":"3cbf2a09510eb66a","type":"server-state-changed","z":"b8d78ce2.c7d748","name":"SOC","server":"b5e5456a.81935","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.inverter1_battery_soc","entityidfiltertype":"exact","outputinitially":true,"state_type":"num","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":100,"wires":[["d71a7e46.4e16b"]]},{"id":"b5e5456a.81935","type":"server","name":"Home Assistant","addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

It may work, but can you describe what it is trying to achieve?

just to delay charging of battery when its more profitable to export in the morning. The deye inverter that i use is smart so i do not need to define how much discharge or charge it will calculate and do grid 0 by it self if its possible.

EMHASS will already calculate for you if it more profitable to charge the battery or export in the morning. You shouldnā€™t need to hard code rules and in fact hard coded rules maybe suboptimal.

Here is my plan for the next 24 hours:

You can see my solar production starts ramping up from 0600. Because the the combination of my buy/ sell prices EMHASS has determined it is more profitable to not only delay charging but actively export from the battery to the grid until 0800 (blue shade above the line and green shade below the line) and then from 0800-1000 delay charging my battery (green shade below the line) because it is still more profitable to export the generated solar PV.

You can see my battery charging all day due to the cheap solar PV and from the grid for 90 minutes at midday over the cheapest time of the day. Importantly it was calculated to be more beneficial to export in the morning (delay charging) and charge from the grid, than to start charging at the start of the day and not need to charge from the grid at midday.

Then at 6pm it is beneficial to export from my battery to the grid for a high export price.

Can you show the plots EMHASS generates for you either via these apex-charts or via the local interface at http://HomeAssistant.local:5000

I think i will just run on the power from the batteries for self consuption instead of exporting to the grid.

maby i need to change profile from profit to self-consumption?

Those numbers look good to me.

Charge battery from grid at 0400 (load_cost:1.04) because it is cheaper than grid cost between 0600-0900 (load_cost: 1.35-1.45).

Delay charging battery from solar until 1300-1600 because it is the lowest export price of the day (prod_price: 0.08-0.16).

What times do you think would be better to charge battery?

yes I agree with this. Just have struggeling to how to tell the inverter to do this. But I think i have found the way explained in the nodered flow.

I have another problem as well. The table with the prices looks correct. But when updating the price for the next hour updates 30 minutes early. Why do it do this and not example get hour for 14:00 updates at 14:00?

You could always try and see if that is the outcome you want

On your node red flow you seem to be seeing the payload to different values; 0, 5 or 40.

Do these values represent the requested charging amps for your inverter?

Which method do you control your inverter; setting desired state of charge (SOC), or setting charging amps (postive for charging and negative for discharging)?

It would be good if you could provide an example.

Charging the value for time stamp rounding in the configuration also impacts when the value is applied in the time sequence.

thanks. did not know about this one. will try to change this. And provide an example later if its not helping :slight_smile:

I changes the SOC to what emhass forecast, when I do this the battery will charge to this from the grid if soc is lower. If higher the inverter will just grid peak shave and recharge to the set soc. If there is lots of pv it will charge untill 100% thats why I need to limit the charge amps.

There is also a grid meter the inveter uses to compancate the buy energy to 0 at all times if the soc is higher than the set soc. if lower grid energy will be used.

Mabey its badly explained but Im trying :slight_smile:

you can see it explained here 3:50:
https://www.google.com/search?q=deye+hybrid&safe=active&rlz=1C1GCEB_enNO1039NO1039&sxsrf=APwXEdewuScwcjzU2dkCRIWeyhfMO34rtA:1685097442261&source=lnms&tbm=vid&sa=X&ved=2ahUKEwjfw9ST5ZL_AhWQbPEDHQMqAIwQ_AUoAnoECAEQBA&biw=1920&bih=969&dpr=1#fpstate=ive&vld=cid:fa44e90e,vid:V6hs4P4SVJ8

what I set from emhass is the time of use soc.

1 Like

have tried them all. Cant find anything about this in the documentation explaining how they work. Seems like the profit works best for me exept that it wants to export to grid sometimes from the battery. I do not want this so I rather just feed my grid to 0 watts as long as there is enough energy and the soc is over the soc emhass recommends.

1 Like

Are you still using solcast to forecast solar? And, are you calling the EMHASS API to update the forecast throughout the day/night?
If so, how are you handling passing in the correct number of samples to ensure your end battery SoC is correctly calculated? It looks like your graph extends for 24hrs into the future from when you took the screenshot.

At the moment Iā€™m using the following, inspired by the docs, which appear to have been based on your post above in the first place. However Iā€™m only calling this once a day (at 23:30), when my lower import rate starts. Since I donā€™t get any money for exporting, Iā€™m storing all that I can use, and having to export the restā€¦ This is today for example:

Iā€™d like to keep the solar forecast up-to-date. It looks like you are still plotting a 24hr window constantly? Or are you also only calling it once?

Hereā€™s my current config API calls:

publish_data: 'curl -i -H "Content-Type:application/json" -X POST -d ''{}'' http://localhost:5000/action/publish-data'
post_mpc_optim_solcast: 'curl -i -H "Content-Type: application/json" -X POST -d ''{"prediction_horizon":48,"pv_power_forecast":{{ states("sensor.solcast_24hrs_forecast") }}, "soc_init":{{ (states("sensor.battery_soc")|float(0))/100 }}}'' http://localhost:5000/action/naive-mpc-optim'

The optimisation call is run at 2330, and the publish just after that.

My EMHASS config uses the following for the SoC defaults:

battery_minimum_state_of_charge: 0.3
battery_maximum_state_of_charge: 0.9
battery_target_state_of_charge: 0.35

Also, I assume youā€™re doing something clever with your car, to ensure that it doesnā€™t drain from the powerwall (or maybe Tesla just does that all for you). Iā€™ve also got an EV, but since we arenā€™t charging every day, Iā€™ve set the deferrable load hrs to 0, so that it doesnt show up. At present Iā€™m just changing my battery state to not discharge whenever the car is charging (itā€™s an MG4 with an Ohme charger, so Iā€™ve got no smart control over it via Home Assistant)

Thanks!

Yes, I continue to employ Solcast for solar predictions, and I am indeed invoking the EMHASS API to update these forecasts around the clock.

Iā€™m implementing a complex template formula for the PV forecast that leverages Solcast, as illustrated in the YAML code below:

\"pv_power_forecast\":{{[states('sensor.APF_Generation_Entity')|int(0)]
            + state_attr('sensor.solcast_forecast_today', 'detailedForecast')|selectattr('period_start','gt',utcnow())
              | map(attribute='pv_estimate')|map('multiply',2000)|map('int')|list 
            + state_attr('sensor.solcast_forecast_tomorrow', 'detailedForecast')|selectattr('period_start','gt',utcnow())
              | map(attribute='pv_estimate')|map('multiply',2000)|map('int')|list}}

[states('sensor.APF_Generation_Entity')|int(0)]

Live Data

[4136]

This data is the first entry since Iā€™m running the EMHASS MPC optimization every minute. Therefore, I use the live data for all my forecast variables (like load, price, cost, and PV). If youā€™re implementing a daily optimization process, injecting live data isnā€™t necessary.

Todayā€™s Forecast

+ state_attr('sensor.solcast_forecast_today', 'detailedForecast')|selectattr('period_start','gt',utcnow())
              | map(attribute='pv_estimate')|map('multiply',2000)|map('int')|list 

  • detailedForecast attribute returns the 30 minute interval forecasts from solcast_forecast_today
  • period_start greaterthan utcnow() ensures I only return future forecasts
  • extract only the pv_estimate which is the forecast energy (kWh) over the 30 minutes
  • multiply by 2000 to convert forecast energy (kWh) into the 30 minute average power (W) required by EMHASS

Tomorrowā€™s Forecast

+ state_attr('sensor.solcast_forecast_tomorrow', 'detailedForecast')|selectattr('period_start','gt',utcnow())
              | map(attribute='pv_estimate')|map('multiply',2000)|map('int')|list

Add in forecast_tomorrow in case forecast_today after utcnow is less than the required 48

2 Likes

Iā€™m doing three things with my car, unfortunately charging the car still drains the household battery by default, even with all Tesla. To overcome this I have an automation to set the Powerwall backup reserve to the SOC_forecast from EMHASS when charging the EV. The Powerwall wonā€™t discharge below this backup reserve so when Iā€™m charging my EV it pulls from excess solar and the grid, which is what I want.

The second thing I do is have a complex template to calculate the def_hours for EV charging to inject into EMHASS.

    - name: def_total_hours_ev
      state: "{{is_state('automation.p_deferable2_automation','on')|abs
             * (is_state('device_tracker.duka_location_tracker','home')|abs) 
             * (is_state('binary_sensor.duka_charger', 'on') | abs) 
             * (((states('number.duka_charge_limit')|int(0) - states('sensor.duka_battery')|int(0))/10+0.9)|int(0))|int(0)}}" 

Hereā€™s a more detailed breakdown of the calculation:

  1. is_state('automation.p_deferable2_automation','on')|abs: This checks if the automation p_deferable2_automation is currently on. This confirms that I actually have my EV charging automation switched on.
  2. is_state('device_tracker.duka_location_tracker','home')|abs: This checks if the device duka_location_tracker is currently at home. This confirms the car is at home.
  3. is_state('binary_sensor.duka_charger', 'on') | abs: This checks if the binary sensor duka_charger is currently on. This confirms the location tracker for the car is home.
  4. (((states('number.duka_charge_limit')|int(0) - states('sensor.duka_battery')|int(0))/10+0.9)|int(0))|int(0): This calculates the number of hours needed to charge the EV based on the current battery level (sensor.duka_battery) and the charge limit (number.duka_charge_limit). The difference between the charge limit and the current battery level is divided by 10 (because the charger adds 10 units of charge per hour), then rounded up to the nearest whole number by adding 0.9 and converting to an integer.

The result of each of these four parts is then multiplied together to get the final state of the def_total_hours_ev sensor. If any of the conditions are not met (i.e., the automation is off, the device tracker is not at home, or the charging cable is not plugged in), then the state will be 0. Otherwise, it will be the calculated number of hours needed to charge the EV to the specified limit.

The final piece is to set the EV charging_amps to match the EMHASS forecast for EV charging (deferrable load 2).

Thank you! I appreciate the detailed explaination

One question outstanding I think, was about the final battery SoC. If youā€™re calling the model evey minute, what are you doing for the soc_final value? Surely if youā€™re forecasting the next 24hrs every minute, that end SoC value will be all over the place?

Can I see your current shell command template that youā€™re using to call the EMHASS API please?

Thanks