Running EMHASS
To run EMHASS, we want to execute the rest commands we setup earlier with all the parameters they need. In this guide we will use scripts.
You will also need to manually enable these disabled Sigenergy Plant sensors in order for the script to function:
sensor.sigen_plant_rated_energy_capacitysensor.sigen_plant_ess_rated_charging_powersensor.sigen_plant_ess_rated_discharging_powersensor.sigen_plant_max_active_power
Solcast by default only enables today and tomorrow solar forecast sensors, meaning at 11pm you’ll only get 25 hours of forecasting. You will need to enable the solcast day 3 forecast sensor to always provide 2 full days of solar forecasting (and if you want longer horizons, you can enable up to 7 days and add them to the forecasts list within the script).
sensor.solcast_pv_forecast_forecast_day_3
Next, create a new script from scratch (under Settings → Automations & Scenes → Scripts), switch to YAML mode and add the content below.
The critical variables to configure are at the very top.
costfun— this can be eitherprofit,costorself-consumption, which either maximize profit, minimize cost or maximize self-consumption (selling any excess).maximum_power_from_grid— this is the maximum power you can draw from the grid (e.g. when charging your batteries from the grid).maximum_power_to_grid— this is the maximum power you can feed into the grid (e.g. when exporting solar, or discharging batteries).battery_minimum_percent— what percentage (0-100) of your battery you always want to keep as a minimum (e.g. for blackout protection).
sequence:
- variables:
costfun: profit
maximum_power_from_grid: 15000
maximum_power_to_grid: 10000
battery_minimum_percent: 10
alias: Configure critical settings
- variables:
num_prediction_days: 2
optimization_time_step: 5
weight_battery_charge: 0.02
weight_battery_discharge: 0.02
sensor_prefix: mpc_
sensor_name_prefix: "MPC "
alias: Configure secondary settings
- variables:
sensors:
energy_capacity: sensor.sigen_plant_rated_energy_capacity
charging_power: sensor.sigen_plant_ess_rated_charging_power
discharging_power: sensor.sigen_plant_ess_rated_discharging_power
max_inverter_power: sensor.sigen_plant_max_active_power
battery_soc: sensor.sigen_plant_battery_state_of_charge
consumed_power: sensor.sigen_plant_consumed_power
solar:
current_power: sensor.sigen_plant_pv_power
forecasts:
- sensor.solcast_pv_forecast_forecast_today
- sensor.solcast_pv_forecast_forecast_tomorrow
- sensor.solcast_pv_forecast_forecast_day_3
amber:
general_price: sensor.home_general_price
general_forecast: sensor.home_general_forecast
feed_in_price: sensor.home_feed_in_price
feed_in_forecast: sensor.home_feed_in_forecast
alias: Configure sensor entities
- action: recorder.get_statistics
data:
start_time: "{{ (now() - timedelta(days=1)).isoformat() }}"
end_time: "{{ now().isoformat() }}"
statistic_ids: "{{ sensors.consumed_power }}"
period: hour
types: mean
response_variable: history
alias: Fetch load history
- variables:
num_forecasts: "{{ (60 / optimization_time_step * 24 * num_prediction_days) | int }}"
soc_init: "{{ (states(sensors.battery_soc) | float(0) / 100) | round(3) }}"
soc_final: "{{ battery_minimum_percent / 100 }}"
now_iso: "{{ now().replace(microsecond=0).isoformat() }}"
max_inverter_power: "{{ (states(sensors.max_inverter_power) | float(0) * 1000) | round }}"
common:
costfun: "{{ costfun }}"
prediction_horizon: "{{ num_forecasts }}"
optimization_time_step: "{{ optimization_time_step }}"
delta_forecast_daily: "{{ num_prediction_days }}"
set_use_pv: true
set_use_battery: true
inverter_is_hybrid: true
number_of_deferrable_loads: 0
set_nodischarge_to_grid: false
lp_solver: HiGHS
payload:
weight_battery_charge: "{{ weight_battery_charge }}"
weight_battery_discharge: "{{ weight_battery_discharge }}"
battery_minimum_state_of_charge: "{{ battery_minimum_percent / 100 }}"
battery_maximum_state_of_charge: 1
battery_nominal_energy_capacity: "{{ (states(sensors.energy_capacity) | float(0) * 1000) | round }}"
battery_charge_power_max: "{{ (states(sensors.charging_power) | float(0) * 1000) | round }}"
battery_discharge_power_max: "{{ (states(sensors.discharging_power) | float(0) * 1000) | round }}"
maximum_power_from_grid: "{{ maximum_power_from_grid }}"
maximum_power_to_grid: "{{ maximum_power_to_grid }}"
inverter_ac_output_max: "{{ max_inverter_power }}"
inverter_ac_input_max: "{{ max_inverter_power }}"
soc_init: "{{ soc_init }}"
soc_final: "{{ soc_final }}"
load_cost_forecast: |-
{% set ns = namespace(
input=(
state_attr(sensors.amber.general_forecast, 'forecasts') | list
) | selectattr('per_kwh', 'is_number') | list,
output={
now_iso: states(sensors.amber.general_price) | float(0)
}
) %}
{% for day in range(num_prediction_days) %}
{% for forecast in ns.input %}
{% set start = forecast.start_time | as_datetime | as_local + timedelta(days=day) %}
{% set price = forecast.per_kwh | float(0) %}
{% set ns.output = ns.output | combine({ start.isoformat(): price }) %}
{% endfor %}
{% endfor %}
{{ ns.output }}
prod_price_forecast: |-
{% set ns = namespace(
input=(
state_attr(sensors.amber.feed_in_forecast, 'forecasts') | list
) | selectattr('per_kwh', 'is_number') | list,
output={
now_iso: states(sensors.amber.feed_in_price) | float(0)
}
) %}
{% for day in range(num_prediction_days) %}
{% for forecast in ns.input %}
{% set start = forecast.start_time | as_datetime | as_local + timedelta(days=day) %}
{% set price = forecast.per_kwh | float(0) %}
{% set ns.output = ns.output | combine({ start.isoformat(): price }) %}
{% endfor %}
{% endfor %}
{{ ns.output }}
pv_power_forecast: |-
{% set ns = namespace(
input=sensors.solar.forecasts
| map('state_attr', 'detailedForecast')
| sum(start=[])
| selectattr('period_start', '>', now())
| selectattr('period_start', '<=', now() + timedelta(days=num_prediction_days)),
output={
now_iso: (states(sensors.solar.current_power) | float(0) * 1000) | round
}
) %}
{% for solar in ns.input %}
{% set key = (solar.period_start | as_datetime | as_local).isoformat() %}
{% set value = (solar.pv_estimate * 1000) | round %}
{% set ns.output = ns.output | combine({ key: value }) %}
{% endfor %}
{{ ns.output }}
load_power_forecast: |-
{% set ns = namespace(
input=history.statistics[sensors.consumed_power],
output={
now_iso: (states(sensors.consumed_power) | float(0) * 1000) | round(0)
}
) %}
{% for day in range(num_prediction_days) %}
{% for load in ns.input %}
{% set load_start = load.start | as_datetime | as_local + timedelta(days=day + 1) %}
{% set load_value_watts = (load.mean | float(0) * 1000) | round(0) %}
{% set ns.output = ns.output | combine({ load_start.isoformat(): load_value_watts }) %}
{% endfor %}
{% endfor %}
{{ ns.output }}
alias: Calculate payload
- action: rest_command.emhass_naive_mpc_optim
metadata: {}
data:
payload: "{{ combine(common, payload) | to_json(pretty_print=true) }}"
alias: Run EMHASS
- variables:
payload:
custom_pv_forecast_id:
entity_id: sensor.{{ sensor_prefix }}pv_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}PV Power"
custom_load_forecast_id:
entity_id: sensor.{{ sensor_prefix }}load_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Load Power"
custom_hybrid_inverter_id:
entity_id: sensor.{{ sensor_prefix }}inverter_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Inverter Power"
custom_batt_forecast_id:
entity_id: sensor.{{ sensor_prefix }}batt_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Battery Power"
custom_grid_forecast_id:
entity_id: sensor.{{ sensor_prefix }}grid_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Grid Power"
custom_batt_soc_forecast_id:
entity_id: sensor.{{ sensor_prefix }}batt_soc
unit_of_measurement: "%"
device_class: battery
friendly_name: "{{ sensor_name_prefix }}Battery SOC"
custom_cost_fun_id:
entity_id: sensor.{{ sensor_prefix }}cost_fun
unit_of_measurement: $
device_class: monetary
friendly_name: "{{ sensor_name_prefix }}Cost Function"
custom_unit_load_cost_id:
entity_id: sensor.{{ sensor_prefix }}general_price
unit_of_measurement: $
device_class: monetary
friendly_name: "{{ sensor_name_prefix }}Buy Price"
custom_unit_prod_price_id:
entity_id: sensor.{{ sensor_prefix }}feed_in_price
unit_of_measurement: $
device_class: monetary
friendly_name: "{{ sensor_name_prefix }}Sell Price"
custom_optim_status_id:
entity_id: sensor.{{ sensor_prefix }}optim_status
unit_of_measurement: ""
friendly_name: "{{ sensor_name_prefix }}Optimisation Status"
- action: rest_command.emhass_publish_data
metadata: {}
data:
payload: "{{ combine(common, payload) | to_json(pretty_print=true) }}"
alias: Publish Energy Plan to HA
alias: Generate EMHASS Energy Plan (MPC)
description: Runs EMHASS MPC optimizer, generating an optimal energy plan
sequence:
- variables:
costfun: profit
maximum_power_from_grid: 15000
maximum_power_to_grid: 10000
battery_minimum_percent: 10
alias: Configure critical settings
- variables:
num_prediction_days: 2
optimization_time_step: 5
weight_battery_charge: 0.02
weight_battery_discharge: 0.02
sensor_prefix: mpc_
sensor_name_prefix: "MPC "
alias: Configure secondary settings
- variables:
sensors:
energy_capacity: sensor.sigen_plant_rated_energy_capacity
charging_power: sensor.sigen_plant_ess_rated_charging_power
discharging_power: sensor.sigen_plant_ess_rated_discharging_power
max_inverter_power: sensor.sigen_plant_max_active_power
battery_soc: sensor.sigen_plant_battery_state_of_charge
consumed_power: sensor.sigen_plant_consumed_power
solar:
current_power: sensor.sigen_plant_pv_power
forecasts:
- sensor.solcast_pv_forecast_forecast_today
- sensor.solcast_pv_forecast_forecast_tomorrow
- sensor.solcast_pv_forecast_forecast_day_3
amber:
general_price: sensor.amber_5min_current_general_price
general_forecast: sensor.amber_5min_forecasts_extended_general_price
feed_in_price: sensor.amber_5min_current_feed_in_price
feed_in_forecast: sensor.amber_5min_forecasts_extended_feed_in_price
alias: Configure sensor entities
- action: recorder.get_statistics
data:
start_time: "{{ (now() - timedelta(days=1)).isoformat() }}"
end_time: "{{ now().isoformat() }}"
statistic_ids: "{{ sensors.consumed_power }}"
period: hour
types: mean
response_variable: history
alias: Fetch load history
- variables:
num_forecasts: "{{ (60 / optimization_time_step * 24 * num_prediction_days) | int }}"
soc_init: "{{ (states(sensors.battery_soc) | float(0) / 100) | round(3) }}"
soc_final: "{{ battery_minimum_percent / 100 }}"
now_iso: "{{ now().replace(microsecond=0).isoformat() }}"
max_inverter_power: "{{ (states(sensors.max_inverter_power) | float(0) * 1000) | round }}"
common:
costfun: "{{ costfun }}"
prediction_horizon: "{{ num_forecasts }}"
optimization_time_step: "{{ optimization_time_step }}"
delta_forecast_daily: "{{ num_prediction_days }}"
set_use_pv: true
set_use_battery: true
inverter_is_hybrid: true
number_of_deferrable_loads: 0
set_nodischarge_to_grid: false
lp_solver: HiGHS
payload:
weight_battery_charge: "{{ weight_battery_charge }}"
weight_battery_discharge: "{{ weight_battery_discharge }}"
battery_minimum_state_of_charge: "{{ battery_minimum_percent / 100 }}"
battery_maximum_state_of_charge: 1
battery_nominal_energy_capacity: "{{ (states(sensors.energy_capacity) | float(0) * 1000) | round }}"
battery_charge_power_max: "{{ (states(sensors.charging_power) | float(0) * 1000) | round }}"
battery_discharge_power_max: "{{ (states(sensors.discharging_power) | float(0) * 1000) | round }}"
maximum_power_from_grid: "{{ maximum_power_from_grid }}"
maximum_power_to_grid: "{{ maximum_power_to_grid }}"
inverter_ac_output_max: "{{ max_inverter_power }}"
inverter_ac_input_max: "{{ max_inverter_power }}"
soc_init: "{{ soc_init }}"
soc_final: "{{ soc_final }}"
load_cost_forecast: |-
{% set ns = namespace(
input=(
state_attr(sensors.amber.general_forecast, 'Forecasts') | list
) | selectattr('per_kwh', 'is_number') | list,
output={
now_iso: states(sensors.amber.general_price) | float(0)
}
) %}
{% for day in range(num_prediction_days) %}
{% for forecast in ns.input %}
{% set start = forecast.start_time | as_datetime | as_local + timedelta(days=day) %}
{% set price = forecast.per_kwh | float(0) %}
{% set ns.output = ns.output | combine({ start.isoformat(): price }) %}
{% endfor %}
{% endfor %}
{{ ns.output }}
prod_price_forecast: |-
{% set ns = namespace(
input=(
state_attr(sensors.amber.feed_in_forecast, 'Forecasts') | list
) | selectattr('per_kwh', 'is_number') | list,
output={
now_iso: states(sensors.amber.feed_in_price) | float(0)
}
) %}
{% for day in range(num_prediction_days) %}
{% for forecast in ns.input %}
{% set start = forecast.start_time | as_datetime | as_local + timedelta(days=day) %}
{% set price = -forecast.per_kwh | float(0) %}
{% set ns.output = ns.output | combine({ start.isoformat(): price }) %}
{% endfor %}
{% endfor %}
{{ ns.output }}
pv_power_forecast: |-
{% set ns = namespace(
input=sensors.solar.forecasts
| map('state_attr', 'detailedForecast')
| sum(start=[])
| selectattr('period_start', '>', now())
| selectattr('period_start', '<=', now() + timedelta(days=num_prediction_days)),
output={
now_iso: (states(sensors.solar.current_power) | float(0) * 1000) | round
}
) %}
{% for solar in ns.input %}
{% set key = (solar.period_start | as_datetime | as_local).isoformat() %}
{% set value = (solar.pv_estimate * 1000) | round %}
{% set ns.output = ns.output | combine({ key: value }) %}
{% endfor %}
{{ ns.output }}
load_power_forecast: |-
{% set ns = namespace(
input=history.statistics[sensors.consumed_power],
output={
now_iso: (states(sensors.consumed_power) | float(0) * 1000) | round(0)
}
) %}
{% for day in range(num_prediction_days) %}
{% for load in ns.input %}
{% set load_start = load.start | as_datetime | as_local + timedelta(days=day + 1) %}
{% set load_value_watts = (load.mean | float(0) * 1000) | round(0) %}
{% set ns.output = ns.output | combine({ load_start.isoformat(): load_value_watts }) %}
{% endfor %}
{% endfor %}
{{ ns.output }}
alias: Calculate payload
- action: rest_command.emhass_naive_mpc_optim
metadata: {}
data:
payload: "{{ combine(common, payload) | to_json(pretty_print=true) }}"
alias: Run EMHASS
- variables:
payload:
custom_pv_forecast_id:
entity_id: sensor.{{ sensor_prefix }}pv_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}PV Power"
custom_load_forecast_id:
entity_id: sensor.{{ sensor_prefix }}load_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Load Power"
custom_hybrid_inverter_id:
entity_id: sensor.{{ sensor_prefix }}inverter_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Inverter Power"
custom_batt_forecast_id:
entity_id: sensor.{{ sensor_prefix }}batt_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Battery Power"
custom_grid_forecast_id:
entity_id: sensor.{{ sensor_prefix }}grid_power
unit_of_measurement: W
device_class: power
friendly_name: "{{ sensor_name_prefix }}Grid Power"
custom_batt_soc_forecast_id:
entity_id: sensor.{{ sensor_prefix }}batt_soc
unit_of_measurement: "%"
device_class: battery
friendly_name: "{{ sensor_name_prefix }}Battery SOC"
custom_cost_fun_id:
entity_id: sensor.{{ sensor_prefix }}cost_fun
unit_of_measurement: $
device_class: monetary
friendly_name: "{{ sensor_name_prefix }}Cost Function"
custom_unit_load_cost_id:
entity_id: sensor.{{ sensor_prefix }}general_price
unit_of_measurement: $
device_class: monetary
friendly_name: "{{ sensor_name_prefix }}Buy Price"
custom_unit_prod_price_id:
entity_id: sensor.{{ sensor_prefix }}feed_in_price
unit_of_measurement: $
device_class: monetary
friendly_name: "{{ sensor_name_prefix }}Sell Price"
custom_optim_status_id:
entity_id: sensor.{{ sensor_prefix }}optim_status
unit_of_measurement: ""
friendly_name: "{{ sensor_name_prefix }}Optimisation Status"
- action: rest_command.emhass_publish_data
metadata: {}
data:
payload: "{{ combine(common, payload) | to_json(pretty_print=true) }}"
alias: Publish Energy Plan to HA
alias: Generate EMHASS Energy Plan (MPC)
description: Runs EMHASS MPC optimizer, generating an optimal energy plan
If you run this script, EMHASS will produce a plan for the day. You should be able to manually run the script and check the output in the EMHASS webview. We will also be adding our own output dashboard shortly.
You’ll want to be running this script regularly as forecasts change throughout the day, so the next step is to setup a new automation:
alias: "Generate EMHASS energy plan"
description: "Automatically generate EMHASS energy plan when prices change"
triggers:
- trigger: state
entity_id:
- sensor.home_general_price
- sensor.home_feed_in_price
enabled: true
conditions: []
actions:
- action: script.generate_emhass_energy_plan_mpc
metadata: {}
data: {}
mode: single
alias: "Generate EMHASS energy plan"
description: "Automatically generate EMHASS energy plan when prices change"
triggers:
- trigger: state
entity_id:
- sensor.amber_5min_current_general_price
- sensor.amber_5min_current_feed_in_price
enabled: true
conditions: []
actions:
- action: script.generate_emhass_energy_plan_mpc
metadata: {}
data: {}
mode: single
This will run every time the prices change.
Note: On my Intel NUC Home Assistant server, this script takes a couple of seconds to run. If you are running lower-end hardware, you may want to either increase the optimization time step or reduce the running frequency.