Handle Z-Wave RssiErrorReceived (#150846)

This commit is contained in:
Martin Hjelmare
2025-08-18 22:14:52 +02:00
committed by GitHub
parent c7001dcfc4
commit 15505cdd56
2 changed files with 204 additions and 11 deletions

View File

@@ -4,15 +4,15 @@ from __future__ import annotations
from collections.abc import Callable, Mapping from collections.abc import Callable, Mapping
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any, cast
import voluptuous as vol import voluptuous as vol
from zwave_js_server.const import CommandClass from zwave_js_server.const import CommandClass, RssiError
from zwave_js_server.const.command_class.meter import ( from zwave_js_server.const.command_class.meter import (
RESET_METER_OPTION_TARGET_VALUE, RESET_METER_OPTION_TARGET_VALUE,
RESET_METER_OPTION_TYPE, RESET_METER_OPTION_TYPE,
) )
from zwave_js_server.exceptions import BaseZwaveJSServerError from zwave_js_server.exceptions import BaseZwaveJSServerError, RssiErrorReceived
from zwave_js_server.model.controller import Controller from zwave_js_server.model.controller import Controller
from zwave_js_server.model.controller.statistics import ControllerStatistics from zwave_js_server.model.controller.statistics import ControllerStatistics
from zwave_js_server.model.driver import Driver from zwave_js_server.model.driver import Driver
@@ -1049,7 +1049,7 @@ class ZWaveStatisticsSensor(SensorEntity):
self, self,
config_entry: ZwaveJSConfigEntry, config_entry: ZwaveJSConfigEntry,
driver: Driver, driver: Driver,
statistics_src: ZwaveNode | Controller, statistics_src: Controller | ZwaveNode,
description: ZWaveJSStatisticsSensorEntityDescription, description: ZWaveJSStatisticsSensorEntityDescription,
) -> None: ) -> None:
"""Initialize a Z-Wave statistics entity.""" """Initialize a Z-Wave statistics entity."""
@@ -1080,13 +1080,31 @@ class ZWaveStatisticsSensor(SensorEntity):
) )
@callback @callback
def statistics_updated(self, event_data: dict) -> None: def _statistics_updated(self, event_data: dict) -> None:
"""Call when statistics updated event is received.""" """Call when statistics updated event is received."""
self._attr_native_value = self.entity_description.convert( statistics = cast(
event_data["statistics_updated"], self.entity_description.key ControllerStatistics | NodeStatistics, event_data["statistics_updated"]
) )
self._set_statistics(statistics)
self.async_write_ha_state() self.async_write_ha_state()
@callback
def _set_statistics(
self, statistics: ControllerStatistics | NodeStatistics
) -> None:
"""Set updated statistics."""
try:
self._attr_native_value = self.entity_description.convert(
statistics, self.entity_description.key
)
except RssiErrorReceived as err:
if err.error is RssiError.NOT_AVAILABLE:
self._attr_available = False
return
self._attr_native_value = None
# Reset available state.
self._attr_available = True
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Call when entity is added.""" """Call when entity is added."""
self.async_on_remove( self.async_on_remove(
@@ -1104,10 +1122,8 @@ class ZWaveStatisticsSensor(SensorEntity):
) )
) )
self.async_on_remove( self.async_on_remove(
self.statistics_src.on("statistics updated", self.statistics_updated) self.statistics_src.on("statistics updated", self._statistics_updated)
) )
# Set initial state # Set initial state
self._attr_native_value = self.entity_description.convert( self._set_statistics(self.statistics_src.statistics)
self.statistics_src.statistics, self.entity_description.key
)

View File

@@ -1045,6 +1045,183 @@ async def test_last_seen_statistics_sensors(
assert state.state == "2024-01-01T12:00:00+00:00" assert state.state == "2024-01-01T12:00:00+00:00"
async def test_rssi_sensor_error(
hass: HomeAssistant,
zp3111: Node,
integration: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test rssi sensor error."""
entity_id = "sensor.4_in_1_sensor_signal_strength"
entity_registry.async_update_entity(entity_id, disabled_by=None)
# reload integration and check if entity is correctly there
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "unknown"
# Fire statistics updated event for node
event = Event(
"statistics updated",
{
"source": "node",
"event": "statistics updated",
"nodeId": zp3111.node_id,
"statistics": {
"commandsTX": 1,
"commandsRX": 2,
"commandsDroppedTX": 3,
"commandsDroppedRX": 4,
"timeoutResponse": 5,
"rtt": 6,
"rssi": 7, # baseline
"lwr": {
"protocolDataRate": 1,
"rssi": 1,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"nlwr": {
"protocolDataRate": 2,
"rssi": 2,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"lastSeen": "2024-01-01T00:00:00+0000",
},
},
)
zp3111.receive_event(event)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "7"
event = Event(
"statistics updated",
{
"source": "node",
"event": "statistics updated",
"nodeId": zp3111.node_id,
"statistics": {
"commandsTX": 1,
"commandsRX": 2,
"commandsDroppedTX": 3,
"commandsDroppedRX": 4,
"timeoutResponse": 5,
"rtt": 6,
"rssi": 125, # no signal detected
"lwr": {
"protocolDataRate": 1,
"rssi": 1,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"nlwr": {
"protocolDataRate": 2,
"rssi": 2,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"lastSeen": "2024-01-01T00:00:00+0000",
},
},
)
zp3111.receive_event(event)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "unknown"
event = Event(
"statistics updated",
{
"source": "node",
"event": "statistics updated",
"nodeId": zp3111.node_id,
"statistics": {
"commandsTX": 1,
"commandsRX": 2,
"commandsDroppedTX": 3,
"commandsDroppedRX": 4,
"timeoutResponse": 5,
"rtt": 6,
"rssi": 127, # not available
"lwr": {
"protocolDataRate": 1,
"rssi": 1,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"nlwr": {
"protocolDataRate": 2,
"rssi": 2,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"lastSeen": "2024-01-01T00:00:00+0000",
},
},
)
zp3111.receive_event(event)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "unavailable"
event = Event(
"statistics updated",
{
"source": "node",
"event": "statistics updated",
"nodeId": zp3111.node_id,
"statistics": {
"commandsTX": 1,
"commandsRX": 2,
"commandsDroppedTX": 3,
"commandsDroppedRX": 4,
"timeoutResponse": 5,
"rtt": 6,
"rssi": 126, # receiver saturated
"lwr": {
"protocolDataRate": 1,
"rssi": 1,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"nlwr": {
"protocolDataRate": 2,
"rssi": 2,
"repeaters": [],
"repeaterRSSI": [],
"routeFailedBetween": [],
},
"lastSeen": "2024-01-01T00:00:00+0000",
},
},
)
zp3111.receive_event(event)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "unknown"
ENERGY_PRODUCTION_ENTITY_MAP = { ENERGY_PRODUCTION_ENTITY_MAP = {
"energy_production_power": { "energy_production_power": {
"state": 1.23, "state": 1.23,