Skip to content

Commit fdcb450

Browse files
committed
Fix cumulative EVCC energy export
1 parent 0cc90e5 commit fdcb450

6 files changed

Lines changed: 60 additions & 15 deletions

File tree

‎dbus_service.py‎

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,27 @@ def update(self, lp: Loadpoint) -> None:
116116
with self._svc as s:
117117
currents = lp.charge_currents
118118
voltages = lp.charge_voltages
119-
# Per-phase power = current * per-phase voltage. Real grids run
120-
# 225-237 V; EVCC reports actuals, so we use them instead of 230.
121-
s["/Ac/L1/Power"] = float(currents[0]) * float(voltages[0])
122-
s["/Ac/L2/Power"] = float(currents[1]) * float(voltages[1])
123-
s["/Ac/L3/Power"] = float(currents[2]) * float(voltages[2])
119+
charge_power = float(lp.charge_power)
120+
apparent = [
121+
float(currents[0]) * float(voltages[0]),
122+
float(currents[1]) * float(voltages[1]),
123+
float(currents[2]) * float(voltages[2]),
124+
]
125+
apparent_total = sum(apparent)
126+
if apparent_total > 0 and charge_power > 0:
127+
phase_powers = [
128+
charge_power * apparent_power / apparent_total
129+
for apparent_power in apparent
130+
]
131+
else:
132+
phase_powers = [0.0, 0.0, 0.0]
133+
s["/Ac/L1/Power"] = phase_powers[0]
134+
s["/Ac/L2/Power"] = phase_powers[1]
135+
s["/Ac/L3/Power"] = phase_powers[2]
124136
s["/Ac/Voltage"] = (
125137
float(voltages[0]) + float(voltages[1]) + float(voltages[2])
126138
) / 3.0
127-
s["/Ac/Power"] = float(lp.charge_power)
139+
s["/Ac/Power"] = charge_power
128140

129141
total_current = sum(float(c) for c in currents)
130142
s["/Current"] = total_current
@@ -150,11 +162,15 @@ def update(self, lp: Loadpoint) -> None:
150162
s["/Status"] = status
151163
s["/Connected"] = 1
152164

153-
# Cumulative counters only refreshed while connected. UNVERIFIED:
154-
# chargedEnergy is treated as Wh (-> /1000 = kWh). Must be confirmed
155-
# against a real EVCC /api/state sample before deploy.
156-
if status != STATUS_DISCONNECTED:
165+
# chargeTotalImport is EVCC's cumulative kWh meter value. It keeps
166+
# increasing for continuous heating devices where chargedEnergy is
167+
# a per-session counter and may stay at 0.
168+
total_import = float(lp.charge_total_import or 0.0)
169+
if total_import > 0:
170+
s["/Ac/Energy/Forward"] = total_import
171+
elif status != STATUS_DISCONNECTED:
157172
s["/Ac/Energy/Forward"] = float(lp.charged_energy) / 1000.0
173+
if status != STATUS_DISCONNECTED:
158174
s["/ChargingTime"] = int(lp.charge_duration_ns) // 1_000_000_000
159175

160176
idx = (int(s["/UpdateIndex"]) + 1) % 256

‎evcc_api.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Loadpoint:
2626
charge_voltages: List[float] = field(default_factory=lambda: [230.0, 230.0, 230.0])
2727
effective_max_current: int = 16
2828
charged_energy: float = 0.0
29+
charge_total_import: float = 0.0
2930
charge_duration_ns: int = 0
3031

3132

@@ -61,6 +62,7 @@ def _parse_loadpoints(state: dict) -> List[Loadpoint]:
6162
charge_voltages=_normalize_triple(raw.get("chargeVoltages"), 230.0),
6263
effective_max_current=int(raw.get("effectiveMaxCurrent") or 16),
6364
charged_energy=float(raw.get("chargedEnergy") or 0.0),
65+
charge_total_import=float(raw.get("chargeTotalImport") or 0.0),
6466
charge_duration_ns=int(raw.get("chargeDuration") or 0),
6567
))
6668
return out

‎tests/fixtures/evcc_state_1lp.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"chargeVoltages": [230, 230, 230],
1111
"effectiveMaxCurrent": 20,
1212
"chargedEnergy": 250.0,
13+
"chargeTotalImport": 12.345,
1314
"chargeDuration": 600000000000
1415
}
1516
]

