Skip to content

Commit 15505cd

Browse files
Handle Z-Wave RssiErrorReceived (home-assistant#150846)
1 parent c7001dc commit 15505cd

File tree

2 files changed

+204
-11
lines changed

2 files changed

+204
-11
lines changed

homeassistant/components/zwave_js/sensor.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
from collections.abc import Callable, Mapping
66
from dataclasses import dataclass
7-
from typing import Any
7+
from typing import Any, cast
88

99
import voluptuous as vol
10-
from zwave_js_server.const import CommandClass
10+
from zwave_js_server.const import CommandClass, RssiError
1111
from zwave_js_server.const.command_class.meter import (
1212
RESET_METER_OPTION_TARGET_VALUE,
1313
RESET_METER_OPTION_TYPE,
1414
)
15-
from zwave_js_server.exceptions import BaseZwaveJSServerError
15+
from zwave_js_server.exceptions import BaseZwaveJSServerError, RssiErrorReceived
1616
from zwave_js_server.model.controller import Controller
1717
from zwave_js_server.model.controller.statistics import ControllerStatistics
1818
from zwave_js_server.model.driver import Driver
@@ -1049,7 +1049,7 @@ def __init__(
10491049
self,
10501050
config_entry: ZwaveJSConfigEntry,
10511051
driver: Driver,
1052-
statistics_src: ZwaveNode | Controller,
1052+
statistics_src: Controller | ZwaveNode,
10531053
description: ZWaveJSStatisticsSensorEntityDescription,
10541054
) -> None:
10551055
"""Initialize a Z-Wave statistics entity."""
@@ -1080,13 +1080,31 @@ async def async_poll_value(self, _: bool) -> None:
10801080
)
10811081

10821082
@callback
1083-
def statistics_updated(self, event_data: dict) -> None:
1083+
def _statistics_updated(self, event_data: dict) -> None:
10841084
"""Call when statistics updated event is received."""
1085-
self._attr_native_value = self.entity_description.convert(
1086-
event_data["statistics_updated"], self.entity_description.key
1085+
statistics = cast(
1086+
ControllerStatistics | NodeStatistics, event_data["statistics_updated"]
10871087
)
1088+
self._set_statistics(statistics)
10881089
self.async_write_ha_state()
10891090

1091+
@callback
1092+
def _set_statistics(
1093+
self, statistics: ControllerStatistics | NodeStatistics
1094+
) -> None:
1095+
"""Set updated statistics."""
1096+
try:
1097+
self._attr_native_value = self.entity_description.convert(
1098+
statistics, self.entity_description.key
1099+
)
1100+
except RssiErrorReceived as err:
1101+
if err.error is RssiError.NOT_AVAILABLE:
1102+
self._attr_available = False
1103+
return
1104+
self._attr_native_value = None
1105+
# Reset available state.
1106+
self._attr_available = True
1107+
10901108
async def async_added_to_hass(self) -> None:
10911109
"""Call when entity is added."""
10921110
self.async_on_remove(
@@ -1104,10 +1122,8 @@ async def async_added_to_hass(self) -> None:
11041122
)
11051123
)
11061124
self.async_on_remove(
1107-
self.statistics_src.on("statistics updated", self.statistics_updated)
1125+
self.statistics_src.on("statistics updated", self._statistics_updated)
11081126
)
11091127

11101128
# Set initial state
1111-
self._attr_native_value = self.entity_description.convert(
1112-
self.statistics_src.statistics, self.entity_description.key
1113-
)
1129+
self._set_statistics(self.statistics_src.statistics)

