Skip to content

Commit dbcde54

Browse files
authored
Update Shelly coordinator coverage to 100% (home-assistant#157380)
1 parent 988355e commit dbcde54

File tree

3 files changed

+101
-16
lines changed

3 files changed

+101
-16
lines changed

homeassistant/components/shelly/coordinator.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
get_rpc_device_wakeup_period,
8080
get_rpc_ws_url,
8181
get_shelly_model_name,
82+
is_rpc_ble_scanner_supported,
8283
update_device_fw_info,
8384
)
8485

@@ -726,6 +727,7 @@ async def _async_connected(self) -> None:
726727
"""Handle device connected."""
727728
async with self._connection_lock:
728729
if self.connected: # Already connected
730+
LOGGER.debug("Device %s already connected", self.name)
729731
return
730732
self.connected = True
731733
try:
@@ -743,10 +745,7 @@ async def _async_run_connected_events(self) -> None:
743745
is updated.
744746
"""
745747
if not self.sleep_period:
746-
if (
747-
self.config_entry.runtime_data.rpc_supports_scripts
748-
and not self.config_entry.runtime_data.rpc_zigbee_firmware
749-
):
748+
if is_rpc_ble_scanner_supported(self.config_entry):
750749
await self._async_connect_ble_scanner()
751750
else:
752751
await self._async_setup_outbound_websocket()
@@ -776,6 +775,10 @@ async def _async_connect_ble_scanner(self) -> None:
776775
if await async_ensure_ble_enabled(self.device):
777776
# BLE enable required a reboot, don't bother connecting
778777
# the scanner since it will be disconnected anyway
778+
LOGGER.debug(
779+
"Device %s BLE enable required a reboot, skipping scanner connect",
780+
self.name,
781+
)
779782
return
780783
assert self.device_id is not None
781784
self._disconnected_callbacks.append(
@@ -844,21 +847,14 @@ async def shutdown(self) -> None:
844847
"""Shutdown the coordinator."""
845848
if self.device.connected:
846849
try:
847-
if not self.sleep_period:
850+
if not self.sleep_period and is_rpc_ble_scanner_supported(
851+
self.config_entry
852+
):
848853
await async_stop_scanner(self.device)
849854
await super().shutdown()
850855
except InvalidAuthError:
851856
self.config_entry.async_start_reauth(self.hass)
852857
return
853-
except RpcCallError as err:
854-
# Ignore 404 (No handler for) error
855-
if err.code != 404:
856-
LOGGER.debug(
857-
"Error during shutdown for device %s: %s",
858-
self.name,
859-
err.message,
860-
)
861-
return
862858
except DeviceConnectionError as err:
863859
# If the device is restarting or has gone offline before
864860
# the ping/pong timeout happens, the shutdown command

homeassistant/components/shelly/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,3 +994,11 @@ def async_migrate_rpc_virtual_components_unique_ids(
994994
}
995995

996996
return None
997+
998+
999+
def is_rpc_ble_scanner_supported(entry: ConfigEntry) -> bool:
1000+
"""Return true if BLE scanner is supported."""
1001+
return (
1002+
entry.runtime_data.rpc_supports_scripts
1003+
and not entry.runtime_data.rpc_zigbee_firmware
1004+
)

tests/components/shelly/test_coordinator.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import timedelta
44
from unittest.mock import AsyncMock, Mock, call, patch
55

6-
from aioshelly.const import MODEL_BULB, MODEL_BUTTON1
6+
from aioshelly.const import MODEL_2PM_G3, MODEL_BULB, MODEL_BUTTON1
77
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
88
from freezegun.api import FrozenDateTimeFactory
99
import pytest
@@ -29,6 +29,8 @@
2929
from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE
3030
from homeassistant.core import Event, HomeAssistant, State
3131
from homeassistant.helpers import device_registry as dr, issue_registry as ir
32+
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceRegistry
33+
from homeassistant.helpers.entity_registry import EntityRegistry
3234

3335
from . import (
3436
MOCK_MAC,
@@ -40,7 +42,11 @@
4042
register_entity,
4143
)
4244

43-
from tests.common import async_fire_time_changed, mock_restore_cache
45+
from tests.common import (
46+
async_fire_time_changed,
47+
async_load_json_object_fixture,
48+
mock_restore_cache,
49+
)
4450

4551
RELAY_BLOCK_ID = 0
4652
LIGHT_BLOCK_ID = 2
@@ -927,6 +933,7 @@ async def test_rpc_runs_connected_events_when_initialized(
927933
hass: HomeAssistant,
928934
mock_rpc_device: Mock,
929935
monkeypatch: pytest.MonkeyPatch,
936+
caplog: pytest.LogCaptureFixture,
930937
supports_scripts: bool,
931938
zigbee_firmware: bool,
932939
result: bool,
@@ -950,6 +957,13 @@ async def test_rpc_runs_connected_events_when_initialized(
950957
# BLE script list is called during connected events if device supports scripts
951958
# and Zigbee is disabled
952959
assert bool(call.script_list() in mock_rpc_device.mock_calls) == result
960+
assert "Device Test name already connected" not in caplog.text
961+
962+
# Mock initialized event after already initialized
963+
caplog.clear()
964+
mock_rpc_device.mock_initialized()
965+
await hass.async_block_till_done()
966+
assert "Device Test name already connected" in caplog.text
953967

954968

955969
async def test_rpc_sleeping_device_unload_ignore_ble_scanner(
@@ -1139,3 +1153,70 @@ async def test_xmod_model_lookup(
11391153
)
11401154
assert device
11411155
assert device.model == xmod_model
1156+
1157+
1158+
async def test_sub_device_area_from_main_device(
1159+
hass: HomeAssistant,
1160+
mock_rpc_device: Mock,
1161+
entity_registry: EntityRegistry,
1162+
device_registry: DeviceRegistry,
1163+
monkeypatch: pytest.MonkeyPatch,
1164+
) -> None:
1165+
"""Test Shelly sub-device area is set to main device area when created."""
1166+
device_fixture = await async_load_json_object_fixture(hass, "2pm_gen3.json", DOMAIN)
1167+
monkeypatch.setattr(mock_rpc_device, "shelly", device_fixture["shelly"])
1168+
monkeypatch.setattr(mock_rpc_device, "status", device_fixture["status"])
1169+
monkeypatch.setattr(mock_rpc_device, "config", device_fixture["config"])
1170+
1171+
config_entry = await init_integration(
1172+
hass, gen=3, model=MODEL_2PM_G3, skip_setup=True
1173+
)
1174+
1175+
# create main device and set area
1176+
device_entry = device_registry.async_get_or_create(
1177+
config_entry_id=config_entry.entry_id,
1178+
name="Test name",
1179+
connections={(CONNECTION_NETWORK_MAC, MOCK_MAC)},
1180+
identifiers={(DOMAIN, MOCK_MAC)},
1181+
suggested_area="living_room",
1182+
)
1183+
1184+
await hass.config_entries.async_setup(config_entry.entry_id)
1185+
await hass.async_block_till_done()
1186+
1187+
# verify sub-devices have the same area as main device
1188+
for relay_index in range(2):
1189+
entity_id = f"switch.test_name_switch_{relay_index}"
1190+
assert hass.states.get(entity_id) is not None
1191+
entry = entity_registry.async_get(entity_id)
1192+
assert entry
1193+
1194+
device_entry = device_registry.async_get(entry.device_id)
1195+
assert device_entry
1196+
assert device_entry.area_id == "living_room"
1197+
1198+
1199+
@pytest.mark.parametrize("restart_required", [True, False])
1200+
async def test_rpc_ble_scanner_enable_reboot(
1201+
hass: HomeAssistant,
1202+
mock_rpc_device,
1203+
monkeypatch: pytest.MonkeyPatch,
1204+
caplog: pytest.LogCaptureFixture,
1205+
restart_required: bool,
1206+
) -> None:
1207+
"""Test RPC BLE scanner enabling requires reboot."""
1208+
monkeypatch.setattr(
1209+
mock_rpc_device,
1210+
"ble_getconfig",
1211+
AsyncMock(return_value={"enable": False}),
1212+
)
1213+
monkeypatch.setattr(
1214+
mock_rpc_device,
1215+
"ble_setconfig",
1216+
AsyncMock(return_value={"restart_required": restart_required}),
1217+
)
1218+
await init_integration(
1219+
hass, 2, options={CONF_BLE_SCANNER_MODE: BLEScannerMode.ACTIVE}
1220+
)
1221+
assert bool("BLE enable required a reboot" in caplog.text) == restart_required
1222+
assert mock_rpc_device.trigger_reboot.call_count == int(restart_required)

0 commit comments

Comments
 (0)