Skip to content

Commit 7974e64

Browse files
committed
Fix EV sensor unit regression and add coverage (#430)
- Initialize native unit attribute to None to avoid unitless sensor crashes - Set monetary native unit from HA system currency in async_added_to_hass - Add regression tests for unitless EV sensors and monetary currency unit
1 parent 4dda7f5 commit 7974e64

2 files changed

Lines changed: 49 additions & 10 deletions

File tree

custom_components/victron_mqtt/sensor.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def __init__(
104104
super().__init__(
105105
device, metric, device_info, "sensor", simple_naming, installation_id
106106
)
107+
self._attr_native_unit_of_measurement = None
107108
self._attr_device_class = METRIC_TYPE_TO_DEVICE_CLASS.get(metric.metric_type)
108109
# Enum sensors must not have a state class
109110
if self._attr_device_class == SensorDeviceClass.ENUM:
@@ -112,19 +113,11 @@ def __init__(
112113
self._attr_state_class = METRIC_NATURE_TO_STATE_CLASS.get(
113114
metric.metric_nature
114115
)
115-
# MONETARY uses hass.config.currency which isn't available in __init__,
116-
# so it's handled by the native_unit_of_measurement property instead.
116+
# MONETARY uses hass.config.currency which isn't available in __init__.
117117
if self._attr_device_class != SensorDeviceClass.MONETARY:
118118
self._set_unit_translation()
119119
self._attr_native_value = VictronSensor._normalize_value(metric.value)
120120

121-
@property
122-
def native_unit_of_measurement(self) -> str | None:
123-
"""Return the unit of measurement."""
124-
if self._attr_device_class == SensorDeviceClass.MONETARY:
125-
return self.hass.config.currency
126-
return self._attr_native_unit_of_measurement
127-
128121
@callback
129122
def _on_update_cb(self, value: Any) -> None:
130123
if self._baseline is not None:
@@ -142,6 +135,8 @@ def _normalize_value(value: Any) -> Any:
142135

143136
async def async_added_to_hass(self) -> None:
144137
"""Restore persistent state for FormulaMetric energy sensors."""
138+
if self._attr_device_class == SensorDeviceClass.MONETARY:
139+
self._attr_native_unit_of_measurement = self.hass.config.currency
145140

146141
# Only restore for cumulative FormulaMetric sensors (TOTAL / TOTAL_INCREASING).
147142
# These metrics start from 0 on each HA restart, so we restore the

tests/test_hub.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,51 @@ async def test_sensor(
284284
assert entities == snapshot
285285

286286

287+
async def test_sensor_without_unit_does_not_crash(
288+
hass: HomeAssistant,
289+
init_integration,
290+
) -> None:
291+
"""Test unitless EV sensors are created without native unit errors."""
292+
victron_hub, mock_config_entry = init_integration
293+
294+
await inject_message(victron_hub, "N/123/ev/40/LastEvContact", '{"value": 1780575127}')
295+
await finalize_injection(victron_hub)
296+
await hass.async_block_till_done()
297+
298+
entity_registry = er.async_get(hass)
299+
entities = er.async_entries_for_config_entry(
300+
entity_registry, mock_config_entry.entry_id
301+
)
302+
assert len(entities) == 1
303+
304+
state = hass.states.get(entities[0].entity_id)
305+
assert state is not None
306+
assert state.attributes.get("unit_of_measurement") is None
307+
308+
309+
async def test_monetary_sensor_uses_ha_currency(
310+
hass: HomeAssistant,
311+
init_integration,
312+
) -> None:
313+
"""Test EV session cost uses Home Assistant system currency."""
314+
victron_hub, mock_config_entry = init_integration
315+
hass.config.currency = "EUR"
316+
317+
await inject_message(victron_hub, "N/123/evcharger/0/Session/Cost", '{"value": 1.23}')
318+
await finalize_injection(victron_hub)
319+
await hass.async_block_till_done()
320+
321+
entity_registry = er.async_get(hass)
322+
entities = er.async_entries_for_config_entry(
323+
entity_registry, mock_config_entry.entry_id
324+
)
325+
assert len(entities) == 1
326+
327+
state = hass.states.get(entities[0].entity_id)
328+
assert state is not None
329+
assert state.attributes.get("unit_of_measurement") == "EUR"
330+
331+
287332
async def test_sensor_complex(
288333
hass: HomeAssistant,
289334
snapshot: SnapshotAssertion,
@@ -1025,4 +1070,3 @@ async def test_number_with_step(
10251070
assert float(state.state) == 57.6
10261071
assert state.attributes.get("step") == 0.1
10271072

1028-

0 commit comments

Comments
 (0)