# HA Energy/Power Analysis Methodology How to diagnose unexpected power consumption (e.g. nighttime grid draw) using HA history data. Based on a session diagnosing ~200W nighttime grid import caused by a heat pump water heating cycle. ## Sensor Inventory (This Homelab) ### Grid / Net Power | Entity | Name | Notes | |--------|------|-------| | `sensor.evcc_grid_power` | Leistung Netz | EVCC aggregate grid power (W). Positive = import. | | `sensor.netzfluss_saldo` | Tibber Pulse Netzfluss Saldo | Tibber Pulse grid balance (W). Negative = export. Higher resolution than EVCC. | | `sensor.stromverbrauch_haus_netto` | Stromverbrauch Haus netto | Net house power consumption (W) | ### Home / Load | Entity | Name | Notes | |--------|------|-------| | `sensor.evcc_home_power` | Leistung Heim | Total home consumption (W) | | `sensor.stromverbrauch_rest` | Stromverbrauch "Rest" | Unknown/rest consumption | ### PV | Entity | Name | Notes | |--------|------|-------| | `sensor.evcc_pv_power` | Leistung PV | Aggregate PV power (W) | | `sensor.hm_1500_power` | HM-1500 Power | Hoymiles HM-1500 via OpenDTU/MQTT | | `sensor.schuppen_string_1_power` | Schuppen 1 Power | Hoymiles Schuppen 1 | | `sensor.schuppen_2_power` | Schuppen 2 Power | Hoymilles Schuppen 2 | ### Batteries | Entity | Name | Notes | |--------|------|-------| | `sensor.evcc_battery_power` | Leistung Batterie | EVCC aggregate battery (W). Positive = discharging. | | `sensor.evcc_battery_soc` | Batterie Ladezustand | EVCC aggregate SOC (%) | | `sensor.sn_3007105790_battery_power_discharge_total` | SMA Battery Discharge | SMA SunnyBoyStorage discharge (W) | | `sensor.sn_3007105790_battery_power_charge_total` | SMA Battery Charge | SMA SunnyBoyStorage charge (W) | | `sensor.sn_3007105790_battery_soc_total` | SMA Battery SOC | SMA SunnyBoyStorage SOC (%) | | `sensor.marstek_venus_modbus_batterieleistung` | Marstek Batterieleistung | Marstek Venus (W). Negative = discharging. | | `sensor.marstek_venus_modbus_ac_leistung` | Marstek AC-Leistung | Marstek AC output (W) | | `sensor.marstek_venus_modbus_batterie_ladezustand` | Marstek SoC | Marstek Venus SOC (%) | | `sensor.marstek_target_power` | Marstek Target Power | EVCC/setpoint target for Marstek (W) | | `sensor.sma_target_power` | SMA Target Power | EVCC/setpoint target for SMA (W) | ### Large Consumers (Individual) | Entity | Name | Notes | |--------|------|-------| | `sensor.shellypro3em_08f9e0e98b9c_total_active_power` | Wärmepumpe Total | Heat pump total active power (W). **Major consumer — up to 5.4kW.** | | `sensor.strommessung_sauna_total_active_power` | Sauna Total | Sauna total active power (W) | | `sensor.cu401b_s_verdichterleistung` | CU401B Verdichterleistung | Heat pump compressor power (kW) | | `sensor.cu401b_s_verdichtermodulation` | CU401B Verdichtermodulation | Heat pump compressor modulation (%) | | `sensor.cu401b_s_kompressorphase` | CU401B Kompressorphase | Compressor phase: off/preparing/heating/pause | | `sensor.cu401b_s_ww_speichertemperatur` | WW-Speichertemperatur | Hot water tank temp (°C). Target: 46°C, Hysteresis: 4K | | `sensor.ev_charger_schon_leistung_ladestation` | EV Charger Leistung | SMA EV Charger station power (W) | | `sensor.evcc_wallbox_charge_power` | Wallbox Ladeleistung | EVCC wallbox charge power (kW) | | `sensor.trockner_device_power` | Trockner Power | Dryer power (W) | | `sensor.samsung_qn90aa_65_tv_power` | Samsung TV Power | TV power (W) | ### EVCC Config / Tariff | Entity | Name | Notes | |--------|------|-------| | `number.evcc_residual_power` | Restleistung | EVCC residual power target (W). Default 100W. | | `sensor.evcc_tariff_grid` | Kosten Netz | Current grid price (€/kWh) | | `binary_sensor.evcc_battery_grid_charge_active` | Hausbatterie Netzladen | Whether battery is charging from grid | ### Heat Pump Config (ViCare / CU401B S) | Entity | Name | Notes | |--------|------|-------| | `climate.vicare_heating` | CU401B S Heizung | Climate entity. Presets: comfort/eco/home/sleep | | `number.cu401b_s_ww_temperatur` | WW-Temperatur | WW target temp (°C). Currently 46. | | `number.cu401b_s_ww_hystereseschalter_ein` | WW-Hysterese ein | WW hysteresis ON (K). Currently 4. | | `number.cu401b_s_ww_hystereseschalter_aus` | WW-Hysterese aus | WW hysteresis OFF (K). Currently 4. | | `automation.komfort_heizung` | Komfort Heizung | Automation controlling heating comfort mode | ## Analysis Workflow 1. **Confirm the anomaly**: Query `sensor.evcc_grid_power` history for the suspected time window. Summarize in 30-min buckets to see the pattern. **Important**: Ask the user for approximate time — they may point to a different window than the first obvious spike. In one session, the first analysis found a 02:47 heat pump spike, but the user clarified the issue was at 04:30+, revealing a second smaller spike that was initially overlooked. 2. **Decompose the power equation**: At any moment: `grid = home - pv - battery_discharge + battery_charge`. Query all four simultaneously to see which component is causing the imbalance. 3. **Check battery SOC trajectory**: If batteries drain to low SOC during the night, they can't cover the remaining demand → grid import kicks in. Plot SOC over time for both batteries. 4. **Identify the triggering consumer**: Query individual large-consumer sensors (heat pump, sauna, EV charger, dryer) for the same time window. Look for spikes or sustained elevated consumption. 5. **Correlate with heat pump phases**: The `cu401b_s_kompressorphase` and `cu401b_s_verdichtermodulation` entities show exactly when the compressor starts, what phase it's in, and how hard it's working. The `ww_speichertemperatur` shows whether it's a hot-water heating cycle (temp rising toward target). 6. **Check EVCC control targets**: `marstek_target_power` and `sma_target_power` show what EVCC is commanding the batteries to do. If these are 0, EVCC isn't actively discharging the batteries — they may be in passive mode. 7. **Summarize findings**: Present a timeline table showing the key events and a root-cause explanation, followed by prevention options. ### Multiple Spikes Per Night (2026-07-09 Session) In one session, two distinct grid import spikes were found in a single night: | Time | Grid Peak | Home Peak | Heat Pump | Cause | |------|-----------|-----------|-----------|-------| | 02:47 | 4,808 W | 6,151 W | 5,440 W | Heat pump WW heating (compressor 47-50% modulation, WW temp 38→47°C) | | 04:30 | 3,570 W | 4,920 W | 91 W (standby) | **Unknown** — heat pump was idle. Possibly a second WW cycle or another consumer not covered by monitored sensors. | **Lesson**: Don't assume the first spike is the only one. Query the full 02:00–07:00 window and look for ALL grid import periods >100W. The 04:30 spike showed home power at 4,920W but the heat pump was only drawing 91W — the 4,800W delta came from an unmonitored consumer. Future analysis should query ALL available power sensors (including `sensor.stromverbrauch_haus_netto` which aggregates differently from `evcc_home_power`) and consider unmonitored circuits. ## Prevention Strategies for Nighttime Grid Import ### Heat Pump WW Timing The most impactful fix: shift hot water preparation to daytime PV hours. - **ViCare schedule**: Set WW heating to only occur between 10:00–15:00 (main PV production window). - **Increase hysteresis**: Change `ww_hystereseschalter_ein` from 4K to 6K → starts later, fewer cycles. - **Lower WW target**: Reduce `ww_temperatur` from 46°C to 43°C → less energy per cycle. ### Battery SOC Management Ensure batteries start the night at 100%: - **EVCC priority SOC**: Ensure `evcc_priority_soc` is set appropriately so batteries reserve enough for overnight. - **Force charge before sunset**: If PV forecast predicts low production, trigger battery charging from PV surplus in late afternoon. - **Marstek discharge limit**: `number.marstek_venus_modbus_entladeleistung_einstellen` controls max discharge rate. Lowering it extends battery runtime but may not fully cover peaks. ### Combination Approach (Recommended) Both WW timing AND battery management together provide the most robust solution. ## EVCC-Side Battery Discharge Diagnosis When the question shifts from "what consumed the power?" to "why didn't the battery cover the gap?", the EVCC `api/state` endpoint provides the answer. This is a different diagnostic axis from the consumption-side analysis above. ### Key Fields in `curl -s http://10.0.30.10:7070/api/state` | Field | Path | Meaning | |-------|------|---------| | Controllable | `battery.devices[].controllable` | `false` = EVCC reads SoC/power but cannot command discharge | | Discharge control | `batteryDischargeControl` | `false` = passive monitoring, no active discharge management | | Residual power | `residualPower` | Watts EVCC tolerates from grid before reacting (default 100) | | Battery mode | `batteryMode` | `unknown` = no active mode set | | Per-battery power | `battery.devices[].power` | Current output (W) — compare to home power | | Per-battery SOC | `battery.devices[].soc` | High SOC + low output = battery is limited, not empty | ### Diagnosis Flow 1. **Check `controllable`** — if `false`, EVCC cannot increase discharge. The battery's own internal limit is the bottleneck. 2. **Check `residualPower`** — if >0, EVCC deliberately allows that much grid import as baseline. 3. **Compare battery output to home power** — if battery output << home power and SOC is high, the battery's internal discharge limit (not EVCC) is the constraint. 4. **Verify by checking spike behavior** — if the battery briefly delivered much more during a spike (e.g. Marstek hit 1,437W during heat pump cycle but settled to ~650W steady-state), that confirms an internal limiter, not a capacity issue. ### Case Study: Marstek Venus Not Covering 200W Gap (2026-07-10) **Symptom**: ~200W continuous grid import 03:00–04:00 despite Marstek SoC at 73%. **Initial hypothesis (INCOMPLETE)**: EVCC `controllable: false` + `residualPower: 100` + Marstek internal discharge limit ≈ 650W. **Actual root cause**: The HA automation "Marstek Kaskaden-Steuerung (Modbus)" (`automation.marstek_modbus_steuerung`) controls the Marstek, NOT EVCC. EVCC sees `controllable: false` because it's only a passive observer. The automation has a **feedback-loop bug** in its `echter_bedarf` formula: ```yaml # BUG: uses setpoint (number.*_einstellen) instead of measured (sensor.*) echter_bedarf: "{{ power_home - production + marstek_current_discharge - marstek_current_power }}" # marstek_current_discharge = states('number.marstek_venus_modbus_entladeleistung_einstellen') ← SETPOINT ``` This causes the discharge setpoint to spiral: 0 → 870 → 1740 → 2500 (capped) within 3 cycles (6 min). The Marstek's `anti_feed` mode then throttles actual output to ~650W (preventing grid export from the oversized command). The ~200W gap is the difference between what the automation commands (2500W) and what the Marstek actually delivers after anti_feed throttling (~650W), versus what the house needs (~870W). **Evidence**: During the 02:43–02:57 heat pump spike, Marstek delivered up to 1,437W (proving capacity exists), but settled back to ~650W afterward — consistent with the automation ramping the setpoint up and anti_feed throttling back. **Fix**: Replace setpoint variables with measured AC power in the automation: ```yaml # FIXED: marstek_actual_ac: "{{ states('sensor.marstek_venus_modbus_ac_leistung') | float(0) }}" echter_bedarf: "{{ power_home - production - marstek_actual_ac }}" ``` Result: `echter_bedarf = 870 - 0 - 650 = 220W` → new setpoint = 650 + 220 = 870W. Stable convergence. See `references/marstek-cascade-automation.md` for the full automation YAML and analysis. ## Query Templates See `references/ha-browser-console-access.md` for the browser-console technique used to execute these queries when no local API token is available. ### Summary query (all sensors, 30-min buckets) ```javascript // Adapt entity list and time range as needed const entities = 'sensor.evcc_grid_power,sensor.evcc_home_power,sensor.evcc_battery_power,sensor.evcc_pv_power'; const url = '/api/history/period/2026-07-09T03:00:00?filter_entity_id=' + entities + '&end_time=2026-07-09T06:00:00&minimal_response'; ``` ### Individual consumer spike detection ```javascript // Filter history records where power > threshold const spikes = hist.filter(h => parseFloat(h.state) > 100).map(h => ({ t: h.last_changed.substring(11, 16), v: parseFloat(h.state) })); ```