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 dataclasses import dataclass
from typing import Any
from typing import Any, cast
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 (
RESET_METER_OPTION_TARGET_VALUE,
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.statistics import ControllerStatistics
from zwave_js_server.model.driver import Driver
@@ -1049,7 +1049,7 @@ class ZWaveStatisticsSensor(SensorEntity):
self,
config_entry: ZwaveJSConfigEntry,
driver: Driver,
statistics_src: ZwaveNode | Controller,
statistics_src: Controller | ZwaveNode,
description: ZWaveJSStatisticsSensorEntityDescription,
) -> None:
"""Initialize a Z-Wave statistics entity."""
@@ -1080,13 +1080,31 @@ class ZWaveStatisticsSensor(SensorEntity):
)
@callback
def statistics_updated(self, event_data: dict) -> None:
def _statistics_updated(self, event_data: dict) -> None:
"""Call when statistics updated event is received."""
self._attr_native_value = self.entity_description.convert(
event_data["statistics_updated"], self.entity_description.key
statistics = cast(
ControllerStatistics | NodeStatistics, event_data["statistics_updated"]
)
self._set_statistics(statistics)
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:
"""Call when entity is added."""
self.async_on_remove(
@@ -1104,10 +1122,8 @@ class ZWaveStatisticsSensor(SensorEntity):
)
)
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
self._attr_native_value = self.entity_description.convert(
self.statistics_src.statistics, self.entity_description.key
)
self._set_statistics(self.statistics_src.statistics)

View File

@@ -1045,6 +1045,183 @@ async def test_last_seen_statistics_sensors(
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_power": {
"state": 1.23,