Mark entity unavailable if data can't be fetched (#156928)

This commit is contained in:
Andre Lengwenus
2025-11-22 15:36:47 +01:00
committed by GitHub
parent 74ad5066e2
commit f73e92a34a
13 changed files with 448 additions and 71 deletions
@@ -73,14 +73,17 @@ class LcnBinarySensor(LcnEntity, BinarySensorEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_binary_sensors(
SCAN_INTERVAL.seconds
self._attr_available = (
await self.device_connection.request_status_binary_sensors(
SCAN_INTERVAL.seconds
)
is not None
)
def input_received(self, input_obj: InputType) -> None:
"""Set sensor value when LCN input object (command) is received."""
if not isinstance(input_obj, pypck.inputs.ModStatusBinSensors):
return
self._attr_available = True
self._attr_is_on = input_obj.get_state(self.bin_sensor_port.value)
self.async_write_ha_state()
+10 -8
View File
@@ -171,20 +171,22 @@ class LcnClimate(LcnEntity, ClimateEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await asyncio.gather(
self.device_connection.request_status_variable(
self.variable, SCAN_INTERVAL.seconds
),
self.device_connection.request_status_variable(
self.setpoint, SCAN_INTERVAL.seconds
),
self._attr_available = any(
await asyncio.gather(
self.device_connection.request_status_variable(
self.variable, SCAN_INTERVAL.seconds
),
self.device_connection.request_status_variable(
self.setpoint, SCAN_INTERVAL.seconds
),
)
)
def input_received(self, input_obj: InputType) -> None:
"""Set temperature value when LCN input object is received."""
if not isinstance(input_obj, pypck.inputs.ModStatusVar):
return
self._attr_available = True
if input_obj.get_var() == self.variable:
self._attr_current_temperature = float(
input_obj.get_value().to_var_unit(self.unit)
+13 -9
View File
@@ -133,13 +133,15 @@ class LcnOutputsCover(LcnEntity, CoverEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
if not self.device_connection.is_group:
await asyncio.gather(
self.device_connection.request_status_output(
pypck.lcn_defs.OutputPort["OUTPUTUP"], SCAN_INTERVAL.seconds
),
self.device_connection.request_status_output(
pypck.lcn_defs.OutputPort["OUTPUTDOWN"], SCAN_INTERVAL.seconds
),
self._attr_available = any(
await asyncio.gather(
self.device_connection.request_status_output(
pypck.lcn_defs.OutputPort["OUTPUTUP"], SCAN_INTERVAL.seconds
),
self.device_connection.request_status_output(
pypck.lcn_defs.OutputPort["OUTPUTDOWN"], SCAN_INTERVAL.seconds
),
)
)
def input_received(self, input_obj: InputType) -> None:
@@ -149,7 +151,7 @@ class LcnOutputsCover(LcnEntity, CoverEntity):
or input_obj.get_output_id() not in self.output_ids
):
return
self._attr_available = True
if input_obj.get_percent() > 0: # motor is on
if input_obj.get_output_id() == self.output_ids[0]:
self._attr_is_opening = True
@@ -272,11 +274,12 @@ class LcnRelayCover(LcnEntity, CoverEntity):
self.motor, self.positioning_mode, SCAN_INTERVAL.seconds
)
)
await asyncio.gather(*coros)
self._attr_available = any(await asyncio.gather(*coros))
def input_received(self, input_obj: InputType) -> None:
"""Set cover states when LCN input object (command) is received."""
if isinstance(input_obj, pypck.inputs.ModStatusRelays):
self._attr_available = True
self._attr_is_opening = input_obj.is_opening(self.motor.value)
self._attr_is_closing = input_obj.is_closing(self.motor.value)
@@ -293,6 +296,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
)
and input_obj.motor == self.motor.value
):
self._attr_available = True
self._attr_current_cover_position = int(input_obj.position)
if self._attr_current_cover_position in [0, 100]:
self._attr_is_opening = False
+10 -4
View File
@@ -149,8 +149,11 @@ class LcnOutputLight(LcnEntity, LightEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_output(
self.output, SCAN_INTERVAL.seconds
self._attr_available = (
await self.device_connection.request_status_output(
self.output, SCAN_INTERVAL.seconds
)
is not None
)
def input_received(self, input_obj: InputType) -> None:
@@ -200,12 +203,15 @@ class LcnRelayLight(LcnEntity, LightEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_relays(SCAN_INTERVAL.seconds)
self._attr_available = (
await self.device_connection.request_status_relays(SCAN_INTERVAL.seconds)
is not None
)
def input_received(self, input_obj: InputType) -> None:
"""Set light state when LCN input object (command) is received."""
if not isinstance(input_obj, pypck.inputs.ModStatusRelays):
return
self._attr_available = True
self._attr_is_on = input_obj.get_state(self.output.value)
self.async_write_ha_state()
@@ -25,7 +25,7 @@ rules:
status: exempt
comment: Integration has no configuration parameters
docs-installation-parameters: done
entity-unavailable: todo
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
+12 -6
View File
@@ -133,8 +133,11 @@ class LcnVariableSensor(LcnEntity, SensorEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_variable(
self.variable, SCAN_INTERVAL.seconds
self._attr_available = (
await self.device_connection.request_status_variable(
self.variable, SCAN_INTERVAL.seconds
)
is not None
)
def input_received(self, input_obj: InputType) -> None:
@@ -144,7 +147,7 @@ class LcnVariableSensor(LcnEntity, SensorEntity):
or input_obj.get_var() != self.variable
):
return
self._attr_available = True
is_regulator = self.variable.name in SETPOINTS
self._attr_native_value = input_obj.get_value().to_var_unit(
self.unit, is_regulator
@@ -171,15 +174,18 @@ class LcnLedLogicSensor(LcnEntity, SensorEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_led_and_logic_ops(
SCAN_INTERVAL.seconds
self._attr_available = (
await self.device_connection.request_status_led_and_logic_ops(
SCAN_INTERVAL.seconds
)
is not None
)
def input_received(self, input_obj: InputType) -> None:
"""Set sensor value when LCN input object (command) is received."""
if not isinstance(input_obj, pypck.inputs.ModStatusLedsAndLogicOps):
return
self._attr_available = True
if self.source in pypck.lcn_defs.LedPort:
self._attr_native_value = input_obj.get_led_state(
self.source.value
+24 -10
View File
@@ -95,8 +95,11 @@ class LcnOutputSwitch(LcnEntity, SwitchEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_output(
self.output, SCAN_INTERVAL.seconds
self._attr_available = (
await self.device_connection.request_status_output(
self.output, SCAN_INTERVAL.seconds
)
is not None
)
def input_received(self, input_obj: InputType) -> None:
@@ -106,7 +109,7 @@ class LcnOutputSwitch(LcnEntity, SwitchEntity):
or input_obj.get_output_id() != self.output.value
):
return
self._attr_available = True
self._attr_is_on = input_obj.get_percent() > 0
self.async_write_ha_state()
@@ -142,13 +145,16 @@ class LcnRelaySwitch(LcnEntity, SwitchEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_relays(SCAN_INTERVAL.seconds)
self._attr_available = (
await self.device_connection.request_status_relays(SCAN_INTERVAL.seconds)
is not None
)
def input_received(self, input_obj: InputType) -> None:
"""Set switch state when LCN input object (command) is received."""
if not isinstance(input_obj, pypck.inputs.ModStatusRelays):
return
self._attr_available = True
self._attr_is_on = input_obj.get_state(self.output.value)
self.async_write_ha_state()
@@ -183,8 +189,11 @@ class LcnRegulatorLockSwitch(LcnEntity, SwitchEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_variable(
self.setpoint_variable, SCAN_INTERVAL.seconds
self._attr_available = (
await self.device_connection.request_status_variable(
self.setpoint_variable, SCAN_INTERVAL.seconds
)
is not None
)
def input_received(self, input_obj: InputType) -> None:
@@ -194,7 +203,7 @@ class LcnRegulatorLockSwitch(LcnEntity, SwitchEntity):
or input_obj.get_var() != self.setpoint_variable
):
return
self._attr_available = True
self._attr_is_on = input_obj.get_value().is_locked_regulator()
self.async_write_ha_state()
@@ -236,7 +245,12 @@ class LcnKeyLockSwitch(LcnEntity, SwitchEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
await self.device_connection.request_status_locked_keys(SCAN_INTERVAL.seconds)
self._attr_available = (
await self.device_connection.request_status_locked_keys(
SCAN_INTERVAL.seconds
)
is not None
)
def input_received(self, input_obj: InputType) -> None:
"""Set switch state when LCN input object (command) is received."""
@@ -245,6 +259,6 @@ class LcnKeyLockSwitch(LcnEntity, SwitchEntity):
or self.key not in pypck.lcn_defs.Key
):
return
self._attr_available = True
self._attr_is_on = input_obj.get_state(self.table_id, self.key_id)
self.async_write_ha_state()
+41 -2
View File
@@ -2,18 +2,20 @@
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import ModStatusBinSensors
from pypck.lcn_addr import LcnAddr
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.lcn.binary_sensor import SCAN_INTERVAL
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, init_integration
from .conftest import MockConfigEntry, MockDeviceConnection, init_integration
from tests.common import snapshot_platform
from tests.common import async_fire_time_changed, snapshot_platform
BINARY_SENSOR_SENSOR1 = "binary_sensor.testmodule_binary_sensor1"
@@ -61,6 +63,43 @@ async def test_pushed_binsensor_status_change(
assert state.state == STATE_ON
async def test_availability(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, entry: MockConfigEntry
) -> None:
"""Test the availability of binary_sensor entity."""
await init_integration(hass, entry)
state = hass.states.get(BINARY_SENSOR_SENSOR1)
assert state is not None
assert state.state != STATE_UNAVAILABLE
# no response from device -> unavailable
with patch.object(
MockDeviceConnection, "request_status_binary_sensors", return_value=None
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(BINARY_SENSOR_SENSOR1)
assert state is not None
assert state.state == STATE_UNAVAILABLE
# response from device -> available
with patch.object(
MockDeviceConnection,
"request_status_binary_sensors",
return_value=ModStatusBinSensors(LcnAddr(0, 7, False), [False] * 8),
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(BINARY_SENSOR_SENSOR1)
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the binary sensor is removed when the config entry is unloaded."""
await init_integration(hass, entry)
+65 -22
View File
@@ -2,6 +2,7 @@
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import ModStatusVar, Unknown
from pypck.lcn_addr import LcnAddr
from pypck.lcn_defs import Var, VarUnit, VarValue
@@ -18,6 +19,7 @@ from homeassistant.components.climate import (
SERVICE_SET_TEMPERATURE,
HVACMode,
)
from homeassistant.components.lcn.climate import SCAN_INTERVAL
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.const import (
ATTR_ENTITY_ID,
@@ -31,7 +33,9 @@ from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, MockDeviceConnection, init_integration
from tests.common import snapshot_platform
from tests.common import async_fire_time_changed, snapshot_platform
CLIMATE_CLIMATE1 = "climate.testmodule_climate1"
async def test_setup_lcn_climate(
@@ -56,7 +60,7 @@ async def test_set_hvac_mode_heat(hass: HomeAssistant, entry: MockConfigEntry) -
DOMAIN_CLIMATE,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: "climate.testmodule_climate1",
ATTR_ENTITY_ID: CLIMATE_CLIMATE1,
ATTR_HVAC_MODE: HVACMode.OFF,
},
blocking=True,
@@ -69,7 +73,7 @@ async def test_set_hvac_mode_heat(hass: HomeAssistant, entry: MockConfigEntry) -
DOMAIN_CLIMATE,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: "climate.testmodule_climate1",
ATTR_ENTITY_ID: CLIMATE_CLIMATE1,
ATTR_HVAC_MODE: HVACMode.HEAT,
},
blocking=True,
@@ -77,7 +81,7 @@ async def test_set_hvac_mode_heat(hass: HomeAssistant, entry: MockConfigEntry) -
lock_regulator.assert_awaited_with(0, False)
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state != HVACMode.HEAT
@@ -89,7 +93,7 @@ async def test_set_hvac_mode_heat(hass: HomeAssistant, entry: MockConfigEntry) -
DOMAIN_CLIMATE,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: "climate.testmodule_climate1",
ATTR_ENTITY_ID: CLIMATE_CLIMATE1,
ATTR_HVAC_MODE: HVACMode.HEAT,
},
blocking=True,
@@ -97,7 +101,7 @@ async def test_set_hvac_mode_heat(hass: HomeAssistant, entry: MockConfigEntry) -
lock_regulator.assert_awaited_with(0, False)
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state == HVACMode.HEAT
@@ -107,7 +111,7 @@ async def test_set_hvac_mode_off(hass: HomeAssistant, entry: MockConfigEntry) ->
await init_integration(hass, entry)
with patch.object(MockDeviceConnection, "lock_regulator") as lock_regulator:
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
state.state = HVACMode.HEAT
# command failed
@@ -117,7 +121,7 @@ async def test_set_hvac_mode_off(hass: HomeAssistant, entry: MockConfigEntry) ->
DOMAIN_CLIMATE,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: "climate.testmodule_climate1",
ATTR_ENTITY_ID: CLIMATE_CLIMATE1,
ATTR_HVAC_MODE: HVACMode.OFF,
},
blocking=True,
@@ -125,7 +129,7 @@ async def test_set_hvac_mode_off(hass: HomeAssistant, entry: MockConfigEntry) ->
lock_regulator.assert_awaited_with(0, True, -1)
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state != HVACMode.OFF
@@ -137,7 +141,7 @@ async def test_set_hvac_mode_off(hass: HomeAssistant, entry: MockConfigEntry) ->
DOMAIN_CLIMATE,
SERVICE_SET_HVAC_MODE,
{
ATTR_ENTITY_ID: "climate.testmodule_climate1",
ATTR_ENTITY_ID: CLIMATE_CLIMATE1,
ATTR_HVAC_MODE: HVACMode.OFF,
},
blocking=True,
@@ -145,7 +149,7 @@ async def test_set_hvac_mode_off(hass: HomeAssistant, entry: MockConfigEntry) ->
lock_regulator.assert_awaited_with(0, True, -1)
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state == HVACMode.OFF
@@ -155,7 +159,7 @@ async def test_set_temperature(hass: HomeAssistant, entry: MockConfigEntry) -> N
await init_integration(hass, entry)
with patch.object(MockDeviceConnection, "var_abs") as var_abs:
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
state.state = HVACMode.HEAT
# wrong temperature set via service call with high/low attributes
@@ -166,7 +170,7 @@ async def test_set_temperature(hass: HomeAssistant, entry: MockConfigEntry) -> N
DOMAIN_CLIMATE,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: "climate.testmodule_climate1",
ATTR_ENTITY_ID: CLIMATE_CLIMATE1,
ATTR_TARGET_TEMP_LOW: 24.5,
ATTR_TARGET_TEMP_HIGH: 25.5,
},
@@ -182,13 +186,13 @@ async def test_set_temperature(hass: HomeAssistant, entry: MockConfigEntry) -> N
await hass.services.async_call(
DOMAIN_CLIMATE,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: "climate.testmodule_climate1", ATTR_TEMPERATURE: 25.5},
{ATTR_ENTITY_ID: CLIMATE_CLIMATE1, ATTR_TEMPERATURE: 25.5},
blocking=True,
)
var_abs.assert_awaited_with(Var.R1VARSETPOINT, 25.5, VarUnit.CELSIUS)
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.attributes[ATTR_TEMPERATURE] != 25.5
@@ -199,13 +203,13 @@ async def test_set_temperature(hass: HomeAssistant, entry: MockConfigEntry) -> N
await hass.services.async_call(
DOMAIN_CLIMATE,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: "climate.testmodule_climate1", ATTR_TEMPERATURE: 25.5},
{ATTR_ENTITY_ID: CLIMATE_CLIMATE1, ATTR_TEMPERATURE: 25.5},
blocking=True,
)
var_abs.assert_awaited_with(Var.R1VARSETPOINT, 25.5, VarUnit.CELSIUS)
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.attributes[ATTR_TEMPERATURE] == 25.5
@@ -226,7 +230,7 @@ async def test_pushed_current_temperature_status_change(
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 25.5
@@ -249,7 +253,7 @@ async def test_pushed_setpoint_status_change(
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state == HVACMode.HEAT
assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None
@@ -272,7 +276,7 @@ async def test_pushed_lock_status_change(
await device_connection.async_process_input(inp)
await hass.async_block_till_done()
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state == HVACMode.OFF
assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None
@@ -291,11 +295,50 @@ async def test_pushed_wrong_input(
await device_connection.async_process_input(Unknown("input"))
await hass.async_block_till_done()
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None
assert state.attributes[ATTR_TEMPERATURE] is None
async def test_availability(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, entry: MockConfigEntry
) -> None:
"""Test the availability of climate entity."""
await init_integration(hass, entry)
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state != STATE_UNAVAILABLE
# no response from device -> unavailable
with patch.object(
MockDeviceConnection, "request_status_variable", return_value=None
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state == STATE_UNAVAILABLE
# response from device -> available
with patch.object(
MockDeviceConnection,
"request_status_variable",
return_value=ModStatusVar(
LcnAddr(0, 7, False), Var.R1VARSETPOINT, VarValue.from_celsius(25.5)
),
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(CLIMATE_CLIMATE1)
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_unload_config_entry(
hass: HomeAssistant,
entry: MockConfigEntry,
@@ -304,5 +347,5 @@ async def test_unload_config_entry(
await init_integration(hass, entry)
await hass.config_entries.async_unload(entry.entry_id)
state = hass.states.get("climate.testmodule_climate1")
state = hass.states.get(CLIMATE_CLIMATE1)
assert state.state == STATE_UNAVAILABLE
+76 -2
View File
@@ -1,7 +1,8 @@
"""Test for the LCN cover platform."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import (
ModStatusMotorPositionBS4,
ModStatusMotorPositionModule,
@@ -19,6 +20,7 @@ from homeassistant.components.cover import (
DOMAIN as DOMAIN_COVER,
CoverState,
)
from homeassistant.components.lcn.cover import SCAN_INTERVAL
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.const import (
ATTR_ENTITY_ID,
@@ -34,7 +36,7 @@ from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, MockDeviceConnection, init_integration
from tests.common import snapshot_platform
from tests.common import async_fire_time_changed, snapshot_platform
COVER_OUTPUTS = "cover.testmodule_cover_outputs"
COVER_RELAYS = "cover.testmodule_cover_relays"
@@ -522,6 +524,78 @@ async def test_pushed_relays_status_change(
assert state.attributes[ATTR_CURRENT_POSITION] == 75
@pytest.mark.parametrize(
("entity_id", "request_methods", "return_values"),
[
(
COVER_OUTPUTS,
("request_status_output",),
(ModStatusOutput(LcnAddr(0, 7, False), 0, 100),),
),
(
COVER_RELAYS,
("request_status_relays",),
(ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),),
),
(
COVER_RELAYS_BS4,
("request_status_relays", "request_status_motor_position"),
(
ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),
ModStatusMotorPositionBS4(LcnAddr(0, 7, False), 1, 50),
),
),
],
)
async def test_availability(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
entry: MockConfigEntry,
entity_id: str,
request_methods: list[str],
return_values: list[ModStatusOutput | ModStatusRelays],
) -> None:
"""Test the availability of cover entity."""
await init_integration(hass, entry)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
# no response from device -> unavailable
with patch.multiple(
MockDeviceConnection,
**{
request_method: AsyncMock(return_value=None)
for request_method in request_methods
},
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
with patch.multiple(
MockDeviceConnection,
**{
request_method: AsyncMock(return_value=return_value)
for request_method, return_value in zip(
request_methods, return_values, strict=True
)
},
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the cover is removed when the config entry is unloaded."""
await init_integration(hass, entry)
+59 -1
View File
@@ -2,12 +2,15 @@
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import ModStatusOutput, ModStatusRelays
from pypck.lcn_addr import LcnAddr
from pypck.lcn_defs import RelayStateModifier
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.components.lcn.light import SCAN_INTERVAL
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_TRANSITION,
@@ -27,7 +30,7 @@ from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, MockDeviceConnection, init_integration
from tests.common import snapshot_platform
from tests.common import async_fire_time_changed, snapshot_platform
LIGHT_OUTPUT1 = "light.testmodule_light_output1"
LIGHT_OUTPUT2 = "light.testmodule_light_output2"
@@ -310,6 +313,61 @@ async def test_pushed_relay_status_change(
assert state.state == STATE_OFF
@pytest.mark.parametrize(
("entity_id", "request_method", "return_value"),
[
(
LIGHT_OUTPUT1,
"request_status_output",
ModStatusOutput(LcnAddr(0, 7, False), 0, 0),
),
(
LIGHT_RELAY1,
"request_status_relays",
ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),
),
],
)
async def test_availability(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
entry: MockConfigEntry,
entity_id: str,
request_method: str,
return_value: ModStatusOutput | ModStatusRelays,
) -> None:
"""Test the availability of light entity."""
await init_integration(hass, entry)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
# no response from device -> unavailable
with patch.object(MockDeviceConnection, request_method, return_value=None):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
# response from device -> available
with patch.object(
MockDeviceConnection,
request_method,
return_value=return_value,
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the light is removed when the config entry is unloaded."""
await init_integration(hass, entry)
+62 -2
View File
@@ -2,19 +2,22 @@
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import ModStatusLedsAndLogicOps, ModStatusVar
from pypck.lcn_addr import LcnAddr
from pypck.lcn_defs import LedStatus, LogicOpStatus, Var, VarValue
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.components.lcn.sensor import SCAN_INTERVAL
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, init_integration
from .conftest import MockConfigEntry, MockDeviceConnection, init_integration
from tests.common import snapshot_platform
from tests.common import async_fire_time_changed, snapshot_platform
SENSOR_VAR1 = "sensor.testmodule_sensor_var1"
SENSOR_SETPOINT1 = "sensor.testmodule_sensor_setpoint1"
@@ -92,6 +95,63 @@ async def test_pushed_ledlogicop_status_change(
assert state.state == "all"
@pytest.mark.parametrize(
("entity_id", "request_method", "return_value"),
[
(
SENSOR_VAR1,
"request_status_variable",
ModStatusVar(LcnAddr(0, 7, False), Var.VAR1, VarValue.from_celsius(20)),
),
(
SENSOR_LED6,
"request_status_led_and_logic_ops",
ModStatusLedsAndLogicOps(
LcnAddr(0, 7, False), [LedStatus.OFF] * 12, [LogicOpStatus.NONE] * 4
),
),
],
)
async def test_availability(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
entry: MockConfigEntry,
entity_id: str,
request_method: str,
return_value: ModStatusVar | ModStatusLedsAndLogicOps,
) -> None:
"""Test the availability of sensor entity."""
await init_integration(hass, entry)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
# no response from device -> unavailable
with patch.object(MockDeviceConnection, request_method, return_value=None):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
# response from device -> available
with patch.object(
MockDeviceConnection,
request_method,
return_value=return_value,
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the sensor is removed when the config entry is unloaded."""
await init_integration(hass, entry)
+69 -1
View File
@@ -2,6 +2,7 @@
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import (
ModStatusKeyLocks,
ModStatusOutput,
@@ -10,9 +11,11 @@ from pypck.inputs import (
)
from pypck.lcn_addr import LcnAddr
from pypck.lcn_defs import KeyLockStateModifier, RelayStateModifier, Var, VarValue
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.components.lcn.switch import SCAN_INTERVAL
from homeassistant.components.switch import DOMAIN as DOMAIN_SWITCH
from homeassistant.const import (
ATTR_ENTITY_ID,
@@ -28,7 +31,7 @@ from homeassistant.helpers import entity_registry as er
from .conftest import MockConfigEntry, MockDeviceConnection, init_integration
from tests.common import snapshot_platform
from tests.common import async_fire_time_changed, snapshot_platform
SWITCH_OUTPUT1 = "switch.testmodule_switch_output1"
SWITCH_OUTPUT2 = "switch.testmodule_switch_output2"
@@ -504,6 +507,71 @@ async def test_pushed_keylock_status_change(
assert state.state == STATE_OFF
@pytest.mark.parametrize(
("entity_id", "request_method", "return_value"),
[
(
SWITCH_OUTPUT1,
"request_status_output",
ModStatusOutput(LcnAddr(0, 7, False), 0, 0),
),
(
SWITCH_RELAY1,
"request_status_relays",
ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),
),
(
SWITCH_REGULATOR1,
"request_status_variable",
ModStatusVar(LcnAddr(0, 7, False), Var.R1VARSETPOINT, VarValue(0x8000)),
),
(
SWITCH_KEYLOCKK1,
"request_status_locked_keys",
ModStatusKeyLocks(LcnAddr(0, 7, False), [[False] * 8 for i in range(4)]),
),
],
)
async def test_availability(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
entry: MockConfigEntry,
entity_id: str,
request_method: str,
return_value: ModStatusOutput | ModStatusRelays,
) -> None:
"""Test the availability of switch entity."""
await init_integration(hass, entry)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
# no response from device -> unavailable
with patch.object(MockDeviceConnection, request_method, return_value=None):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_UNAVAILABLE
# response from device -> available
with patch.object(
MockDeviceConnection,
request_method,
return_value=return_value,
):
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Test the switch is removed when the config entry is unloaded."""
await init_integration(hass, entry)