Handle state unknown if last state is missing in Shelly (#86813)

Shelly - handle state unknown if last state is missing
This commit is contained in:
Shay Levy 2023-01-28 00:34:56 +02:00 committed by GitHub
parent 50c2992f36
commit 803cd8d9a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 170 additions and 38 deletions

View File

@ -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

View File

@ -75,7 +75,6 @@ async def validate_input(
options,
)
await rpc_device.shutdown()
assert rpc_device.shelly
return {
"title": rpc_device.name,

View File

@ -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()

View File

@ -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)

View File

@ -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."""

View File

@ -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

View File

@ -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

View File

@ -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":

View File

@ -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

View File

@ -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)

View File

@ -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"