‎tests/fixtures/evcc_state_3lp.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"chargeVoltages": [230, 230, 230],
1111
"effectiveMaxCurrent": 16,
1212
"chargedEnergy": 0.0,
13+
"chargeTotalImport": 1.234,
1314
"chargeDuration": 0
1415
},
1516
{
@@ -22,6 +23,7 @@
2223
"chargeVoltages": [230, 230, 230],
2324
"effectiveMaxCurrent": 20,
2425
"chargedEnergy": 1800.0,
26+
"chargeTotalImport": 98.765,
2527
"chargeDuration": 3600000000000
2628
},
2729
{
@@ -34,6 +36,7 @@
3436
"chargeVoltages": [230, 230, 230],
3537
"effectiveMaxCurrent": 16,
3638
"chargedEnergy": 0.0,
39+
"chargeTotalImport": 19341.908,
3740
"chargeDuration": 0
3841
}
3942
]

‎tests/test_dbus_service.py‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,41 @@ def test_update_charging_uses_per_phase_voltages(monkeypatch):
125125
sets = dict(c.args for c in vedbus.__setitem__.call_args_list)
126126
assert sets["/Status"] == STATUS_CHARGING
127127
assert sets["/Ac/Power"] == 4500.0
128-
assert sets["/Ac/L1/Power"] == 6.5 * 229.0
129-
assert sets["/Ac/L2/Power"] == 6.5 * 231.0
130-
assert sets["/Ac/L3/Power"] == 6.5 * 232.5
128+
assert abs(
129+
sets["/Ac/L1/Power"] + sets["/Ac/L2/Power"] + sets["/Ac/L3/Power"]
130+
- 4500.0
131+
) < 0.01
132+
assert sets["/Ac/L1/Power"] < sets["/Ac/L2/Power"] < sets["/Ac/L3/Power"]
131133
assert abs(sets["/Ac/Voltage"] - (229.0 + 231.0 + 232.5) / 3) < 0.01
132134
assert sets["/Current"] == 19.5
133135
assert sets["/MaxCurrent"] == 20
134136
assert sets["/Mode"] == MODE_AUTO
135-
# UNVERIFIED unit: chargedEnergy treated as Wh; must confirm against
136-
# a live EVCC /api/state sample before merging the deploy.
137+
# Fallback for EVCC payloads without chargeTotalImport.
137138
assert sets["/Ac/Energy/Forward"] == 1.8
138139
assert sets["/ChargingTime"] == 3600
139140
assert sets["/UpdateIndex"] == 6
140141

141142

143+
def test_update_prefers_charge_total_import_for_cumulative_energy(monkeypatch):
144+
svc, vedbus, _ = _make_svc(monkeypatch)
145+
vedbus.__getitem__.return_value = 0
146+
lp = Loadpoint(
147+
title="Heatpump", connected=True, charging=False, mode="off",
148+
charge_power=90.25,
149+
charge_currents=[0.93, 0.49, 0.5],
150+
charge_voltages=[237.5, 236.9, 236.9],
151+
charged_energy=0.0,
152+
charge_total_import=19341.908,
153+
)
154+
svc.update(lp)
155+
sets = dict(c.args for c in vedbus.__setitem__.call_args_list)
156+
assert sets["/Ac/Energy/Forward"] == 19341.908
157+
assert abs(
158+
sets["/Ac/L1/Power"] + sets["/Ac/L2/Power"] + sets["/Ac/L3/Power"]
159+
- sets["/Ac/Power"]
160+
) < 0.01
161+
162+
142163
def test_update_index_wraps_at_255(monkeypatch):
143164
svc, vedbus, _ = _make_svc(monkeypatch)
144165
vedbus.__getitem__.return_value = 255

‎tests/test_evcc_api.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def test_fetch_state_returns_loadpoints_1lp(requests_mock):
3030
assert lp.charge_currents == [6.5, 0, 0]
3131
assert lp.effective_max_current == 20
3232
assert lp.mode == "pv"
33+
assert lp.charge_total_import == 12.345
3334

3435

3536
def test_fetch_state_returns_loadpoints_3lp(requests_mock):
@@ -90,6 +91,7 @@ def test_missing_optional_fields_use_defaults():
9091
assert lps[0].charge_currents == [0, 0, 0]
9192
assert lps[0].connected is False
9293
assert lps[0].charge_power == 0.0
94+
assert lps[0].charge_total_import == 0.0
9395
assert lps[0].effective_max_current == 16
9496

9597

0 commit comments

Comments
 (0)