Skip to content

Commit a35c06e

Browse files
committed
feat: add human-readable state_label for inverter working mode (state register)
1 parent b4f62ae commit a35c06e

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

‎api/__init__.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,38 @@ def _row_to_dict(row: tuple, columns: list[str]) -> dict[str, Any]:
7676
return dict(zip(columns, row))
7777

7878

79+
# Working-mode status codes (Register 0) → human-readable label.
80+
# Source: EG4-18KPV-12LV Modbus protocol, Table 9 "Working modes definition".
81+
_STATE_LABELS = {
82+
0x00: "Standby",
83+
0x01: "Fault",
84+
0x02: "Programming",
85+
0x04: "PV on-grid",
86+
0x08: "PV charging battery",
87+
0x0C: "PV charging battery + on-grid",
88+
0x10: "Battery on-grid",
89+
0x14: "PV + battery on-grid",
90+
0x20: "AC charge (grid → battery)",
91+
0x28: "PV + AC charge",
92+
0x40: "Battery off-grid",
93+
0x80: "PV off-grid",
94+
0x88: "PV charge + off-grid",
95+
0xC0: "PV + battery off-grid",
96+
}
97+
98+
99+
def _state_label(value) -> str:
100+
"""Return a human-readable label for a raw working-mode state value."""
101+
try:
102+
code = int(value)
103+
except (TypeError, ValueError):
104+
return str(value)
105+
label = _STATE_LABELS.get(code)
106+
if label is None:
107+
return f"Unknown ({code})"
108+
return f"{label} ({code})"
109+
110+
79111
# ── endpoints ────────────────────────────────────────────────────────────────
80112

81113
@app.get("/api/status")
@@ -105,6 +137,13 @@ def api_status():
105137
for row in cur.fetchall()
106138
}
107139

140+
# Human-readable working-mode label alongside the raw state code.
141+
if "state" in registers:
142+
registers["state_label"] = {
143+
"value": _state_label(registers["state"]["value"]),
144+
"unit": "",
145+
}
146+
108147
return {
109148
"snapshot_id": snap_id,
110149
"timestamp": ts_aware.isoformat(),
@@ -285,6 +324,13 @@ def api_summary():
285324
"unit": "°F",
286325
}
287326

327+
# Human-readable working-mode label alongside the raw state code.
328+
if "state" in registers:
329+
registers["state_label"] = {
330+
"value": _state_label(registers["state"]["value"]),
331+
"unit": "",
332+
}
333+
288334
return {
289335
"snapshot_id": snap_id,
290336
"timestamp": ts_aware.isoformat(),

‎collector/outputs.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,38 @@
2222
logger = logging.getLogger("luxmon.outputs")
2323

2424

25+
# Working-mode status codes (Register 0) → human-readable label.
26+
# Source: EG4-18KPV-12LV Modbus protocol, Table 9 "Working modes definition".
27+
_STATE_LABELS = {
28+
0x00: "Standby",
29+
0x01: "Fault",
30+
0x02: "Programming",
31+
0x04: "PV on-grid",
32+
0x08: "PV charging battery",
33+
0x0C: "PV charging battery + on-grid",
34+
0x10: "Battery on-grid",
35+
0x14: "PV + battery on-grid",
36+
0x20: "AC charge (grid → battery)",
37+
0x28: "PV + AC charge",
38+
0x40: "Battery off-grid",
39+
0x80: "PV off-grid",
40+
0x88: "PV charge + off-grid",
41+
0xC0: "PV + battery off-grid",
42+
}
43+
44+
45+
def _state_label(value) -> str:
46+
"""Return a human-readable label for a raw working-mode state value."""
47+
try:
48+
code = int(value)
49+
except (TypeError, ValueError):
50+
return str(value)
51+
label = _STATE_LABELS.get(code)
52+
if label is None:
53+
return f"Unknown ({code})"
54+
return f"{label} ({code})"
55+
56+
2557
# Mapping from lux-mon decoded register names to SolarAssistant-style
2658
# InfluxDB measurement/field names.
2759
#
@@ -646,6 +678,10 @@ def _write_mqtt(self, decoded: dict) -> None:
646678
state_payload[key] = val
647679
state_payload.update(computed)
648680

681+
# Human-readable working-mode label alongside the raw state code.
682+
if "state" in state_payload:
683+
state_payload["state_label"] = _state_label(state_payload["state"])
684+
649685
state_topic = f"{base}/{device}/state"
650686
self._mqtt_client.publish(state_topic, json.dumps(state_payload), qos=0, retain=False)
651687

0 commit comments

Comments
 (0)