mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
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:
parent
50c2992f36
commit
803cd8d9a3
@ -296,12 +296,15 @@ class BlockSleepingBinarySensor(ShellySleepingBlockAttributeEntity, BinarySensor
|
|||||||
entity_description: BlockBinarySensorDescription
|
entity_description: BlockBinarySensorDescription
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if sensor state is on."""
|
"""Return true if sensor state is on."""
|
||||||
if self.block is not None:
|
if self.block is not None:
|
||||||
return bool(self.attribute_value)
|
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):
|
class RpcSleepingBinarySensor(ShellySleepingRpcAttributeEntity, BinarySensorEntity):
|
||||||
@ -310,9 +313,12 @@ class RpcSleepingBinarySensor(ShellySleepingRpcAttributeEntity, BinarySensorEnti
|
|||||||
entity_description: RpcBinarySensorDescription
|
entity_description: RpcBinarySensorDescription
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if RPC sensor state is on."""
|
"""Return true if RPC sensor state is on."""
|
||||||
if self.coordinator.device.initialized:
|
if self.coordinator.device.initialized:
|
||||||
return bool(self.attribute_value)
|
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
|
||||||
|
@ -75,7 +75,6 @@ async def validate_input(
|
|||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
await rpc_device.shutdown()
|
await rpc_device.shutdown()
|
||||||
assert rpc_device.shelly
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": rpc_device.name,
|
"title": rpc_device.name,
|
||||||
|
@ -9,8 +9,7 @@ from aioshelly.block_device import Block
|
|||||||
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
|
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT
|
from homeassistant.core import HomeAssistant, State, callback
|
||||||
from homeassistant.core import HomeAssistant, callback
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
||||||
@ -566,8 +565,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sleeping sensor."""
|
"""Initialize the sleeping sensor."""
|
||||||
self.sensors = sensors
|
self.sensors = sensors
|
||||||
self.last_state: StateType = None
|
self.last_state: State | None = None
|
||||||
self.last_unit: str | None = None
|
|
||||||
self.coordinator = coordinator
|
self.coordinator = coordinator
|
||||||
self.attribute = attribute
|
self.attribute = attribute
|
||||||
self.block: Block | None = block # type: ignore[assignment]
|
self.block: Block | None = block # type: ignore[assignment]
|
||||||
@ -592,12 +590,7 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti
|
|||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Handle entity which will be added."""
|
"""Handle entity which will be added."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
self.last_state = await self.async_get_last_state()
|
||||||
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)
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_callback(self) -> None:
|
def _update_callback(self) -> None:
|
||||||
@ -649,8 +642,7 @@ class ShellySleepingRpcAttributeEntity(ShellyRpcAttributeEntity, RestoreEntity):
|
|||||||
entry: RegistryEntry | None = None,
|
entry: RegistryEntry | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sleeping sensor."""
|
"""Initialize the sleeping sensor."""
|
||||||
self.last_state: StateType = None
|
self.last_state: State | None = None
|
||||||
self.last_unit: str | None = None
|
|
||||||
self.coordinator = coordinator
|
self.coordinator = coordinator
|
||||||
self.key = key
|
self.key = key
|
||||||
self.attribute = attribute
|
self.attribute = attribute
|
||||||
@ -675,9 +667,4 @@ class ShellySleepingRpcAttributeEntity(ShellyRpcAttributeEntity, RestoreEntity):
|
|||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Handle entity which will be added."""
|
"""Handle entity which will be added."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
self.last_state = await self.async_get_last_state()
|
||||||
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)
|
|
||||||
|
@ -81,7 +81,6 @@ def async_setup_block_entry(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
blocks.append(block)
|
blocks.append(block)
|
||||||
assert coordinator.device.shelly
|
|
||||||
unique_id = f"{coordinator.mac}-{block.type}_{block.channel}"
|
unique_id = f"{coordinator.mac}-{block.type}_{block.channel}"
|
||||||
async_remove_shelly_entity(hass, "switch", unique_id)
|
async_remove_shelly_entity(hass, "switch", unique_id)
|
||||||
|
|
||||||
|
@ -93,12 +93,15 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, NumberEntity):
|
|||||||
entity_description: BlockNumberDescription
|
entity_description: BlockNumberDescription
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> float:
|
def native_value(self) -> float | None:
|
||||||
"""Return value of number."""
|
"""Return value of number."""
|
||||||
if self.block is not None:
|
if self.block is not None:
|
||||||
return cast(float, self.attribute_value)
|
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:
|
async def async_set_native_value(self, value: float) -> None:
|
||||||
"""Set value."""
|
"""Set value."""
|
||||||
|
@ -15,6 +15,7 @@ from homeassistant.components.sensor import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
DEGREE,
|
DEGREE,
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
@ -669,7 +670,10 @@ class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
|
|||||||
if self.block is not None:
|
if self.block is not None:
|
||||||
return self.attribute_value
|
return self.attribute_value
|
||||||
|
|
||||||
return self.last_state
|
if self.last_state is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.last_state.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self) -> str | None:
|
def native_unit_of_measurement(self) -> str | None:
|
||||||
@ -677,7 +681,10 @@ class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
|
|||||||
if self.block is not None:
|
if self.block is not None:
|
||||||
return self.entity_description.native_unit_of_measurement
|
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):
|
class RpcSleepingSensor(ShellySleepingRpcAttributeEntity, SensorEntity):
|
||||||
@ -691,12 +698,12 @@ class RpcSleepingSensor(ShellySleepingRpcAttributeEntity, SensorEntity):
|
|||||||
if self.coordinator.device.initialized:
|
if self.coordinator.device.initialized:
|
||||||
return self.attribute_value
|
return self.attribute_value
|
||||||
|
|
||||||
return self.last_state
|
if self.last_state is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.last_state.state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self) -> str | None:
|
def native_unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit of measurement of the sensor, if any."""
|
"""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.entity_description.native_unit_of_measurement
|
||||||
|
|
||||||
return self.last_unit
|
|
||||||
|
@ -221,7 +221,6 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity):
|
|||||||
@property
|
@property
|
||||||
def installed_version(self) -> str | None:
|
def installed_version(self) -> str | None:
|
||||||
"""Version currently in use."""
|
"""Version currently in use."""
|
||||||
assert self.coordinator.device.shelly
|
|
||||||
return cast(str, self.coordinator.device.shelly["ver"])
|
return cast(str, self.coordinator.device.shelly["ver"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -51,8 +51,6 @@ def async_remove_shelly_entity(
|
|||||||
|
|
||||||
def get_number_of_channels(device: BlockDevice, block: Block) -> int:
|
def get_number_of_channels(device: BlockDevice, block: Block) -> int:
|
||||||
"""Get number of channels for block type."""
|
"""Get number of channels for block type."""
|
||||||
assert isinstance(device.shelly, dict)
|
|
||||||
|
|
||||||
channels = None
|
channels = None
|
||||||
|
|
||||||
if block.type == "input":
|
if block.type == "input":
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||||
from homeassistant.components.shelly.const import SLEEP_PERIOD_MULTIPLIER
|
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.core import State
|
||||||
from homeassistant.helpers.entity_registry import async_get
|
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
|
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:
|
async def test_rpc_binary_sensor(hass, mock_rpc_device, monkeypatch) -> None:
|
||||||
"""Test RPC binary sensor."""
|
"""Test RPC binary sensor."""
|
||||||
entity_id = f"{BINARY_SENSOR_DOMAIN}.test_cover_0_overpowering"
|
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()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get(entity_id).state == STATE_OFF
|
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
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.components.number import (
|
|||||||
)
|
)
|
||||||
from homeassistant.components.shelly.const import DOMAIN
|
from homeassistant.components.shelly.const import DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
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.core import State
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
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"
|
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):
|
async def test_block_number_set_value(hass, mock_block_device):
|
||||||
"""Test block device number set value."""
|
"""Test block device number set value."""
|
||||||
await init_integration(hass, 1, sleep_period=1000)
|
await init_integration(hass, 1, sleep_period=1000)
|
||||||
|
@ -95,6 +95,29 @@ async def test_block_restored_sleeping_sensor(
|
|||||||
assert hass.states.get(entity_id).state == "22.1"
|
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):
|
async def test_block_sensor_error(hass, mock_block_device, monkeypatch):
|
||||||
"""Test block sensor unavailable on sensor error."""
|
"""Test block sensor unavailable on sensor error."""
|
||||||
entity_id = f"{SENSOR_DOMAIN}.test_name_battery"
|
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()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get(entity_id).state == "22.9"
|
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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user