Skip to content

Commit e7e6cd6

Browse files
committed
Sensor info
1 parent 9791fe3 commit e7e6cd6

7 files changed

Lines changed: 847 additions & 805 deletions

File tree

‎scripts/gen_sensors.py‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,7 @@ def get_sensor_info(sensor: Sensor | str | None) -> str:
4747
"""Sensor info."""
4848
if not sensor:
4949
return ""
50-
if isinstance(sensor, str):
51-
return sensor
52-
reg = (
53-
str(sensor.address[0]) if len(sensor.address) == 1 else str(sensor.address)
54-
)
55-
return reg
50+
return sensor if isinstance(sensor, str) else sensor.source
5651

5752
headers = ["Name", *sorted(all_defs), "Group"]
5853
data = table_data(sensors.values(), headers)

‎src/ha_addon_sunsynk_multi/__main__.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pathlib import Path
77

88
from sunsynk import VERSION
9+
from sunsynk.utils import pretty_table_sensors
910

1011
from .a_inverter import STATE
1112
from .a_sensor import MQTT, SS_TOPIC
@@ -66,6 +67,11 @@ async def main_loop() -> int:
6667
await ist.hass_discover_sensors()
6768
ist.cb = build_callback_schedule(ist)
6869
CALLBACKS.append(ist.cb)
70+
71+
# Add info from the callback schedules?
72+
tab = pretty_table_sensors(list(SOPT), ist.inv, {})
73+
_LOG.info("Inverter %s\n%s", ist.inv.port, tab)
74+
6975
except (ConnectionError, ValueError) as err:
7076
ist.log_bold(str(err))
7177
_LOG.critical(

‎src/ha_addon_sunsynk_multi/a_inverter.py‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sunsynk.helpers import slug
1212
from sunsynk.rwsensors import RWSensor
1313
from sunsynk.sunsynk import Sensor, Sunsynk, ValType
14+
from sunsynk.utils import pretty_table_sensors
1415

1516
from .a_sensor import MQTT, SS_TOPIC, ASensor
1617
from .options import OPT, InverterOptions
@@ -133,20 +134,19 @@ async def connect(self) -> None:
133134
raise ValueError(
134135
f"Serial number mismatch. Expected {expected_ser}, got {actual_ser}"
135136
)
136-
self.log_bold(f"Inverter serial number '****{actual_ser[-4:]}'")
137137

138-
_LOG.info(
139-
"Device type: %s, using the %s sensor definitions",
140-
self.inv.state[DEFS.device_type],
141-
OPT.sensor_definitions,
142-
)
138+
# All seem ok
139+
add_info = {"device_type": f"\n(config: {OPT.sensor_definitions})"}
143140

144-
_LOG.info("Protocol version: %s", self.inv.state[DEFS.protocol])
141+
tab = pretty_table_sensors(sensors, self.inv, add_info=add_info)
142+
_LOG.info("Inverter %s - startup sensors\n%s", self.inv.port, tab)
145143

146144
# Initial read for all sensors
147145
sensors = list(SOPT)
148-
_LOG.info("Reading all sensors %s", ", ".join(s.name for s in sensors))
146+
_LOG.info("Reading configured sensors %s", len(sensors))
149147
await self.read_sensors(sensors=sensors)
148+
# tab = pretty_table_sensors(sensors, self.inv)
149+
# _LOG.info("Inverter %s - active sensors\n%s", self.inv.port, tab)
150150

151151
@property
152152
def rated_power(self) -> float:

‎src/sunsynk/sensors.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ def id(self) -> str:
3535
"""Get the sensor ID."""
3636
return slug(self.name)
3737

38+
@property
39+
def source(self) -> str:
40+
"""Return the source of the sensor."""
41+
res = (
42+
f"[{self.address[0]}]"
43+
if len(self.address) == 1
44+
else str(self.address).replace("(", "[").replace(")", "]").replace(" ", "")
45+
)
46+
if self.bitmask:
47+
res += f" & 0x{self.bitmask:02X}"
48+
sig = " S" if self.factor == -1 else ""
49+
if abs(self.factor) != 1:
50+
res += f" * {abs(self.factor)}"
51+
return f"{res}{sig}"
52+
3853
def reg_to_value(self, regs: RegType) -> ValType:
3954
"""Return the value from the registers."""
4055
regs = self.masked(regs)

‎src/sunsynk/utils.py‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
from pathlib import Path
77
from textwrap import wrap
88
from types import ModuleType
9-
from typing import Any
9+
from typing import TYPE_CHECKING, Any
1010

1111
from prettytable import PrettyTable
1212

13+
if TYPE_CHECKING:
14+
from sunsynk.sensors import Sensor
15+
from sunsynk.sunsynk import Sunsynk
16+
1317

1418
def import_module(mod_name: str, folder: str | Path | None = None) -> ModuleType:
1519
"""import_module."""
@@ -57,15 +61,9 @@ def pretty_table[T](
5761
if calculated_cols:
5862
for colidx, func in calculated_cols.items():
5963
row[int(colidx)] = func(row_any)
60-
61-
if wrap_length == 0 or not any(len(v) > wrap_length for v in row):
62-
table.add_row(row)
63-
continue
64-
65-
row_wrap = [wrap(v, wrap_length) for v in row]
66-
while any(row_wrap):
67-
table.add_row([r[0] if r else "" for r in row_wrap])
68-
row_wrap = [r[1:] for r in row_wrap]
64+
if wrap_length > 0:
65+
row = ["\n".join(wrap(v, wrap_length)) for v in row]
66+
table.add_row(row)
6967

7068
return table
7169

@@ -77,3 +75,19 @@ def table_data[T](
7775
if headers is None:
7876
headers = list({k for v in data for k in v.keys()})
7977
return headers, [[v.get(k) for k in headers] for v in data]
78+
79+
80+
def pretty_table_sensors(
81+
sensors: list["Sensor"], inv: "Sunsynk", add_info: dict[str, str]
82+
) -> PrettyTable:
83+
"""Generate a pretty table for the given sensors."""
84+
data: list[list[str]] = []
85+
for sen in sorted(sensors, key=lambda s: s.address):
86+
row = [sen.id, sen.source, f"{inv.state[sen]} {sen.unit}"]
87+
if sen.id == "serial":
88+
row[2] = f"****{row[2][-4:]}"
89+
if addi := add_info.get(sen.id):
90+
row[2] += f"{addi}"
91+
data.append(row)
92+
93+
return pretty_table(["Sensor", "Source", "Value"], data, wrap_length=0)

‎src/tests/sunsynk/test_sensors.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,15 @@ def test_decode_fault() -> None:
274274
assert s.reg_to_value(regs) == "F32"
275275
regs = (0x0, 0x0, 0x1, 0x0)
276276
assert s.reg_to_value(regs) == "F33"
277+
278+
279+
def test_source() -> None:
280+
"""Test sensor source."""
281+
assert Sensor(1, "test_sensor", "V", -1).source == "[1] S"
282+
assert Sensor((1, 2), "test_sensor", "V", 10).source == "[1,2] * 10"
283+
assert Sensor((1, 2), "test_sensor", "V", bitmask=0xA).source == "[1,2] & 0x0A"
284+
assert (
285+
Sensor((1, 2), "test_sensor", "V", bitmask=0xA, factor=-1).source
286+
== "[1,2] & 0x0A S"
287+
)
288+
assert Sensor((1, 2), "test_sensor", "V", bitmask=0xEF1A).source == "[1,2] & 0xEF1A"

0 commit comments

Comments
 (0)