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_capacity
sensor.sigen_plant_ess_rated_charging_power
sensor.sigen_plant_ess_rated_discharging_power
sensor.sigen_plant_max_active_power
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.
cost_fun
— this can be eitherprofit
,cost
orself-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:
cost_fun: profit
maximum_power_from_grid: 15000
maximum_power_to_grid: 10000
battery_minimum_percent: 10
alias: Configure critical settings
- variables:
num_prediction_days: 1
optimization_time_step: 5
load_history_days_ago: 1
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
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
- variables:
start_time: |-
{% set delta = timedelta(days=load_history_days_ago) %}
{{ (now() - delta).isoformat() }}
end_time: |-
{% set delta = timedelta(days=load_history_days_ago) %}
{% set duration = timedelta(days=num_prediction_days) %}
{{ (now() - delta + duration).isoformat() }}
alias: Calculate variables
- action: recorder.get_statistics
data:
start_time: "{{ start_time }}"
end_time: "{{ end_time }}"
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:
cost_fun: "{{ cost_fun }}"
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
payload:
weight_battery_charge: "{{ weight_battery_charge }}"
weight_battery_discharge: "{{ weight_battery_discharge }}"
battery_minimum_state_of_charge: "{{ battery_minimum_percent / 100 }}"
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 load in ns.input %}
{% set load_start = load.start | as_datetime | as_local + timedelta(days=load_history_days_ago) %}
{% set load_value_watts = (load.mean | float(0) * 1000) | round(0) %}
{% set ns.output = ns.output | combine({ load_start.isoformat(): load_value_watts }) %}
{% 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
- trigger: time_pattern
minutes: "*"
hours: "*"
conditions: []
actions:
- action: script.generate_emhass_energy_plan_mpc
metadata: {}
data: {}
mode: single
This will run every time the prices change and every minute to account for live solar / usage power changes.
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.