Skip to content

Commit 57b1f61

Browse files
authored
Merge branch 'main' into remove-log-f-strings
2 parents 0f9672b + fea8a3b commit 57b1f61

6 files changed

Lines changed: 50 additions & 4 deletions

File tree

custom_components/solax_modbus/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ class BaseModbusNumberEntityDescription(NumberEntityDescription):
383383
state: str | None = None
384384
max_exceptions: list[tuple[str, int | float]] | None = None # None or list with structure [ ('U50EC' , 40,) ]
385385
min_exceptions_minus: list[tuple[str, int | float]] | None = None # same structure as max_exceptions, values are applied with a minus
386+
min_exceptions: list[tuple[str, int | float]] | None = None # None or list with structure [ ('U50EC' , 10,) ]
386387
blacklist: list[str] | None = None # None or list of serial number prefixes like
387388
write_method: int = WRITE_SINGLE_MODBUS # WRITE_SINGLE_MOBUS or WRITE_MULTI_MODBUS or WRITE_DATA_LOCAL
388389
initvalue: int | None = None # initial default value for WRITE_DATA_LOCAL entities

custom_components/solax_modbus/energy_dashboard.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,14 +319,16 @@ def get_source_key(self, datadict: dict[str, float]) -> str:
319319

320320
return self.source_key # Use regular sensor (single mode or Slave)
321321

322-
def get_value(self, datadict: dict[str, float]) -> float:
322+
def get_value(self, datadict: dict[str, float]) -> float | None:
323323
"""Get value from source sensor, applying filter, invert, and custom functions."""
324324
source_key = self.get_source_key(datadict) # Handles parallel mode
325-
value = datadict.get(source_key, 0)
326325

326+
# Grab the value for the source. If it is unavailable, return None value to propagate unavailable state.
327+
# This avoids resetting total increasing sensors and unintentionally breaking energy statistics.
328+
value = datadict.get(source_key, None)
327329
if value is None:
328-
_LOGGER.warning("Source sensor %s not found, using 0", source_key) # type: ignore[unreachable]
329-
return 0
330+
_LOGGER.debug("Source sensor %s not found or has no value, marking unavailable", source_key)
331+
return None
330332

331333
# Apply filter function first (universal - applies to all sensor types)
332334
if self.filter_function:

custom_components/solax_modbus/number.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ def __init__(
122122
) in number_info.max_exceptions:
123123
if hub.seriesnumber.startswith(prefix):
124124
self._attr_native_max_value = native_value
125+
if number_info.min_exceptions:
126+
for (
127+
prefix,
128+
native_value,
129+
) in number_info.min_exceptions:
130+
if hub.seriesnumber.startswith(prefix):
131+
self._attr_native_min_value = native_value
125132
if number_info.min_exceptions_minus:
126133
for (
127134
prefix,

custom_components/solax_modbus/plugin_sofar.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3981,6 +3981,7 @@ def value_function_sync_rtc_ymd_sofar(initval: Any, descr: Any, datadict: dict[s
39813981
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
39823982
device_class=SensorDeviceClass.VOLTAGE,
39833983
register=0x9051,
3984+
suggested_display_precision=3,
39843985
scale=0.001,
39853986
rounding=3,
39863987
allowedtypes=BAT_BTS,
@@ -3992,6 +3993,7 @@ def value_function_sync_rtc_ymd_sofar(initval: Any, descr: Any, datadict: dict[s
39923993
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
39933994
device_class=SensorDeviceClass.VOLTAGE,
39943995
register=0x9069,
3996+
suggested_display_precision=3,
39953997
scale=0.001,
39963998
rounding=3,
39973999
allowedtypes=BAT_BTS,
@@ -4002,6 +4004,7 @@ def value_function_sync_rtc_ymd_sofar(initval: Any, descr: Any, datadict: dict[s
40024004
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
40034005
device_class=SensorDeviceClass.VOLTAGE,
40044006
register=0x906A,
4007+
suggested_display_precision=3,
40054008
scale=0.001,
40064009
rounding=3,
40074010
allowedtypes=BAT_BTS,

custom_components/solax_modbus/plugin_solax.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,6 +1900,10 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
19001900
# ('H1E', 1 ), # more specific entry comes last and wins
19011901
]
19021902

1903+
MIN_SOC: list[tuple[str, int | float]] = [
1904+
("10M", 0), # Gen6 X1-VAST allow discharge to 0% per #2187
1905+
]
1906+
19031907
NUMBER_TYPES: Sequence["SolaxModbusNumberEntityDescription"] = [
19041908
###
19051909
#
@@ -2034,6 +2038,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
20342038
native_max_value=100,
20352039
native_step=1,
20362040
native_unit_of_measurement=PERCENTAGE,
2041+
min_exceptions=MIN_SOC,
20372042
initvalue=95,
20382043
register_data_type=REGISTER_U16,
20392044
write_method=WRITE_DATA_LOCAL,
@@ -2048,6 +2053,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
20482053
native_max_value=100,
20492054
native_step=1,
20502055
native_unit_of_measurement=PERCENTAGE,
2056+
min_exceptions=MIN_SOC,
20512057
initvalue=10,
20522058
register_data_type=REGISTER_U16,
20532059
write_method=WRITE_DATA_LOCAL,
@@ -2299,6 +2305,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
22992305
native_max_value=25,
23002306
native_step=1,
23012307
native_unit_of_measurement=PERCENTAGE,
2308+
min_exceptions=MIN_SOC,
23022309
allowedtypes=AC | HYBRID | GEN4 | GEN5 | GEN6 | EPS,
23032310
icon="mdi:battery-charging-low",
23042311
),
@@ -2350,6 +2357,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
23502357
native_max_value=100,
23512358
native_step=1,
23522359
native_unit_of_measurement=PERCENTAGE,
2360+
min_exceptions=MIN_SOC,
23532361
allowedtypes=AC | HYBRID | GEN4 | GEN5 | GEN6,
23542362
icon="mdi:battery-charging-low",
23552363
),
@@ -2512,6 +2520,7 @@ def value_function_bms_2_max_charge(initval: int, descr: Any, datadict: dict[str
25122520
native_max_value=100,
25132521
native_step=1,
25142522
native_unit_of_measurement=PERCENTAGE,
2523+
min_exceptions=MIN_SOC,
25152524
allowedtypes=AC | HYBRID | GEN4 | GEN5 | GEN6,
25162525
icon="mdi:battery-charging-low",
25172526
),

tests/unit/test_number_exceptions_type_regressions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,30 @@ def test_exception_value_used_as_native_max_value() -> None:
137137
assert isinstance(native_max_value, float)
138138

139139

140+
def test_exception_value_used_as_native_min_value() -> None:
141+
"""Test that exception values are used as native_min_value (float).
142+
143+
This documents why the exception values need to support float: they are
144+
directly assigned to native_min_value which is typed as float in number.py.
145+
"""
146+
max_exceptions: list[tuple[str, int | float]] = [
147+
("L30E", 15.0),
148+
("10M", 1.0),
149+
]
150+
151+
serial_number = "10M123456"
152+
native_min_value: float = 10.0 # Default
153+
154+
# Simulate exception lookup and assignment
155+
for prefix, value in max_exceptions:
156+
if serial_number.startswith(prefix):
157+
native_min_value = float(value) # Must be float
158+
break
159+
160+
assert native_min_value == 1.0
161+
assert isinstance(native_min_value, float)
162+
163+
140164
def test_list_invariance_with_mixed_types() -> None:
141165
"""Test that list invariance is satisfied with int | float union type.
142166

0 commit comments

Comments
 (0)