- EMHASS config for Deye SUN-12K + 32 kWh battery, NordPool LV - Day-ahead optimization automation (14:30 daily) with real battery SOC init - Forecast vs reality template sensors and dashboard card - Migration log from Domoticz Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EMHASS Energy Management — Deye + NordPool Setup
Energy Management for Home Assistant (EMHASS) configured for:
- Deye SUN-12K-SG04LP3-EU hybrid inverter
- 32 kWh LiFePO4 battery
- NordPool LV spot prices
- Self-consumption optimization with price arbitrage
System Overview
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Deye Inverter │ │ Home Assistant │ │ NordPool │
│ (MQTT → HA) │─────▶│ Sensors │◀─────│ Spot Prices │
│ │ │ │ │ (LV zone) │
└─────────────────┘ └────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ EMHASS │
│ (add-on) │
│ │
│ • Day-ahead │
│ optimization │
│ • Battery │
│ scheduling │
│ • Price │
│ arbitrage │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Forecast Sensors │
│ • p_pv_forecast │
│ • p_load_forecast │
│ • p_grid_forecast│
│ • soc_batt_forecast│
└─────────────────┘
Daily Workflow
1. NordPool Price Publication (~14:30 EET/EEST)
NordPool publishes next-day spot prices for the LV zone. The sensor.nordpool_kwh_lv_eur_3_10_021 sensor receives:
today— 96 values (15-min resolution) for current daytomorrow— 96 values for next day (once published, typically by 14:30)tomorrow_valid— becomestruewhen prices are available
2. EMHASS Automation (14:30 daily)
trigger: 14:30
↓
wait: tomorrow_valid == true (timeout 60min)
↓
build: 48-value price list (30-min resolution)
= NordPool 15-min prices resampled to 30-min steps
(prices already include distribution + VAT — no offset added)
read: battery_soc_init = sensor.deye_inverter_mqtt_battery_soc / 100
↓
POST: /action/dayahead-optim with price list + battery_soc_init
↓
wait: 15 seconds
↓
POST: /action/publish-data
↓
result: Forecast sensors updated with tomorrow's plan
3. What EMHASS Optimizes
With costfun: profit and set_nodischarge_to_grid: true:
- Discharge battery to cover load during expensive hours (sell to yourself)
- Charge battery from grid during cheap hours (price arbitrage)
- Never export to grid (
maximum_power_to_grid: 0) - Maintain SOC between 10%–100%, target 60% end-of-day
Configuration Files
EMHASS Config (/addon_configs/5b918bf2_emhass/config.json)
{
"hass_url": "http://192.168.66.113:8123",
"long_lived_token": "<HA long-lived access token>",
"costfun": "profit",
"optimization_time_step": 30,
"historic_days_to_retrieve": 10,
"sensor_power_photovoltaics": "sensor.deye_inverter_mqtt_dc_total_power",
"sensor_power_load_no_var_loads": "sensor.deye_inverter_mqtt_total_load_power",
"maximum_power_from_grid": 16090,
"maximum_power_to_grid": 0,
"load_cost_forecast_method": "list",
"set_use_battery": true,
"battery_nominal_energy_capacity": 32000,
"battery_charge_power_max": 11000,
"battery_discharge_power_max": 11000,
"battery_charge_efficiency": 0.96,
"battery_discharge_efficiency": 0.96,
"battery_minimum_state_of_charge": 0.1,
"battery_maximum_state_of_charge": 1.0,
"battery_target_state_of_charge": 0.6,
"number_of_deferrable_loads": 0
}
HA Configuration (/config/configuration.yaml)
rest_command:
emhass_dayahead_optim:
url: http://192.168.66.113:5000/action/dayahead-optim
method: POST
content_type: application/json
body: "{{ body }}"
emhass_publish_data:
url: http://192.168.66.113:5000/action/publish-data
method: POST
content_type: application/json
Automation (/config/automations.yaml)
Located at the end of automations.yaml — see the file for the full Jinja2 template that:
- Reads
sensor.nordpool_kwh_lv_eur_3_10_021tomorrowattribute - Resamples from 96 values (15-min) to 48 values (30-min)
- Reads real battery SOC and converts to fraction (
battery_soc_init) - Passes both to EMHASS via
load_cost_forecast+battery_soc_initparameters
Note: NordPool prices already include distribution + VAT — no fixed offset is added.
Key Sensors
| Role | Entity ID | Notes |
|---|---|---|
| PV Power | sensor.deye_inverter_mqtt_dc_total_power |
DC total from strings |
| Load Power | sensor.deye_inverter_mqtt_total_load_power |
House consumption |
| Grid Power | sensor.deye_inverter_mqtt_total_grid_power |
+import / -export |
| Battery SOC | sensor.deye_inverter_mqtt_battery_soc |
% |
| Battery Power | sensor.deye_inverter_mqtt_battery_power |
+discharge / -charge |
| NordPool | sensor.nordpool_kwh_lv_eur_3_10_021 |
Spot price EUR/kWh |
| PV Forecast | sensor.p_pv_forecast |
EMHASS output |
| Load Forecast | sensor.p_load_forecast |
EMHASS output |
| Grid Forecast | sensor.p_grid_forecast |
EMHASS output |
| SOC Forecast | sensor.soc_batt_forecast |
EMHASS output — planned end-of-day SOC (always = target 60%) |
Sign Conventions
| Sensor | Positive | Negative |
|---|---|---|
battery_power |
Discharging to load | Charging from PV/grid |
total_grid_power |
Importing from grid | Exporting to grid |
inverter_l*_power |
Export to grid | Import from grid |
Comparison: Forecast vs Reality
Template Sensors Created
| Sensor | Description |
|---|---|
sensor.battery_soc_forecast_error |
Actual SOC - Forecast SOC (%) |
sensor.current_hour_price |
Current hour's electricity price (€/kWh) |
Monitoring Dashboard
Add the dashboard card from dashboard_card.yaml:
- Go to Dashboard → Edit → Add Card → Manual
- Paste the YAML content
Checking Performance
Daily comparison questions:
- Did battery charge during cheap hours (00:00–05:00, 12:00–14:00)?
- Did battery discharge during peak hours (19:00–22:00)?
- Is
sensor.battery_soc_forecast_errorwithin ±5%?
If error > 10%:
- Check if Deye inverter is overriding EMHASS decisions
- Consider adding enforcement automation (see below)
History Graph
Compare actual vs forecast over time:
type: history-graph
entities:
- entity: sensor.deye_inverter_mqtt_battery_soc
name: Actual SOC
- entity: sensor.soc_batt_forecast
name: Forecast SOC
hours_to_show: 48
Adding EV Charging (OpenEVSE)
When the EV charger is integrated:
-
Create a sensor for EV power (or use OpenEVSE integration):
# In configuration.yaml sensor: - platform: template sensors: ev_charger_power: friendly_name: "EV Charger Power" value_template: "{{ states('sensor.openevse_current_power') | float(0) * 230 }}" unit_of_measurement: "W" -
Create a template sensor that subtracts EV from total load:
- platform: template sensors: load_power_no_var_loads: friendly_name: "Load Power (no EV)" value_template: "{{ states('sensor.deye_inverter_mqtt_total_load_power') | float - states('sensor.ev_charger_power') | float(0) }}" unit_of_measurement: "W" -
Update EMHASS config:
"sensor_power_load_no_var_loads": "sensor.load_power_no_var_loads", "number_of_deferrable_loads": 1, "nominal_power_of_deferrable_loads": [7400], "operating_hours_of_each_deferrable_load": [6] -
Restart EMHASS
Troubleshooting
EMHASS logs show "config.json does not exist"
The config file must be at:
/addon_configs/5b918bf2_emhass/config.json
Not at /homeassistant/config.json or /config/config.json.
"Shape of passed values is (X, 1), indices imply (48, 1)"
Load sensor has insufficient history. EMHASS needs ≥48 data points (24h at 30-min resolution). Either:
- Wait for more history to accumulate
- Switch to
sensor.deye_inverter_mqtt_total_load_power(has months of history)
Check EMHASS status via SSH
ssh root@192.168.66.113
ha addons logs 5b918bf2_emhass | tail -30
ha addons restart 5b918bf2_emhass
Verify NordPool prices are being passed
curl -s -X POST http://192.168.66.113:5000/action/dayahead-optim \
-H "Content-Type: application/json" \
-d '{"load_cost_forecast": [0.20, 0.19, ...]}'
Check logs for: Passed runtime parameters: {'load_cost_forecast': [...]}
Files in This Repository
| File | Purpose |
|---|---|
config.json |
EMHASS configuration (deploy to HA) |
config_emhass.yaml |
Human-readable reference (same config) |
migration_log.md |
Migration notes from Domoticz |
README.md |
This file |