Make sensor value unknown on missing phyrate in devolo_home_network (#179056)

This commit is contained in:
Guido Schmitz
2026-08-24 21:57:48 +02:00
committed by GitHub
parent e21cb04e98
commit b0f9769593
4 changed files with 199 additions and 10 deletions
@@ -256,13 +256,18 @@ class DevoloPlcDataRateSensorEntity(
@property
@override
def native_value(self) -> float:
def native_value(self) -> float | None:
"""State of the sensor."""
return self.entity_description.value_func(
next(
data_rate
for data_rate in self.coordinator.data.data_rates
if data_rate.mac_address_from == self.device.mac
and data_rate.mac_address_to == self._peer
if (
data_rate := next(
(
data_rate
for data_rate in self.coordinator.data.data_rates
if data_rate.mac_address_from == self.device.mac
and data_rate.mac_address_to == self._peer
),
None,
)
)
) is None:
return None
return self.entity_description.value_func(data_rate)
@@ -172,4 +172,35 @@ PLCNET_ATTACHED = LogicalNetwork(
],
)
PLCNET_MISSING_DATA_RATE = LogicalNetwork(
devices=[
{
"mac_address": "00:00:5E:00:53:00",
"attached_to_router": False,
"topology": LOCAL,
"user_device_name": "test1",
},
{
"mac_address": "00:00:5E:00:53:02",
"attached_to_router": True,
"topology": REMOTE,
"user_device_name": "test2",
},
{
"mac_address": "00:00:5E:00:53:03",
"attached_to_router": False,
"topology": REMOTE,
"user_device_name": "test3",
},
],
data_rates=[
{
"mac_address_from": "00:00:5E:00:53:00",
"mac_address_to": "00:00:5E:00:53:03",
"rx_rate": 150.0,
"tx_rate": 150.0,
},
],
)
UPTIME = 100
@@ -313,3 +313,113 @@
'unit_of_measurement': <UnitOfDataRate.MEGABITS_PER_SECOND: 'Mbit/s'>,
})
# ---
# name: test_update_plc_phyrates_missing_data_rate
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'data_rate',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Title PLC downlink PHY rate (test2)',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDataRate.MEGABITS_PER_SECOND: 'Mbit/s'>,
}),
'context': <ANY>,
'entity_id': 'sensor.mock_title_plc_downlink_phy_rate_test2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100.0',
})
# ---
# name: test_update_plc_phyrates_missing_data_rate.1
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.mock_title_plc_downlink_phy_rate_test2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'PLC downlink PHY rate (test2)',
'options': dict({
'sensor': dict({
'suggested_display_precision': 0,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_RATE: 'data_rate'>,
'original_icon': None,
'original_name': 'PLC downlink PHY rate (test2)',
'platform': 'devolo_home_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'plc_rx_rate',
'unique_id': '1234567890_plc_rx_rate_00:00:5E:00:53:02',
'unit_of_measurement': <UnitOfDataRate.MEGABITS_PER_SECOND: 'Mbit/s'>,
})
# ---
# name: test_update_plc_phyrates_missing_data_rate.2
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'data_rate',
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Mock Title PLC uplink PHY rate (test2)',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDataRate.MEGABITS_PER_SECOND: 'Mbit/s'>,
}),
'context': <ANY>,
'entity_id': 'sensor.mock_title_plc_uplink_phy_rate_test2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100.0',
})
# ---
# name: test_update_plc_phyrates_missing_data_rate.3
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.mock_title_plc_uplink_phy_rate_test2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'PLC uplink PHY rate (test2)',
'options': dict({
'sensor': dict({
'suggested_display_precision': 0,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_RATE: 'data_rate'>,
'original_icon': None,
'original_name': 'PLC uplink PHY rate (test2)',
'platform': 'devolo_home_network',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'plc_tx_rate',
'unique_id': '1234567890_plc_tx_rate_00:00:5E:00:53:02',
'unit_of_measurement': <UnitOfDataRate.MEGABITS_PER_SECOND: 'Mbit/s'>,
})
# ---
@@ -15,12 +15,12 @@ from homeassistant.components.devolo_home_network.const import (
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import configure_integration
from .const import PLCNET
from .const import PLCNET, PLCNET_MISSING_DATA_RATE
from .mock import MockDevice
from tests.common import async_fire_time_changed
@@ -193,6 +193,49 @@ async def test_update_plc_phyrates(
assert state.state == str(PLCNET.data_rates[0].tx_rate)
async def test_update_plc_phyrates_missing_data_rate(
hass: HomeAssistant,
mock_device: MockDevice,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Test plc_downlink_phyrate and plc_uplink_phyrate sensors on a lost PLC link."""
entry = configure_integration(hass)
device_name = entry.title.replace(" ", "_").lower()
entity_id_downlink = (
f"{SENSOR_DOMAIN}.{device_name}_plc_downlink_phy_rate_"
f"{PLCNET.devices[1].user_device_name}"
)
entity_id_uplink = (
f"{SENSOR_DOMAIN}.{device_name}_plc_uplink_phy_rate_"
f"{PLCNET.devices[1].user_device_name}"
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get(entity_id_downlink) == snapshot
assert entity_registry.async_get(entity_id_downlink) == snapshot
assert hass.states.get(entity_id_uplink) == snapshot
assert entity_registry.async_get(entity_id_uplink) == snapshot
# Emulate the PLC link to the peer getting lost
mock_device.plcnet.async_get_network_overview = AsyncMock(
return_value=PLCNET_MISSING_DATA_RATE
)
freezer.tick(LONG_UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(entity_id_downlink)
assert state is not None
assert state.state == STATE_UNKNOWN
state = hass.states.get(entity_id_uplink)
assert state is not None
assert state.state == STATE_UNKNOWN
async def test_update_last_update_auth_failed(
hass: HomeAssistant, mock_device: MockDevice
) -> None: