Skip to content

Commit 0cd3dc5

Browse files
committed
#676 duplicates
1 parent 94d61bd commit 0cd3dc5

8 files changed

Lines changed: 152 additions & 133 deletions

File tree

‎scripts/gen_sensors.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Sensors."""
22

33
import asyncio
4+
import sys
45
from collections import defaultdict
56
from pathlib import Path
67

@@ -15,12 +16,29 @@ async def main() -> None:
1516
"""Generate docs."""
1617
all_defs = import_all_defs()
1718

19+
check_register_bit_conflicts(all_defs)
20+
1821
all_defs = {simple_def_name(k): v for k, v in all_defs.items()}
1922

2023
sen_groups = generate_group_sensors(all_defs)
2124
generate_all_sensors(all_defs, sen_groups)
2225

2326

27+
def check_register_bit_conflicts(all_defs: dict[str, SensorDefinitions]) -> None:
28+
"""Fail if any definition has two sensors claiming the same register bits."""
29+
lines: list[str] = []
30+
for name, defs in all_defs.items():
31+
for addr, a, b, bits in defs.register_bit_conflicts():
32+
lines.append(
33+
f" {name}: reg {addr} bit overlap 0x{bits:X} — "
34+
f"{a.id} (0x{a.bitmask:X}) vs {b.id} (0x{b.bitmask:X})"
35+
)
36+
if lines:
37+
print("Register bit conflicts in sensor definitions:", file=sys.stderr)
38+
print("\n".join(lines), file=sys.stderr)
39+
raise SystemExit(1)
40+
41+
2442
TAB_ATTR = {"style": "overflow-x: unset;", "class": "bigt"}
2543

2644

‎src/sunsynk/definitions/single_phase.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@
8080
alias="Generator input",
8181
),
8282
Sensor(166, "Gen power", WATT, -1, alias="AUX power"),
83-
Sensor(181, "Gen L1 voltage", VOLT, 0.1, alias="AUX L1 voltage"),
84-
Sensor(182, "Gen L2 voltage", VOLT, 0.1, alias="AUX L2 voltage"),
85-
Sensor(183, "Gen L3 voltage", VOLT, 0.1, alias="AUX L3 voltage"),
86-
Sensor(184, "Gen L1 current", AMPS, -0.01, alias="AUX L1 current"),
87-
Sensor(185, "Gen L2 current", AMPS, -0.01, alias="AUX L2 current"),
88-
Sensor(186, "Gen L3 current", AMPS, -0.01, alias="AUX L3 current"),
83+
# Sensor(181, "Gen L1 voltage", VOLT, 0.1, alias="AUX L1 voltage"),
84+
# Sensor(182, "Gen L2 voltage", VOLT, 0.1, alias="AUX L2 voltage"),
85+
# Sensor(183, "Gen L3 voltage", VOLT, 0.1, alias="AUX L3 voltage"),
86+
# Sensor(184, "Gen L1 current", AMPS, -0.01, alias="AUX L1 current"),
87+
# Sensor(185, "Gen L2 current", AMPS, -0.01, alias="AUX L2 current"),
88+
# Sensor(186, "Gen L3 current", AMPS, -0.01, alias="AUX L3 current"),
8989
)
9090

9191
#####################

‎src/sunsynk/definitions/three_phase_common.py‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@
104104
Sensor(661, "Gen L1 voltage", VOLT, 0.1, alias="AUX L1 voltage"),
105105
Sensor(662, "Gen L2 voltage", VOLT, 0.1, alias="AUX L2 voltage"),
106106
Sensor(663, "Gen L3 voltage", VOLT, 0.1, alias="AUX L3 voltage"),
107-
Sensor(671, "Gen L1 current", AMPS, -0.01, alias="AUX L1 current"),
108-
Sensor(672, "Gen L2 current", AMPS, -0.01, alias="AUX L2 current"),
109-
Sensor(673, "Gen L3 current", AMPS, -0.01, alias="AUX L3 current"),
107+
# Sensor(671, "Gen L1 current", AMPS, -0.01, alias="AUX L1 current"),
108+
# Sensor(672, "Gen L2 current", AMPS, -0.01, alias="AUX L2 current"),
109+
# Sensor(673, "Gen L3 current", AMPS, -0.01, alias="AUX L3 current"),
110110
)
111111

112112
####################
@@ -188,7 +188,6 @@
188188
# SDStatusSensor(0, "SD Status", ""), # 3 Phase does not have SD Card but crashes when removed
189189
TempSensor(540, "DC transformer temperature", CELSIUS, 0.1),
190190
TempSensor(541, "Radiator temperature", CELSIUS, 0.1),
191-
BinarySensor(552, "Grid Connected", bitmask=1 << 2),
192191
SystemTimeRWSensor((62, 63, 64), "Date Time", unit=""),
193192
)
194193

@@ -198,7 +197,7 @@
198197
SENSORS += (
199198
BinarySensor(552, "INV Relay Status", bitmask=1 << 0),
200199
BinarySensor(552, "Undefined Load Relay Status", bitmask=1 << 1),
201-
BinarySensor(552, "Grid Relay Status", bitmask=1 << 2),
200+
BinarySensor(552, "Grid Connected", bitmask=1 << 2, alias="Grid Relay Status"),
202201
BinarySensor(552, "Generator Relay Status", bitmask=1 << 3),
203202
BinarySensor(552, "Grid Give Power to Relay Status", bitmask=1 << 4),
204203
BinarySensor(552, "Dry Contact1 Status", bitmask=1 << 5),
@@ -215,7 +214,6 @@
215214
NumberRWSensor(126, "Grid Charge Start Battery Voltage", VOLT, 0.01, max=6300),
216215
NumberRWSensor(127, "Grid Charge Start Battery SOC", "%"),
217216
SwitchRWSensor(130, "Grid Charge enabled", on=1),
218-
SwitchRWSensor(146, "Use Timer"),
219217
SwitchRWSensor(145, "Solar Export", on=1),
220218
NumberRWSensor(143, "Export Limit power", WATT, max=RATED_POWER),
221219
NumberRWSensor(108, "Battery Max Charge current", AMPS, max=240),
@@ -357,6 +355,7 @@
357355
"Prog Time Of Use Enabled",
358356
on=1 << 0,
359357
bitmask=1 << 0,
358+
alias="Use Timer",
360359
),
361360
SwitchRWSensor(146, "Prog Monday Enabled", on=1 << 1, bitmask=1 << 1),
362361
SwitchRWSensor(146, "Prog Tuesday Enabled", on=1 << 2, bitmask=1 << 2),

‎src/sunsynk/definitions/three_phase_hv.py‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
SwitchRWSensor,
99
)
1010
from sunsynk.sensors import (
11-
BinarySensor,
1211
EnumSensor,
1312
HVFaultSensor,
1413
Sensor,
@@ -41,7 +40,6 @@
4140
Sensor(594, "Battery 2 current", AMPS, -0.01),
4241
Sensor(595, "Battery 2 power", WATT, -10),
4342
TempSensor(596, "Battery 2 temperature", CELSIUS, 0.1),
44-
SwitchRWSensor(110, "Parallel Bat1&Bat2", on=1),
4543
)
4644

4745
################
@@ -86,7 +84,6 @@
8684
bitmask=1 << 15,
8785
),
8886
# SDStatusSensor(0, "SD Status", ""),
89-
BinarySensor(552, "Grid Connected", bitmask=1 << 2),
9087
)
9188

9289
###########
@@ -101,12 +98,13 @@
10198
# Additional optional sensors
10299
SENSORS += (
103100
NumberRWSensor(104, "System Zero Export power", WATT, -10, min=-500, max=500),
104-
SwitchRWSensor(110, "Parallel Battery 1 and 2"),
101+
SwitchRWSensor(110, "Parallel Battery 1 and 2", alias="Parallel Bat1&Bat2"),
105102
SelectRWSensor(
106103
112,
107-
"Battery 1 Wake Up",
104+
"Battery Wake Up",
108105
options={0: "Enabled", 1 << 0: "Disabled"},
109106
bitmask=1 << 0,
107+
alias="Battery 1 Wake Up",
110108
), # according to docs, 0 is enabled for this one
111109
SelectRWSensor(
112110
112,
@@ -178,6 +176,7 @@
178176
9: "Tianbanda 485 Protocol",
179177
10: "Shenggao Electrical CAN Protocol",
180178
},
179+
alias="Battery 1 BMS type",
181180
),
182181
EnumSensor(229, "Battery 1 Manufacturer", options=hv_battery_manufacturers),
183182
# Sensor(210, "Battery 1 BMS charging voltage", VOLT, 0.1),
@@ -210,7 +209,6 @@
210209
on=1 << 3,
211210
bitmask=1 << 3,
212211
),
213-
Sensor(223, "Battery 1 BMS type", ""),
214212
Sensor(224, "Battery 1 BMS SOH", "%"),
215213
Sensor(225, "Battery 1 BMS software version", ""),
216214
Sensor(226, "Battery 1 BMS rated AH", "Ah"),

‎src/sunsynk/sensors.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,34 @@ def copy(self) -> "SensorDefinitions":
198198
"""Copy the sensor definitions."""
199199
return SensorDefinitions(all=self.all.copy(), deprecated=self.deprecated.copy())
200200

201+
def register_bit_conflicts(self) -> list[tuple[int, Sensor, Sensor, int]]:
202+
"""Find sensors that claim overlapping bits on the same register.
203+
204+
Returns ``(register, sensor_a, sensor_b, overlapping_bits)``.
205+
A zero bitmask means the full 16-bit register (``0xFFFF``).
206+
``MathSensor`` / ``PVDynamicTotalSensor`` intentionally reuse other
207+
sensors' registers and are skipped.
208+
"""
209+
by_reg: dict[int, list[Sensor]] = {}
210+
for key, sen in self.all.items():
211+
if key != sen.id or not sen.address:
212+
continue
213+
# Forward refs: MathSensor / PVDynamicTotalSensor defined below.
214+
if isinstance(sen, (MathSensor, PVDynamicTotalSensor)):
215+
continue
216+
for addr in sen.address:
217+
by_reg.setdefault(addr, []).append(sen)
218+
219+
conflicts: list[tuple[int, Sensor, Sensor, int]] = []
220+
for addr, sens in by_reg.items():
221+
for i, a in enumerate(sens):
222+
ma = a.bitmask or 0xFFFF
223+
for b in sens[i + 1 :]:
224+
overlap = ma & (b.bitmask or 0xFFFF)
225+
if overlap:
226+
conflicts.append((addr, a, b, overlap))
227+
return conflicts
228+
201229
def override(self, values: dict[str, int | float]) -> None:
202230
"""Override existing sensors with new definitions."""
203231
new_sensors = dict[str, Sensor]()

‎src/tests/sunsynk/test_sensors.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,38 @@ def test_source() -> None:
312312
assert Sensor((1, 2), "test_sensor", "V", bitmask=0xEF1A).source == "[1,2] & 0xEF1A"
313313

314314

315+
def test_register_bit_conflicts() -> None:
316+
"""Overlapping bits on the same register are reported; disjoint masks are not."""
317+
defs = SensorDefinitions()
318+
a = Sensor(10, "full")
319+
b = Sensor(10, "also full")
320+
defs += (a, b)
321+
conflicts = defs.register_bit_conflicts()
322+
assert len(conflicts) == 1
323+
assert conflicts[0] == (10, a, b, 0xFFFF)
324+
325+
defs = SensorDefinitions()
326+
low = BinarySensor(10, "low", bitmask=0x0F)
327+
high = BinarySensor(10, "high", bitmask=0xF0)
328+
defs += (low, high)
329+
assert defs.register_bit_conflicts() == []
330+
331+
defs += BinarySensor(10, "overlap", bitmask=0x03)
332+
conflicts = defs.register_bit_conflicts()
333+
assert len(conflicts) == 1
334+
assert conflicts[0][0] == 10
335+
assert conflicts[0][3] == 0x03
336+
337+
# MathSensor reuses component registers on purpose.
338+
defs = SensorDefinitions()
339+
defs += (
340+
Sensor(1, "l1"),
341+
Sensor(2, "l2"),
342+
MathSensor((1, 2), "sum", factors=(1, 1)),
343+
)
344+
assert defs.register_bit_conflicts() == []
345+
346+
315347
def test_override() -> None:
316348
"""Tests."""
317349
sen = SensorDefinitions()

0 commit comments

Comments
 (0)