Skip to content

Commit

Permalink
Fix shelly available check when device is not initialized (home-assis…
Browse files Browse the repository at this point in the history
…tant#124182)

* Fix shelly available check when device is not initialized

available needs to check for device.initialized or if the device
is sleepy as calls to status will raise NotInitialized which results
in many unretrieved exceptions while writing state

fixes
```
2024-08-18 09:33:03.757 ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved (None)
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 258, in _handle_refresh_interval
    await self._async_refresh(log_failures=True, scheduled=True)
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 453, in _async_refresh
    self.async_update_listeners()
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 168, in async_update_listeners
    update_callback()
  File "/config/custom_components/shelly/entity.py", line 374, in _update_callback
    self.async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1005, in async_write_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1130, in _async_write_ha_state
    self.__async_calculate_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1067, in __async_calculate_state
    state = self._stringify_state(available)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1011, in _stringify_state
    if (state := self.state) is None:
                 ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/binary_sensor/__init__.py", line 293, in state
    if (is_on := self.is_on) is None:
                 ^^^^^^^^^^
  File "/config/custom_components/shelly/binary_sensor.py", line 331, in is_on
    return bool(self.attribute_value)
                ^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/shelly/entity.py", line 545, in attribute_value
    self._last_value = self.sub_status
                       ^^^^^^^^^^^^^^^
  File "/config/custom_components/shelly/entity.py", line 534, in sub_status
    return self.status[self.entity_description.sub_key]
           ^^^^^^^^^^^
  File "/config/custom_components/shelly/entity.py", line 364, in status
    return cast(dict, self.coordinator.device.status[self.key])
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/aioshelly/rpc_device/device.py", line 390, in status
    raise NotInitialized
aioshelly.exceptions.NotInitialized
```

* tweak

* cover

* fix

* cover

* fixes
  • Loading branch information
bdraco authored Aug 22, 2024
1 parent 1bc0ec2 commit df82567
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 1 deletion.
3 changes: 3 additions & 0 deletions homeassistant/components/shelly/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ def _async_handle_update(
self.entry.async_create_background_task(
self.hass, self._async_connected(), "rpc device init", eager_start=True
)
# Make sure entities are marked available
self.async_set_updated_data(None)
elif update_type is RpcUpdateType.DISCONNECTED:
self.entry.async_create_background_task(
Expand All @@ -690,6 +691,8 @@ def _async_handle_update(
"rpc device disconnected",
eager_start=True,
)
# Make sure entities are marked as unavailable
self.async_set_updated_data(None)
elif update_type is RpcUpdateType.STATUS:
self.async_set_updated_data(None)
if self.sleep_period:
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/shelly/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,14 @@ def __init__(self, coordinator: ShellyRpcCoordinator, key: str) -> None:
self._attr_unique_id = f"{coordinator.mac}-{key}"
self._attr_name = get_rpc_entity_name(coordinator.device, key)

@property
def available(self) -> bool:
"""Check if device is available and initialized or sleepy."""
coordinator = self.coordinator
return super().available and (
coordinator.device.initialized or bool(coordinator.sleep_period)
)

@property
def status(self) -> dict:
"""Device status by entity key."""
Expand Down
34 changes: 33 additions & 1 deletion tests/components/shelly/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
register_entity,
)

from tests.common import mock_restore_cache_with_extra_data
from tests.common import async_fire_time_changed, mock_restore_cache_with_extra_data

RELAY_BLOCK_ID = 0
SENSOR_BLOCK_ID = 3
Expand Down Expand Up @@ -1279,3 +1279,35 @@ async def test_rpc_rgbw_sensors(
entry = entity_registry.async_get(entity_id)
assert entry
assert entry.unique_id == f"123456789ABC-{light_type}:0-temperature_{light_type}"


async def test_rpc_device_sensor_goes_unavailable_on_disconnect(
hass: HomeAssistant,
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test RPC device with sensor goes unavailable on disconnect."""
await init_integration(hass, 2)
temp_sensor_state = hass.states.get("sensor.test_name_temperature")
assert temp_sensor_state is not None
assert temp_sensor_state.state != STATE_UNAVAILABLE
monkeypatch.setattr(mock_rpc_device, "connected", False)
monkeypatch.setattr(mock_rpc_device, "initialized", False)
mock_rpc_device.mock_disconnected()
await hass.async_block_till_done()
temp_sensor_state = hass.states.get("sensor.test_name_temperature")
assert temp_sensor_state.state == STATE_UNAVAILABLE

freezer.tick(60)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert "NotInitialized" not in caplog.text

monkeypatch.setattr(mock_rpc_device, "connected", True)
monkeypatch.setattr(mock_rpc_device, "initialized", True)
mock_rpc_device.mock_initialized()
await hass.async_block_till_done()
temp_sensor_state = hass.states.get("sensor.test_name_temperature")
assert temp_sensor_state.state != STATE_UNAVAILABLE

0 comments on commit df82567

Please sign in to comment.