mirror of
https://github.com/home-assistant/core.git
synced 2025-07-08 22:07:10 +00:00
Move Switcher handle_coordinator_update to base entity (#143738)
This commit is contained in:
parent
40752dcfb6
commit
868b8ad318
@ -6,14 +6,10 @@ from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
from aioswitcher.api import (
|
||||
DeviceState,
|
||||
SwitcherApi,
|
||||
SwitcherBaseResponse,
|
||||
ThermostatSwing,
|
||||
)
|
||||
from aioswitcher.api import SwitcherApi
|
||||
from aioswitcher.api.messages import SwitcherBaseResponse
|
||||
from aioswitcher.api.remotes import SwitcherBreezeRemote
|
||||
from aioswitcher.device import DeviceCategory
|
||||
from aioswitcher.device import DeviceCategory, DeviceState, ThermostatSwing
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
|
@ -26,7 +26,7 @@ from homeassistant.components.climate import (
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@ -117,20 +117,15 @@ class SwitcherClimateEntity(SwitcherEntity, ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
self._update_data(True)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._update_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _update_data(self, force_update: bool = False) -> None:
|
||||
def _update_data(self) -> None:
|
||||
"""Update data from device."""
|
||||
data = cast(SwitcherThermostat, self.coordinator.data)
|
||||
features = self._remote.modes_features[data.mode]
|
||||
|
||||
if data.target_temperature == 0 and not force_update:
|
||||
# Ignore empty update from device that was power cycled
|
||||
if data.target_temperature == 0 and self.target_temperature is not None:
|
||||
return
|
||||
|
||||
self._attr_current_temperature = data.temperature
|
||||
|
@ -6,7 +6,7 @@ from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any, Final
|
||||
|
||||
from aioswitcher.bridge import SwitcherBase
|
||||
from aioswitcher.device import SwitcherBase
|
||||
from aioswitcher.device.tools import validate_token
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -69,12 +69,6 @@ class SwitcherBaseCoverEntity(SwitcherEntity, CoverEntity):
|
||||
)
|
||||
_cover_id: int
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._update_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _update_data(self) -> None:
|
||||
"""Update data from device."""
|
||||
data = cast(SwitcherShutter, self.coordinator.data)
|
||||
|
@ -6,6 +6,7 @@ from typing import Any
|
||||
from aioswitcher.api import SwitcherApi
|
||||
from aioswitcher.api.messages import SwitcherBaseResponse
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@ -28,6 +29,15 @@ class SwitcherEntity(CoordinatorEntity[SwitcherDataUpdateCoordinator]):
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, coordinator.mac_address)}
|
||||
)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._update_data()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
def _update_data(self) -> None:
|
||||
"""Update data from device."""
|
||||
|
||||
async def _async_call_api(self, api: str, *args: Any, **kwargs: Any) -> None:
|
||||
"""Call Switcher API."""
|
||||
_LOGGER.debug("Calling api for %s, api: '%s', args: %s", self.name, api, args)
|
||||
|
@ -68,32 +68,28 @@ class SwitcherBaseLightEntity(SwitcherEntity, LightEntity):
|
||||
super().__init__(coordinator)
|
||||
self._light_id = light_id
|
||||
self.control_result: bool | None = None
|
||||
self._update_data()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""When device updates, clear control result that overrides state."""
|
||||
self.control_result = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
def _update_data(self) -> None:
|
||||
"""Update data from device."""
|
||||
if self.control_result is not None:
|
||||
return self.control_result
|
||||
self._attr_is_on = self.control_result
|
||||
self.control_result = None
|
||||
return
|
||||
|
||||
data = cast(SwitcherLight, self.coordinator.data)
|
||||
return bool(data.light[self._light_id] == DeviceState.ON)
|
||||
self._attr_is_on = bool(data.light[self._light_id] == DeviceState.ON)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the light on."""
|
||||
await self._async_call_api(API_SET_LIGHT, DeviceState.ON, self._light_id)
|
||||
self.control_result = True
|
||||
self._attr_is_on = self.control_result = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
await self._async_call_api(API_SET_LIGHT, DeviceState.OFF, self._light_id)
|
||||
self.control_result = False
|
||||
self._attr_is_on = self.control_result = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@ -109,8 +105,6 @@ class SwitcherSingleLightEntity(SwitcherBaseLightEntity):
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, light_id)
|
||||
|
||||
# Entity class attributes
|
||||
self._attr_unique_id = f"{coordinator.device_id}-{coordinator.mac_address}"
|
||||
|
||||
|
||||
@ -126,8 +120,6 @@ class SwitcherMultiLightEntity(SwitcherBaseLightEntity):
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, light_id)
|
||||
|
||||
# Entity class attributes
|
||||
self._attr_translation_placeholders = {"light_id": str(light_id + 1)}
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.device_id}-{coordinator.mac_address}-{light_id}"
|
||||
|
@ -111,34 +111,28 @@ class SwitcherBaseSwitchEntity(SwitcherEntity, SwitchEntity):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self.control_result: bool | None = None
|
||||
|
||||
# Entity class attributes
|
||||
self._attr_unique_id = f"{coordinator.device_id}-{coordinator.mac_address}"
|
||||
self._update_data()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""When device updates, clear control result that overrides state."""
|
||||
self.control_result = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
def _update_data(self) -> None:
|
||||
"""Update data from device."""
|
||||
if self.control_result is not None:
|
||||
return self.control_result
|
||||
self._attr_is_on = self.control_result
|
||||
self.control_result = None
|
||||
return
|
||||
|
||||
return bool(self.coordinator.data.device_state == DeviceState.ON)
|
||||
self._attr_is_on = bool(self.coordinator.data.device_state == DeviceState.ON)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
await self._async_call_api(API_CONTROL_DEVICE, Command.ON)
|
||||
self.control_result = True
|
||||
self._attr_is_on = self.control_result = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity off."""
|
||||
await self._async_call_api(API_CONTROL_DEVICE, Command.OFF)
|
||||
self.control_result = False
|
||||
self._attr_is_on = self.control_result = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_auto_off_service(self, auto_off: timedelta) -> None:
|
||||
@ -177,44 +171,45 @@ class SwitcherWaterHeaterSwitchEntity(SwitcherBaseSwitchEntity):
|
||||
async def async_turn_on_with_timer_service(self, timer_minutes: int) -> None:
|
||||
"""Use for turning device on with a timer service calls."""
|
||||
await self._async_call_api(API_CONTROL_DEVICE, Command.ON, timer_minutes)
|
||||
self.control_result = True
|
||||
self._attr_is_on = self.control_result = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class SwitcherShutterChildLockBaseSwitchEntity(SwitcherEntity, SwitchEntity):
|
||||
"""Representation of a Switcher shutter base switch entity."""
|
||||
"""Representation of a Switcher child lock base switch entity."""
|
||||
|
||||
_attr_device_class = SwitchDeviceClass.SWITCH
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_icon = "mdi:lock-open"
|
||||
_cover_id: int
|
||||
|
||||
def __init__(self, coordinator: SwitcherDataUpdateCoordinator) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: SwitcherDataUpdateCoordinator,
|
||||
cover_id: int,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._cover_id = cover_id
|
||||
self.control_result: bool | None = None
|
||||
self._update_data()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""When device updates, clear control result that overrides state."""
|
||||
self.control_result = None
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
def _update_data(self) -> None:
|
||||
"""Update data from device."""
|
||||
if self.control_result is not None:
|
||||
return self.control_result
|
||||
self._attr_is_on = self.control_result
|
||||
self.control_result = None
|
||||
return
|
||||
|
||||
data = cast(SwitcherShutter, self.coordinator.data)
|
||||
return bool(data.child_lock[self._cover_id] == ShutterChildLock.ON)
|
||||
self._attr_is_on = bool(data.child_lock[self._cover_id] == ShutterChildLock.ON)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
await self._async_call_api(
|
||||
API_SET_CHILD_LOCK, ShutterChildLock.ON, self._cover_id
|
||||
)
|
||||
self.control_result = True
|
||||
self._attr_is_on = self.control_result = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
@ -222,7 +217,7 @@ class SwitcherShutterChildLockBaseSwitchEntity(SwitcherEntity, SwitchEntity):
|
||||
await self._async_call_api(
|
||||
API_SET_CHILD_LOCK, ShutterChildLock.OFF, self._cover_id
|
||||
)
|
||||
self.control_result = False
|
||||
self._attr_is_on = self.control_result = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
@ -239,9 +234,7 @@ class SwitcherShutterChildLockSingleSwitchEntity(
|
||||
cover_id: int,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._cover_id = cover_id
|
||||
|
||||
super().__init__(coordinator, cover_id)
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.device_id}-{coordinator.mac_address}-child_lock"
|
||||
)
|
||||
@ -260,8 +253,7 @@ class SwitcherShutterChildLockMultiSwitchEntity(
|
||||
cover_id: int,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._cover_id = cover_id
|
||||
super().__init__(coordinator, cover_id)
|
||||
|
||||
self._attr_translation_placeholders = {"cover_id": str(cover_id + 1)}
|
||||
self._attr_unique_id = (
|
||||
|
@ -6,7 +6,8 @@ import asyncio
|
||||
import logging
|
||||
|
||||
from aioswitcher.api.remotes import SwitcherBreezeRemoteManager
|
||||
from aioswitcher.bridge import SwitcherBase, SwitcherBridge
|
||||
from aioswitcher.bridge import SwitcherBridge
|
||||
from aioswitcher.device import SwitcherBase
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import singleton
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
from unittest.mock import ANY, patch
|
||||
|
||||
from aioswitcher.api import DeviceState, SwitcherBaseResponse, ThermostatSwing
|
||||
from aioswitcher.api.messages import SwitcherBaseResponse
|
||||
from aioswitcher.device import DeviceState, ThermostatSwing
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import ANY, patch
|
||||
|
||||
from aioswitcher.api import SwitcherBaseResponse
|
||||
from aioswitcher.api.messages import SwitcherBaseResponse
|
||||
from aioswitcher.device import (
|
||||
DeviceState,
|
||||
ThermostatFanLevel,
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from aioswitcher.api import SwitcherBaseResponse
|
||||
from aioswitcher.api.messages import SwitcherBaseResponse
|
||||
from aioswitcher.device import ShutterDirection
|
||||
import pytest
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from aioswitcher.api import SwitcherBaseResponse
|
||||
from aioswitcher.api.messages import SwitcherBaseResponse
|
||||
from aioswitcher.device import DeviceState
|
||||
import pytest
|
||||
|
||||
@ -111,6 +111,44 @@ async def test_light(
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_bridge", [[DEVICE]], indirect=True)
|
||||
async def test_light_ignore_previous_async_state(
|
||||
hass: HomeAssistant, mock_bridge, mock_api
|
||||
) -> None:
|
||||
"""Test light ignores previous async state."""
|
||||
await init_integration(hass, USERNAME, TOKEN)
|
||||
assert mock_bridge
|
||||
|
||||
entity_id = f"{LIGHT_DOMAIN}.{slugify(DEVICE.name)}_light_1"
|
||||
|
||||
# Test initial state - light on
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
# Test turning off light
|
||||
with patch(
|
||||
"homeassistant.components.switcher_kis.entity.SwitcherApi.set_light"
|
||||
) as mock_set_light:
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
|
||||
# Push old state and makge sure it is ignored
|
||||
mock_bridge.mock_callbacks([DEVICE])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_api.call_count == 2
|
||||
mock_set_light.assert_called_once_with(DeviceState.OFF, 0)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Verify new state is not ignored
|
||||
mock_bridge.mock_callbacks([DEVICE])
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("device", "entity_id", "light_id", "device_state"),
|
||||
[
|
||||
@ -133,7 +171,6 @@ async def test_light_control_fail(
|
||||
mock_bridge,
|
||||
mock_api,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
device,
|
||||
entity_id: str,
|
||||
light_id: int,
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from aioswitcher.api import Command, ShutterChildLock, SwitcherBaseResponse
|
||||
from aioswitcher.device import DeviceState
|
||||
from aioswitcher.api import Command
|
||||
from aioswitcher.api.messages import SwitcherBaseResponse
|
||||
from aioswitcher.device import DeviceState, ShutterChildLock
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
@ -86,6 +87,45 @@ async def test_switch(
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_bridge", [[DUMMY_WATER_HEATER_DEVICE]], indirect=True)
|
||||
async def test_switch_ignore_previous_async_state(
|
||||
hass: HomeAssistant, mock_bridge, mock_api
|
||||
) -> None:
|
||||
"""Test switch ignores previous async state."""
|
||||
await init_integration(hass)
|
||||
assert mock_bridge
|
||||
|
||||
device = DUMMY_WATER_HEATER_DEVICE
|
||||
entity_id = f"{SWITCH_DOMAIN}.{slugify(device.name)}"
|
||||
|
||||
# Test initial state - on
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
# Test turning off
|
||||
with patch(
|
||||
"homeassistant.components.switcher_kis.entity.SwitcherApi.control_device"
|
||||
) as mock_control_device:
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
|
||||
# Push old state and makge sure it is ignored
|
||||
mock_bridge.mock_callbacks([DUMMY_WATER_HEATER_DEVICE])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_api.call_count == 2
|
||||
mock_control_device.assert_called_once_with(Command.OFF)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Verify new state is not ignored
|
||||
mock_bridge.mock_callbacks([DUMMY_WATER_HEATER_DEVICE])
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_bridge", [[DUMMY_PLUG_DEVICE]], indirect=True)
|
||||
async def test_switch_control_fail(
|
||||
hass: HomeAssistant,
|
||||
@ -240,6 +280,44 @@ async def test_child_lock_switch(
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_bridge", [[DEVICE]], indirect=True)
|
||||
async def test_child_lock_switch_ignore_previous_async_state(
|
||||
hass: HomeAssistant, mock_bridge, mock_api
|
||||
) -> None:
|
||||
"""Test child lock switch ignores previous async state."""
|
||||
await init_integration(hass)
|
||||
assert mock_bridge
|
||||
|
||||
entity_id = f"{SWITCH_DOMAIN}.{slugify(DEVICE.name)}_child_lock"
|
||||
|
||||
# Test initial state - on
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
# Test turning off
|
||||
with patch(
|
||||
"homeassistant.components.switcher_kis.entity.SwitcherApi.set_shutter_child_lock"
|
||||
) as mock_control_device:
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||
)
|
||||
|
||||
# Push old state and makge sure it is ignored
|
||||
mock_bridge.mock_callbacks([DEVICE])
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_api.call_count == 2
|
||||
mock_control_device.assert_called_once_with(ShutterChildLock.OFF, 0)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Verify new state is not ignored
|
||||
mock_bridge.mock_callbacks([DEVICE])
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"device",
|
||||
|
Loading…
x
Reference in New Issue
Block a user