Skip to content

Commit 05746f9

Browse files
committed
chore: add function-enable bitfields + optimal schedule + generator/meter registers
Add the remaining writable holding registers below 500 that were missing from the map: - Function-enable bitfields (21, 110, 120, 179) — packed on/off toggles - Optimal charge/discharge time marks (126-131) — 2-bit half-hour marks - Grid regulation (203), LCD password (225) - Meter config (230) + WattNode CT directions (251) — WattNode capability - Generator start/end times (256-259) — generator capability Document the registers intentionally NOT exposed as writable: firmware/version codes (7-10), reset (11), clock (12-14), device type (16, 19), write-only phase/alarm clears (113, 114), LCD config (224), G100 reset (231), service mode (241), bootloader/flash (244-245). The 7-day scheduling block (500-723) is deferred: it requires WriteMultipleRegisters (0x10), which lux-mon does not implement yet. Adding it as writable would create a broken UI where every write silently fails. HOLDING_REGISTERS: 200 -> 218. 63/63 tests pass.
1 parent a753d19 commit 05746f9

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

‎collector/capabilities.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
VOLT_WATT,
8181
QV_CURVE,
8282
QP_CURVE,
83-
SEVEN_DAY_SCHEDULE,
8483
BATTERY_BMS,
8584
})
8685

@@ -95,7 +94,6 @@
9594
VOLT_WATT,
9695
QV_CURVE,
9796
QP_CURVE,
98-
SEVEN_DAY_SCHEDULE,
9997
BATTERY_BMS,
10098
})
10199

‎collector/protocol.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,51 @@
312312
254: {"name": "voltage_delta_hysteresis", "unit": "V", "scale": 0.1, "desc": "Voltage delta/hysteresis", "min": 0, "max": 100},
313313
260: {"name": "bus_voltage_high_limit", "unit": "V", "scale": 0.1, "desc": "Bus voltage high limit", "min": 0, "max": 800},
314314
261: {"name": "discharge_recovery", "unit": "%", "scale": 1.0, "desc": "Discharge recovery", "min": 0, "max": 100},
315+
316+
# ── Function-enable bitfields (21, 110, 120, 179) ──
317+
# These pack many on/off toggles into a single 16-bit register. They are
318+
# writable, but the UI should present them as bit toggles, not raw numbers.
319+
21: {"name": "function_enable_1", "unit": "", "scale": 1.0, "desc": "Function enable 1 (bitfield: EPS, derate, DRMS, LVRT, anti-island, neutral detect, soft start, AC charge, seamless switch, standby, forced dischg/chg, ISO, GFCI, DCI, grid export)", "min": 0, "max": 65535},
320+
110: {"name": "function_enable_3", "unit": "", "scale": 1.0, "desc": "Function enable 3 (bitfield: PV grid-off, fast zero-export, micro-grid, battery shared, charge-last, CT sample ratio, buzzer, PV CT type, take-load-together, on-grid mode, green/eco mode)", "min": 0, "max": 65535},
321+
120: {"name": "system_enable_2", "unit": "", "scale": 1.0, "desc": "System enable 2 (bitfield of system-level toggles)", "min": 0, "max": 65535},
322+
179: {"name": "function_enable_4", "unit": "", "scale": 1.0, "desc": "Function enable 4 (bitfield of function toggles)", "min": 0, "max": 65535},
323+
324+
# ── Optimal charge/discharge time marks (126-131) ──
325+
# Each register packs 8 half-hour marks (00:00-03:30, etc.). Each mark is
326+
# 2 bits: 0=no op, 1=AC charge, 2=PV charge, 3=discharge.
327+
126: {"name": "optimal_chg_dischg_0_3", "unit": "", "scale": 1.0, "desc": "Optimal charge/discharge marks 00:00-03:30 (2 bits per half-hour: 0=off, 1=AC chg, 2=PV chg, 3=dischg)", "min": 0, "max": 65535},
328+
127: {"name": "optimal_chg_dischg_4_7", "unit": "", "scale": 1.0, "desc": "Optimal charge/discharge marks 04:00-07:30", "min": 0, "max": 65535},
329+
128: {"name": "optimal_chg_dischg_8_11", "unit": "", "scale": 1.0, "desc": "Optimal charge/discharge marks 08:00-11:30", "min": 0, "max": 65535},
330+
129: {"name": "optimal_chg_dischg_12_15", "unit": "", "scale": 1.0, "desc": "Optimal charge/discharge marks 12:00-15:30", "min": 0, "max": 65535},
331+
130: {"name": "optimal_chg_dischg_16_19", "unit": "", "scale": 1.0, "desc": "Optimal charge/discharge marks 16:00-19:30", "min": 0, "max": 65535},
332+
131: {"name": "optimal_chg_dischg_20_23", "unit": "", "scale": 1.0, "desc": "Optimal charge/discharge marks 20:00-23:30", "min": 0, "max": 65535},
333+
334+
# ── Grid regulation / meter / LCD (203, 225, 230, 251) ──
335+
203: {"name": "grid_regulation", "unit": "", "scale": 1.0, "desc": "Grid regulation settings (bitfield)", "min": 0, "max": 65535},
336+
225: {"name": "lcd_password", "unit": "", "scale": 1.0, "desc": "LCD advanced-page password", "min": 0, "max": 65535},
337+
230: {"name": "meter_config", "unit": "", "scale": 1.0, "desc": "Meter config (meter count, measure type, install phase)", "min": 0, "max": 65535, "capabilities": {"wattnode"}},
338+
251: {"name": "wattnode_ct_directions", "unit": "", "scale": 1.0, "desc": "WattNode CT direction and frequency settings (bitfield)", "min": 0, "max": 65535, "capabilities": {"wattnode"}},
339+
340+
# ── Generator start/end times (256-259) — GENERATOR capability ──
341+
256: {"name": "generator_start_time", "unit": "time", "scale": 1.0, "desc": "Generator start time (hour+minute)", "min": 0, "max": 2359, "capabilities": {"generator"}},
342+
257: {"name": "generator_end_time", "unit": "time", "scale": 1.0, "desc": "Generator end time (hour+minute)", "min": 0, "max": 2359, "capabilities": {"generator"}},
343+
258: {"name": "generator_start_time_1", "unit": "time", "scale": 1.0, "desc": "Generator period 1 start time (hour+minute)", "min": 0, "max": 2359, "capabilities": {"generator"}},
344+
259: {"name": "generator_end_time_1", "unit": "time", "scale": 1.0, "desc": "Generator period 1 end time (hour+minute)", "min": 0, "max": 2359, "capabilities": {"generator"}},
345+
346+
# ── Intentionally NOT exposed as writable (read-only or dangerous) ──
347+
# 7-10 firmware/version codes (read-only)
348+
# 11 reset (dangerous — restarts inverter)
349+
# 12-14 inverter clock (dangerous — changing time disrupts operation)
350+
# 16 language + device type (read-only, used for model detection)
351+
# 19 device type high-speed (read-only)
352+
# 113 set composed phase (write-only)
353+
# 114 clear parallel alarm (write-only)
354+
# 224 LCD config (read-only)
355+
# 231 reset G100 lockout (dangerous)
356+
# 241 permit service (dangerous)
357+
# 244-245 bootloader/flash (read-only)
358+
# 500-723 7-day scheduling — requires WriteMultipleRegisters (0x10),
359+
# which lux-mon does not implement yet. Deferred.
315360
}
316361

