diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 716303b7bda..301a74064a5 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -296,12 +296,15 @@ class BlockSleepingBinarySensor(ShellySleepingBlockAttributeEntity, BinarySensor entity_description: BlockBinarySensorDescription @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if sensor state is on.""" if self.block is not None: return bool(self.attribute_value) - return self.last_state == STATE_ON + if self.last_state is None: + return None + + return self.last_state.state == STATE_ON class RpcSleepingBinarySensor(ShellySleepingRpcAttributeEntity, BinarySensorEntity): @@ -310,9 +313,12 @@ class RpcSleepingBinarySensor(ShellySleepingRpcAttributeEntity, BinarySensorEnti entity_description: RpcBinarySensorDescription @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if RPC sensor state is on.""" if self.coordinator.device.initialized: return bool(self.attribute_value) - return self.last_state == STATE_ON + if self.last_state is None: + return None + + return self.last_state.state == STATE_ON diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index a6d7be3cd0c..43e9f9b8fe7 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -75,7 +75,6 @@ async def validate_input( options, ) await rpc_device.shutdown() - assert rpc_device.shelly return { "title": rpc_device.name, diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 4811334b285..a17f1a7d507 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -9,8 +9,7 @@ from aioshelly.block_device import Block from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant, State, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription @@ -566,8 +565,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti ) -> None: """Initialize the sleeping sensor.""" self.sensors = sensors - self.last_state: StateType = None - self.last_unit: str | None = None + self.last_state: State | None = None self.coordinator = coordinator self.attribute = attribute self.block: Block | None = block # type: ignore[assignment] @@ -592,12 +590,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" await super().async_added_to_hass() - - last_state = await self.async_get_last_state() - - if last_state is not None: - self.last_state = last_state.state - self.last_unit = last_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + self.last_state = await self.async_get_last_state() @callback def _update_callback(self) -> None: @@ -649,8 +642,7 @@ class ShellySleepingRpcAttributeEntity(ShellyRpcAttributeEntity, RestoreEntity): entry: RegistryEntry | None = None, ) -> None: """Initialize the sleeping sensor.""" - self.last_state: StateType = None - self.last_unit: str | None = None + self.last_state: State | None = None self.coordinator = coordinator self.key = key self.attribute = attribute @@ -675,9 +667,4 @@ class ShellySleepingRpcAttributeEntity(ShellyRpcAttributeEntity, RestoreEntity): async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" await super().async_added_to_hass() - - last_state = await self.async_get_last_state() - - if last_state is not None: - self.last_state = last_state.state - self.last_unit = last_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + self.last_state = await self.async_get_last_state() diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 18c1da8a795..0421a8990b1 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -81,7 +81,6 @@ def async_setup_block_entry( continue blocks.append(block) - assert coordinator.device.shelly unique_id = f"{coordinator.mac}-{block.type}_{block.channel}" async_remove_shelly_entity(hass, "switch", unique_id) diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index b7b8c3300d3..2726041db58 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -93,12 +93,15 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, NumberEntity): entity_description: BlockNumberDescription @property - def native_value(self) -> float: + def native_value(self) -> float | None: """Return value of number.""" if self.block is not None: return cast(float, self.attribute_value) - return cast(float, self.last_state) + if self.last_state is None: + return None + + return cast(float, self.last_state.state) async def async_set_native_value(self, value: float) -> None: """Set value.""" diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index c344e522716..67e2ad19008 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -15,6 +15,7 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_UNIT_OF_MEASUREMENT, CONCENTRATION_PARTS_PER_MILLION, DEGREE, LIGHT_LUX, @@ -669,7 +670,10 @@ class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): if self.block is not None: return self.attribute_value - return self.last_state + if self.last_state is None: + return None + + return self.last_state.state @property def native_unit_of_measurement(self) -> str | None: @@ -677,7 +681,10 @@ class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity): if self.block is not None: return self.entity_description.native_unit_of_measurement - return self.last_unit + if self.last_state is None: + return None + + return self.last_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) class RpcSleepingSensor(ShellySleepingRpcAttributeEntity, SensorEntity): @@ -691,12 +698,12 @@ class RpcSleepingSensor(ShellySleepingRpcAttributeEntity, SensorEntity): if self.coordinator.device.initialized: return self.attribute_value - return self.last_state + if self.last_state is None: + return None + + return self.last_state.state @property def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement of the sensor, if any.""" - if self.coordinator.device.initialized: - return self.entity_description.native_unit_of_measurement - - return self.last_unit + return self.entity_description.native_unit_of_measurement diff --git a/homeassistant/components/shelly/update.py b/homeassistant/components/shelly/update.py index 3f14fd45138..065b0469e39 100644 --- a/homeassistant/components/shelly/update.py +++ b/homeassistant/components/shelly/update.py @@ -221,7 +221,6 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): @property def installed_version(self) -> str | None: """Version currently in use.""" - assert self.coordinator.device.shelly return cast(str, self.coordinator.device.shelly["ver"]) @property diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 44a1802c9eb..95fbf94a83b 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -51,8 +51,6 @@ def async_remove_shelly_entity( def get_number_of_channels(device: BlockDevice, block: Block) -> int: """Get number of channels for block type.""" - assert isinstance(device.shelly, dict) - channels = None if block.type == "input": diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py index b39a395d11b..5330a7b42a4 100644 --- a/tests/components/shelly/test_binary_sensor.py +++ b/tests/components/shelly/test_binary_sensor.py @@ -3,7 +3,7 @@ from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.shelly.const import SLEEP_PERIOD_MULTIPLIER -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN from homeassistant.core import State from homeassistant.helpers.entity_registry import async_get @@ -134,6 +134,29 @@ async def test_block_restored_sleeping_binary_sensor( assert hass.states.get(entity_id).state == STATE_OFF +async def test_block_restored_sleeping_binary_sensor_no_last_state( + hass, mock_block_device, device_reg, monkeypatch +): + """Test block restored sleeping binary sensor missing last state.""" + entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True) + register_device(device_reg, entry) + entity_id = register_entity( + hass, BINARY_SENSOR_DOMAIN, "test_name_motion", "sensor_0-motion", entry + ) + monkeypatch.setattr(mock_block_device, "initialized", False) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNKNOWN + + # Make device online + monkeypatch.setattr(mock_block_device, "initialized", True) + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_OFF + + async def test_rpc_binary_sensor(hass, mock_rpc_device, monkeypatch) -> None: """Test RPC binary sensor.""" entity_id = f"{BINARY_SENSOR_DOMAIN}.test_cover_0_overpowering" @@ -212,3 +235,28 @@ async def test_rpc_restored_sleeping_binary_sensor( await hass.async_block_till_done() assert hass.states.get(entity_id).state == STATE_OFF + + +async def test_rpc_restored_sleeping_binary_sensor_no_last_state( + hass, mock_rpc_device, device_reg, monkeypatch +): + """Test RPC restored sleeping binary sensor missing last state.""" + entry = await init_integration(hass, 2, sleep_period=1000, skip_setup=True) + register_device(device_reg, entry) + entity_id = register_entity( + hass, BINARY_SENSOR_DOMAIN, "test_name_cloud", "cloud-cloud", entry + ) + + monkeypatch.setattr(mock_rpc_device, "initialized", False) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNKNOWN + + # Make device online + monkeypatch.setattr(mock_rpc_device, "initialized", True) + mock_rpc_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_OFF diff --git a/tests/components/shelly/test_number.py b/tests/components/shelly/test_number.py index 69b1105fef5..6229d62b5f4 100644 --- a/tests/components/shelly/test_number.py +++ b/tests/components/shelly/test_number.py @@ -11,7 +11,7 @@ from homeassistant.components.number import ( ) from homeassistant.components.shelly.const import DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import State from homeassistant.exceptions import HomeAssistantError @@ -74,6 +74,40 @@ async def test_block_restored_number(hass, mock_block_device, device_reg, monkey assert hass.states.get(entity_id).state == "50" +async def test_block_restored_number_no_last_state( + hass, mock_block_device, device_reg, monkeypatch +): + """Test block restored number missing last state.""" + entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True) + register_device(device_reg, entry) + capabilities = { + "min": 0, + "max": 100, + "step": 1, + "mode": "slider", + } + entity_id = register_entity( + hass, + NUMBER_DOMAIN, + "test_name_valve_position", + "device_0-valvePos", + entry, + capabilities, + ) + monkeypatch.setattr(mock_block_device, "initialized", False) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNKNOWN + + # Make device online + monkeypatch.setattr(mock_block_device, "initialized", True) + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "50" + + async def test_block_number_set_value(hass, mock_block_device): """Test block device number set value.""" await init_integration(hass, 1, sleep_period=1000) diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index d62682df5a9..5113a1208fb 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -95,6 +95,29 @@ async def test_block_restored_sleeping_sensor( assert hass.states.get(entity_id).state == "22.1" +async def test_block_restored_sleeping_sensor_no_last_state( + hass, mock_block_device, device_reg, monkeypatch +): + """Test block restored sleeping sensor missing last state.""" + entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True) + register_device(device_reg, entry) + entity_id = register_entity( + hass, SENSOR_DOMAIN, "test_name_temperature", "sensor_0-temp", entry + ) + monkeypatch.setattr(mock_block_device, "initialized", False) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNKNOWN + + # Make device online + monkeypatch.setattr(mock_block_device, "initialized", True) + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "22.1" + + async def test_block_sensor_error(hass, mock_block_device, monkeypatch): """Test block sensor unavailable on sensor error.""" entity_id = f"{SENSOR_DOMAIN}.test_name_battery" @@ -270,3 +293,32 @@ async def test_rpc_restored_sleeping_sensor( await hass.async_block_till_done() assert hass.states.get(entity_id).state == "22.9" + + +async def test_rpc_restored_sleeping_sensor_no_last_state( + hass, mock_rpc_device, device_reg, monkeypatch +): + """Test RPC restored sensor missing last state.""" + entry = await init_integration(hass, 2, sleep_period=1000, skip_setup=True) + register_device(device_reg, entry) + entity_id = register_entity( + hass, + SENSOR_DOMAIN, + "test_name_temperature", + "temperature:0-temperature_0", + entry, + ) + + monkeypatch.setattr(mock_rpc_device, "initialized", False) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNKNOWN + + # Make device online + monkeypatch.setattr(mock_rpc_device, "initialized", True) + mock_rpc_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "22.9"