mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 01:11:51 -04:00
Fix Duco unknown node type not re-evaluated after becoming known (#169579)
This commit is contained in:
@@ -167,6 +167,7 @@ async def async_setup_entry(
|
||||
|
||||
@callback
|
||||
def _async_add_new_entities() -> None:
|
||||
"""Add new sensor entities and remove stale ones on coordinator updates."""
|
||||
# Remove devices whose nodes have disappeared from the API.
|
||||
# The firmware removes deregistered RF/wired nodes automatically.
|
||||
# BSRH box sensors that are physically unplugged from the PCB are
|
||||
@@ -190,14 +191,19 @@ async def async_setup_entry(
|
||||
for node in coordinator.data.nodes.values():
|
||||
if node.node_id in known_nodes:
|
||||
continue
|
||||
known_nodes.add(node.node_id)
|
||||
if node.general.node_type == NodeType.UNKNOWN:
|
||||
_LOGGER.warning(
|
||||
"Duco node %s (%s) has an unsupported device type and will be ignored",
|
||||
# Do not add the node to known_nodes so that it is re-evaluated
|
||||
# on every coordinator update. This allows entities to be
|
||||
# created automatically once a firmware update or library
|
||||
# update adds support for the device type.
|
||||
_LOGGER.debug(
|
||||
"Duco node %s (%s) has an unsupported device type and will be "
|
||||
"retried on subsequent coordinator updates",
|
||||
node.node_id,
|
||||
node.general.name,
|
||||
)
|
||||
continue
|
||||
known_nodes.add(node.node_id)
|
||||
new_entities.extend(
|
||||
DucoSensorEntity(coordinator, node, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for the Duco sensor platform."""
|
||||
|
||||
import logging
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from duco.exceptions import DucoConnectionError, DucoError
|
||||
@@ -255,3 +256,104 @@ async def test_unknown_node_type_logs_warning_and_creates_no_entities(
|
||||
identifiers={(DOMAIN, f"{mock_config_entry.unique_id}_99")}
|
||||
)
|
||||
assert device is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_previously_unknown_node_gets_entities_after_type_becomes_known(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
mock_nodes: list[Node],
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that a node with UNKNOWN type is retried and gets entities once the type resolves."""
|
||||
node_id = 99
|
||||
ventilation = NodeVentilationInfo(
|
||||
state="AUTO", time_state_remain=0, time_state_end=0, mode="-", flow_lvl_tgt=None
|
||||
)
|
||||
sensor = NodeSensorInfo(co2=None, iaq_co2=None, rh=62.0, iaq_rh=70, temp=21.0)
|
||||
|
||||
def _make_node(node_type: NodeType | str) -> Node:
|
||||
"""Create a Node with the given node type for use in tests."""
|
||||
return Node(
|
||||
node_id=node_id,
|
||||
general=NodeGeneralInfo(
|
||||
node_type=node_type,
|
||||
sub_type=0,
|
||||
network_type="RF",
|
||||
parent=1,
|
||||
asso=1,
|
||||
name="Future sensor",
|
||||
identify=0,
|
||||
),
|
||||
ventilation=ventilation,
|
||||
sensor=sensor,
|
||||
)
|
||||
|
||||
# First poll: UNKNOWN type — no entities created.
|
||||
mock_duco_client.async_get_nodes.return_value = [
|
||||
*mock_nodes,
|
||||
_make_node(NodeType.UNKNOWN),
|
||||
]
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert hass.states.get("sensor.future_sensor_humidity") is None
|
||||
|
||||
# Second poll: type now resolved — entities must be created.
|
||||
mock_duco_client.async_get_nodes.return_value = [*mock_nodes, _make_node("BSRH")]
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
state = hass.states.get("sensor.future_sensor_humidity")
|
||||
assert state is not None
|
||||
assert state.state == "62.0"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_unknown_node_logged_at_debug(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
mock_nodes: list[Node],
|
||||
freezer: FrozenDateTimeFactory,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that UNKNOWN nodes are logged at DEBUG level on every coordinator update."""
|
||||
unknown_node = Node(
|
||||
node_id=99,
|
||||
general=NodeGeneralInfo(
|
||||
node_type=NodeType.UNKNOWN,
|
||||
sub_type=0,
|
||||
network_type="RF",
|
||||
parent=1,
|
||||
asso=1,
|
||||
name="Future sensor",
|
||||
identify=0,
|
||||
),
|
||||
ventilation=NodeVentilationInfo(
|
||||
state="AUTO",
|
||||
time_state_remain=0,
|
||||
time_state_end=0,
|
||||
mode="-",
|
||||
flow_lvl_tgt=None,
|
||||
),
|
||||
sensor=NodeSensorInfo(co2=None, iaq_co2=None, rh=None, iaq_rh=None, temp=None),
|
||||
)
|
||||
mock_duco_client.async_get_nodes.return_value = [*mock_nodes, unknown_node]
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="homeassistant.components.duco"):
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert "has an unsupported device type" not in caplog.text
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="homeassistant.components.duco"):
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert "has an unsupported device type" in caplog.text
|
||||
|
||||
Reference in New Issue
Block a user