317362
# Friendly display labels matching the reference portal's terminology.

‎tests/test_capabilities.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,35 @@ def test_new_registers_present():
7777
assert 261 in HOLDING_REGISTERS # discharge_recovery
7878

7979

80+
def test_function_enable_and_schedule_registers_present():
81+
# Function-enable bitfields and optimal charge/discharge marks
82+
assert 21 in HOLDING_REGISTERS # function_enable_1
83+
assert 110 in HOLDING_REGISTERS # function_enable_3
84+
assert 120 in HOLDING_REGISTERS # system_enable_2
85+
assert 179 in HOLDING_REGISTERS # function_enable_4
86+
assert 126 in HOLDING_REGISTERS # optimal_chg_dischg_0_3
87+
assert 131 in HOLDING_REGISTERS # optimal_chg_dischg_20_23
88+
assert 203 in HOLDING_REGISTERS # grid_regulation
89+
assert 225 in HOLDING_REGISTERS # lcd_password
90+
assert 230 in HOLDING_REGISTERS # meter_config
91+
assert 251 in HOLDING_REGISTERS # wattnode_ct_directions
92+
assert 256 in HOLDING_REGISTERS # generator_start_time
93+
assert 259 in HOLDING_REGISTERS # generator_end_time_1
94+
95+
96+
def test_dangerous_registers_not_exposed():
97+
# Reset, clock, and other dangerous/read-only registers must NOT be writable.
98+
for reg in (7, 8, 9, 10, 11, 12, 13, 14, 16, 19, 113, 114, 224, 231, 241, 244, 245):
99+
assert reg not in HOLDING_REGISTERS, f"Register {reg} should not be writable"
100+
101+
102+
def test_seven_day_schedule_not_exposed():
103+
# 7-day scheduling (500-723) requires WriteMultipleRegisters, not yet
104+
# implemented. It must not appear as writable.
105+
for reg in range(500, 724):
106+
assert reg not in HOLDING_REGISTERS, f"Register {reg} should not be writable yet"
107+
108+
80109
def test_no_duplicate_register_numbers():
81110
# Every register number must appear exactly once.
82111
from collections import Counter

0 commit comments

Comments
 (0)