tests/components/zwave_js/test_sensor.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,183 @@ async def test_last_seen_statistics_sensors(
10451045
assert state.state == "2024-01-01T12:00:00+00:00"
10461046

10471047

1048+
async def test_rssi_sensor_error(
1049+
hass: HomeAssistant,
1050+
zp3111: Node,
1051+
integration: MockConfigEntry,
1052+
entity_registry: er.EntityRegistry,
1053+
) -> None:
1054+
"""Test rssi sensor error."""
1055+
entity_id = "sensor.4_in_1_sensor_signal_strength"
1056+
1057+
entity_registry.async_update_entity(entity_id, disabled_by=None)
1058+
1059+
# reload integration and check if entity is correctly there
1060+
await hass.config_entries.async_reload(integration.entry_id)
1061+
await hass.async_block_till_done()
1062+
1063+
state = hass.states.get(entity_id)
1064+
assert state
1065+
assert state.state == "unknown"
1066+
1067+
# Fire statistics updated event for node
1068+
event = Event(
1069+
"statistics updated",
1070+
{
1071+
"source": "node",
1072+
"event": "statistics updated",
1073+
"nodeId": zp3111.node_id,
1074+
"statistics": {
1075+
"commandsTX": 1,
1076+
"commandsRX": 2,
1077+
"commandsDroppedTX": 3,
1078+
"commandsDroppedRX": 4,
1079+
"timeoutResponse": 5,
1080+
"rtt": 6,
1081+
"rssi": 7, # baseline
1082+
"lwr": {
1083+
"protocolDataRate": 1,
1084+
"rssi": 1,
1085+
"repeaters": [],
1086+
"repeaterRSSI": [],
1087+
"routeFailedBetween": [],
1088+
},
1089+
"nlwr": {
1090+
"protocolDataRate": 2,
1091+
"rssi": 2,
1092+
"repeaters": [],
1093+
"repeaterRSSI": [],
1094+
"routeFailedBetween": [],
1095+
},
1096+
"lastSeen": "2024-01-01T00:00:00+0000",
1097+
},
1098+
},
1099+
)
1100+
zp3111.receive_event(event)
1101+
await hass.async_block_till_done()
1102+
1103+
state = hass.states.get(entity_id)
1104+
assert state
1105+
assert state.state == "7"
1106+
1107+
event = Event(
1108+
"statistics updated",
1109+
{
1110+
"source": "node",
1111+
"event": "statistics updated",
1112+
"nodeId": zp3111.node_id,
1113+
"statistics": {
1114+
"commandsTX": 1,
1115+
"commandsRX": 2,
1116+
"commandsDroppedTX": 3,
1117+
"commandsDroppedRX": 4,
1118+
"timeoutResponse": 5,
1119+
"rtt": 6,
1120+
"rssi": 125, # no signal detected
1121+
"lwr": {
1122+
"protocolDataRate": 1,
1123+
"rssi": 1,
1124+
"repeaters": [],
1125+
"repeaterRSSI": [],
1126+
"routeFailedBetween": [],
1127+
},
1128+
"nlwr": {
1129+
"protocolDataRate": 2,
1130+
"rssi": 2,
1131+
"repeaters": [],
1132+
"repeaterRSSI": [],
1133+
"routeFailedBetween": [],
1134+
},
1135+
"lastSeen": "2024-01-01T00:00:00+0000",
1136+
},
1137+
},
1138+
)
1139+
zp3111.receive_event(event)
1140+
await hass.async_block_till_done()
1141+
1142+
state = hass.states.get(entity_id)
1143+
assert state
1144+
assert state.state == "unknown"
1145+
1146+
event = Event(
1147+
"statistics updated",
1148+
{
1149+
"source": "node",
1150+
"event": "statistics updated",
1151+
"nodeId": zp3111.node_id,
1152+
"statistics": {
1153+
"commandsTX": 1,
1154+
"commandsRX": 2,
1155+
"commandsDroppedTX": 3,
1156+
"commandsDroppedRX": 4,
1157+
"timeoutResponse": 5,
1158+
"rtt": 6,
1159+
"rssi": 127, # not available
1160+
"lwr": {
1161+
"protocolDataRate": 1,
1162+
"rssi": 1,
1163+
"repeaters": [],
1164+
"repeaterRSSI": [],
1165+
"routeFailedBetween": [],
1166+
},
1167+
"nlwr": {
1168+
"protocolDataRate": 2,
1169+
"rssi": 2,
1170+
"repeaters": [],
1171+
"repeaterRSSI": [],
1172+
"routeFailedBetween": [],
1173+
},
1174+
"lastSeen": "2024-01-01T00:00:00+0000",
1175+
},
1176+
},
1177+
)
1178+
zp3111.receive_event(event)
1179+
await hass.async_block_till_done()
1180+
1181+
state = hass.states.get(entity_id)
1182+
assert state
1183+
assert state.state == "unavailable"
1184+
1185+
event = Event(
1186+
"statistics updated",
1187+
{
1188+
"source": "node",
1189+
"event": "statistics updated",
1190+
"nodeId": zp3111.node_id,
1191+
"statistics": {
1192+
"commandsTX": 1,
1193+
"commandsRX": 2,
1194+
"commandsDroppedTX": 3,
1195+
"commandsDroppedRX": 4,
1196+
"timeoutResponse": 5,
1197+
"rtt": 6,
1198+
"rssi": 126, # receiver saturated
1199+
"lwr": {
1200+
"protocolDataRate": 1,
1201+
"rssi": 1,
1202+
"repeaters": [],
1203+
"repeaterRSSI": [],
1204+
"routeFailedBetween": [],
1205+
},
1206+
"nlwr": {
1207+
"protocolDataRate": 2,
1208+
"rssi": 2,
1209+
"repeaters": [],
1210+
"repeaterRSSI": [],
1211+
"routeFailedBetween": [],
1212+
},
1213+
"lastSeen": "2024-01-01T00:00:00+0000",
1214+
},
1215+
},
1216+
)
1217+
zp3111.receive_event(event)
1218+
await hass.async_block_till_done()
1219+
1220+
state = hass.states.get(entity_id)
1221+
assert state
1222+
assert state.state == "unknown"
1223+
1224+
10481225
ENERGY_PRODUCTION_ENTITY_MAP = {
10491226
"energy_production_power": {
10501227
"state": 1.23,

0 commit comments

Comments
 (0)