Code cleanups for lookin (#64106)

This commit is contained in:
J. Nick Koston 2022-01-14 14:22:06 -10:00 committed by GitHub
parent b949199866
commit 06329a2f43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 149 additions and 185 deletions

View File

@ -2,31 +2,59 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable, Coroutine
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any
import aiohttp import aiohttp
from aiolookin import ( from aiolookin import (
Climate,
LookInHttpProtocol, LookInHttpProtocol,
LookinUDPSubscriptions, LookinUDPSubscriptions,
MeteoSensor, MeteoSensor,
Remote,
start_lookin_udp, start_lookin_udp,
) )
from aiolookin.models import UDPCommandType, UDPEvent from aiolookin.models import UDPCommandType, UDPEvent
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, PLATFORMS from .const import DOMAIN, PLATFORMS, TYPE_TO_PLATFORM
from .models import LookinData from .models import LookinData
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
def _async_climate_updater(
lookin_protocol: LookInHttpProtocol,
uuid: str,
) -> Callable[[], Coroutine[None, Any, Remote]]:
"""Create a function to capture the cell variable."""
async def _async_update() -> Climate:
return await lookin_protocol.get_conditioner(uuid)
return _async_update
def _async_remote_updater(
lookin_protocol: LookInHttpProtocol,
uuid: str,
) -> Callable[[], Coroutine[None, Any, Remote]]:
"""Create a function to capture the cell variable."""
async def _async_update() -> Remote:
return await lookin_protocol.get_remote(uuid)
return _async_update
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up lookin from a config entry.""" """Set up lookin from a config entry."""
@ -52,6 +80,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
) )
await meteo_coordinator.async_config_entry_first_refresh() await meteo_coordinator.async_config_entry_first_refresh()
device_coordinators: dict[str, DataUpdateCoordinator] = {}
for remote in devices:
if (platform := TYPE_TO_PLATFORM.get(remote["Type"])) is None:
continue
uuid = remote["UUID"]
if platform == Platform.CLIMATE:
updater = _async_climate_updater(lookin_protocol, uuid)
else:
updater = _async_remote_updater(lookin_protocol, uuid)
coordinator = DataUpdateCoordinator(
hass,
LOGGER,
name=f"{entry.title} {uuid}",
update_method=updater,
update_interval=timedelta(
seconds=60
), # Updates are pushed (fallback is polling)
)
await coordinator.async_config_entry_first_refresh()
device_coordinators[uuid] = coordinator
@callback @callback
def _async_meteo_push_update(event: UDPEvent) -> None: def _async_meteo_push_update(event: UDPEvent) -> None:
"""Process an update pushed via UDP.""" """Process an update pushed via UDP."""
@ -66,6 +115,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
lookin_device.id, UDPCommandType.meteo, None, _async_meteo_push_update lookin_device.id, UDPCommandType.meteo, None, _async_meteo_push_update
) )
) )
entry.async_on_unload(await start_lookin_udp(lookin_udp_subs, lookin_device.id)) entry.async_on_unload(await start_lookin_udp(lookin_udp_subs, lookin_device.id))
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = LookinData( hass.data.setdefault(DOMAIN, {})[entry.entry_id] = LookinData(
@ -74,6 +124,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
meteo_coordinator=meteo_coordinator, meteo_coordinator=meteo_coordinator,
devices=devices, devices=devices,
lookin_protocol=lookin_protocol, lookin_protocol=lookin_protocol,
device_coordinators=device_coordinators,
) )
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)

View File

@ -1,8 +1,6 @@
"""The lookin integration climate platform.""" """The lookin integration climate platform."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Coroutine
from datetime import timedelta
import logging import logging
from typing import Any, Final, cast from typing import Any, Final, cast
@ -29,12 +27,17 @@ from homeassistant.components.climate.const import (
SWING_OFF, SWING_OFF,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS from homeassistant.const import (
ATTR_TEMPERATURE,
PRECISION_WHOLE,
TEMP_CELSIUS,
Platform,
)
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN, TYPE_TO_PLATFORM
from .entity import LookinCoordinatorEntity from .entity import LookinCoordinatorEntity
from .models import LookinData from .models import LookinData
@ -77,30 +80,10 @@ async def async_setup_entry(
entities = [] entities = []
for remote in lookin_data.devices: for remote in lookin_data.devices:
if remote["Type"] != "EF": if TYPE_TO_PLATFORM.get(remote["Type"]) != Platform.CLIMATE:
continue continue
uuid = remote["UUID"] uuid = remote["UUID"]
coordinator = lookin_data.device_coordinators[uuid]
def _wrap_async_update(
uuid: str,
) -> Callable[[], Coroutine[None, Any, Climate]]:
"""Create a function to capture the uuid cell variable."""
async def _async_update() -> Climate:
return await lookin_data.lookin_protocol.get_conditioner(uuid)
return _async_update
coordinator = DataUpdateCoordinator(
hass,
LOGGER,
name=f"{config_entry.title} {uuid}",
update_method=_wrap_async_update(uuid),
update_interval=timedelta(
seconds=60
), # Updates are pushed (fallback is polling)
)
await coordinator.async_refresh()
device: Climate = coordinator.data device: Climate = coordinator.data
entities.append( entities.append(
ConditionerEntity( ConditionerEntity(

View File

@ -14,3 +14,11 @@ PLATFORMS: Final = [
Platform.MEDIA_PLAYER, Platform.MEDIA_PLAYER,
Platform.SENSOR, Platform.SENSOR,
] ]
TYPE_TO_PLATFORM = {
"01": Platform.MEDIA_PLAYER,
"02": Platform.MEDIA_PLAYER,
"03": Platform.LIGHT,
"EF": Platform.CLIMATE,
}

View File

@ -1,8 +1,12 @@
"""The lookin integration entity.""" """The lookin integration entity."""
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
import logging
from typing import cast
from aiolookin import POWER_CMD, POWER_OFF_CMD, POWER_ON_CMD, Climate, Remote from aiolookin import POWER_CMD, POWER_OFF_CMD, POWER_ON_CMD, Climate, Remote
from aiolookin.models import Device from aiolookin.models import Device, UDPCommandType, UDPEvent
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
@ -13,6 +17,8 @@ from homeassistant.helpers.update_coordinator import (
from .const import DOMAIN, MODEL_NAMES from .const import DOMAIN, MODEL_NAMES
from .models import LookinData from .models import LookinData
LOGGER = logging.getLogger(__name__)
def _lookin_device_to_device_info(lookin_device: Device) -> DeviceInfo: def _lookin_device_to_device_info(lookin_device: Device) -> DeviceInfo:
"""Convert a lookin device into DeviceInfo.""" """Convert a lookin device into DeviceInfo."""
@ -124,3 +130,58 @@ class LookinPowerEntity(LookinCoordinatorEntity):
self._power_on_command = POWER_ON_CMD self._power_on_command = POWER_ON_CMD
if POWER_OFF_CMD in self._function_names: if POWER_OFF_CMD in self._function_names:
self._power_off_command = POWER_OFF_CMD self._power_off_command = POWER_OFF_CMD
class LookinPowerPushRemoteEntity(LookinPowerEntity):
"""A Lookin entity that has a power on and power off command with push updates."""
def __init__(
self,
coordinator: DataUpdateCoordinator,
uuid: str,
device: Remote,
lookin_data: LookinData,
) -> None:
"""Init the entity."""
super().__init__(coordinator, uuid, device, lookin_data)
self._update_from_status(self._remote.status)
self._attr_name = self._remote.name
@property
def _remote(self) -> Remote:
return cast(Remote, self.coordinator.data)
@abstractmethod
def _update_from_status(self, status: str) -> None:
"""Update properties from status."""
def _async_push_update(self, event: UDPEvent) -> None:
"""Process an update pushed via UDP."""
LOGGER.debug("Processing push message for %s: %s", self.entity_id, event)
self._update_from_status(event.value)
self.coordinator.async_set_updated_data(self._remote)
async def _async_push_update_device(self, event: UDPEvent) -> None:
"""Process an update pushed via UDP."""
LOGGER.debug("Processing push message for %s: %s", self.entity_id, event)
await self.coordinator.async_refresh()
self._attr_name = self._remote.name
async def async_added_to_hass(self) -> None:
"""Call when the entity is added to hass."""
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.ir,
self._uuid,
self._async_push_update,
)
)
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.data,
self._uuid,
self._async_push_update_device,
)
)

View File

@ -1,22 +1,19 @@
"""The lookin integration light platform.""" """The lookin integration light platform."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Coroutine
from datetime import timedelta
import logging import logging
from typing import Any, cast from typing import Any
from aiolookin import Remote from aiolookin import Remote
from aiolookin.models import UDPCommandType, UDPEvent
from homeassistant.components.light import COLOR_MODE_ONOFF, LightEntity from homeassistant.components.light import COLOR_MODE_ONOFF, LightEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN, TYPE_TO_PLATFORM
from .entity import LookinPowerEntity from .entity import LookinPowerPushRemoteEntity
from .models import LookinData from .models import LookinData
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -32,65 +29,29 @@ async def async_setup_entry(
entities = [] entities = []
for remote in lookin_data.devices: for remote in lookin_data.devices:
if remote["Type"] != "03": if TYPE_TO_PLATFORM.get(remote["Type"]) != Platform.LIGHT:
continue continue
uuid = remote["UUID"] uuid = remote["UUID"]
coordinator = lookin_data.device_coordinators[uuid]
def _wrap_async_update(
uuid: str,
) -> Callable[[], Coroutine[None, Any, Remote]]:
"""Create a function to capture the uuid cell variable."""
async def _async_update() -> Remote:
return await lookin_data.lookin_protocol.get_remote(uuid)
return _async_update
coordinator = DataUpdateCoordinator(
hass,
LOGGER,
name=f"{config_entry.title} {uuid}",
update_method=_wrap_async_update(uuid),
update_interval=timedelta(
seconds=60
), # Updates are pushed (fallback is polling)
)
await coordinator.async_refresh()
device: Remote = coordinator.data device: Remote = coordinator.data
entities.append( entities.append(
LookinLightEntity( LookinLightEntity(
coordinator=coordinator,
uuid=uuid, uuid=uuid,
device=device, device=device,
lookin_data=lookin_data, lookin_data=lookin_data,
coordinator=coordinator,
) )
) )
async_add_entities(entities) async_add_entities(entities)
class LookinLightEntity(LookinPowerEntity, LightEntity): class LookinLightEntity(LookinPowerPushRemoteEntity, LightEntity):
"""A lookin IR controlled light.""" """A lookin IR controlled light."""
_attr_supported_color_modes = {COLOR_MODE_ONOFF} _attr_supported_color_modes = {COLOR_MODE_ONOFF}
_attr_color_mode = COLOR_MODE_ONOFF _attr_color_mode = COLOR_MODE_ONOFF
def __init__(
self,
uuid: str,
device: Remote,
lookin_data: LookinData,
coordinator: DataUpdateCoordinator,
) -> None:
"""Init the light."""
super().__init__(coordinator, uuid, device, lookin_data)
self._attr_is_on = False
@property
def _remote(self) -> Remote:
return cast(Remote, self.coordinator.data)
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light.""" """Turn on the light."""
await self._async_send_command(self._power_on_command) await self._async_send_command(self._power_on_command)
@ -114,35 +75,3 @@ class LookinLightEntity(LookinPowerEntity, LightEntity):
state = status[0] state = status[0]
self._attr_is_on = state == "1" self._attr_is_on = state == "1"
def _async_push_update(self, event: UDPEvent) -> None:
"""Process an update pushed via UDP."""
LOGGER.debug("Processing push message for %s: %s", self.entity_id, event)
self._update_from_status(event.value)
self.coordinator.async_set_updated_data(self._remote)
self.async_write_ha_state()
async def _async_push_update_device(self, event: UDPEvent) -> None:
"""Process an update pushed via UDP."""
LOGGER.debug("Processing push message for %s: %s", self.entity_id, event)
await self.coordinator.async_refresh()
self._attr_name = self._remote.name
async def async_added_to_hass(self) -> None:
"""Call when the entity is added to hass."""
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.ir,
self._uuid,
self._async_push_update,
)
)
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.data,
self._uuid,
self._async_push_update_device,
)
)

View File

@ -1,13 +1,9 @@
"""The lookin integration light platform.""" """The lookin integration light platform."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Coroutine
from datetime import timedelta
import logging import logging
from typing import Any, cast
from aiolookin import Remote from aiolookin import Remote
from aiolookin.models import UDPCommandType, UDPEvent
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MediaPlayerDeviceClass, MediaPlayerDeviceClass,
@ -22,13 +18,13 @@ from homeassistant.components.media_player.const import (
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_STEP,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_ON, STATE_STANDBY from homeassistant.const import STATE_ON, STATE_STANDBY, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN from .const import DOMAIN, TYPE_TO_PLATFORM
from .entity import LookinPowerEntity from .entity import LookinPowerPushRemoteEntity
from .models import LookinData from .models import LookinData
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -59,73 +55,44 @@ async def async_setup_entry(
entities = [] entities = []
for remote in lookin_data.devices: for remote in lookin_data.devices:
if remote["Type"] not in _TYPE_TO_DEVICE_CLASS: if TYPE_TO_PLATFORM.get(remote["Type"]) != Platform.MEDIA_PLAYER:
continue continue
uuid = remote["UUID"] uuid = remote["UUID"]
coordinator = lookin_data.device_coordinators[uuid]
def _wrap_async_update(
uuid: str,
) -> Callable[[], Coroutine[None, Any, Remote]]:
"""Create a function to capture the uuid cell variable."""
async def _async_update() -> Remote:
return await lookin_data.lookin_protocol.get_remote(uuid)
return _async_update
coordinator = DataUpdateCoordinator(
hass,
LOGGER,
name=f"{config_entry.title} {uuid}",
update_method=_wrap_async_update(uuid),
update_interval=timedelta(
seconds=60
), # Updates are pushed (fallback is polling)
)
await coordinator.async_refresh()
device: Remote = coordinator.data device: Remote = coordinator.data
entities.append( entities.append(
LookinMedia( LookinMedia(
coordinator=coordinator,
uuid=uuid, uuid=uuid,
device=device, device=device,
lookin_data=lookin_data, lookin_data=lookin_data,
device_class=_TYPE_TO_DEVICE_CLASS[remote["Type"]], device_class=_TYPE_TO_DEVICE_CLASS[remote["Type"]],
coordinator=coordinator,
) )
) )
async_add_entities(entities) async_add_entities(entities)
class LookinMedia(LookinPowerEntity, MediaPlayerEntity): class LookinMedia(LookinPowerPushRemoteEntity, MediaPlayerEntity):
"""A lookin media player.""" """A lookin media player."""
_attr_should_poll = False _attr_should_poll = False
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator,
uuid: str, uuid: str,
device: Remote, device: Remote,
lookin_data: LookinData, lookin_data: LookinData,
device_class: str, device_class: str,
coordinator: DataUpdateCoordinator,
) -> None: ) -> None:
"""Init the lookin media player.""" """Init the lookin media player."""
self._attr_device_class = device_class self._attr_device_class = device_class
self._attr_supported_features: int = 0 self._attr_supported_features: int = 0
self._attr_state = None
self._attr_is_volume_muted: bool = False
super().__init__(coordinator, uuid, device, lookin_data) super().__init__(coordinator, uuid, device, lookin_data)
for function_name, feature in _FUNCTION_NAME_TO_FEATURE.items(): for function_name, feature in _FUNCTION_NAME_TO_FEATURE.items():
if function_name in self._function_names: if function_name in self._function_names:
self._attr_supported_features |= feature self._attr_supported_features |= feature
self._attr_name = self._remote.name
self._async_update_from_data()
@property
def _remote(self) -> Remote:
return cast(Remote, self.coordinator.data)
async def async_volume_up(self) -> None: async def async_volume_up(self) -> None:
"""Turn volume up for media player.""" """Turn volume up for media player."""
@ -177,39 +144,3 @@ class LookinMedia(LookinPowerEntity, MediaPlayerEntity):
self._attr_state = STATE_ON if state == "1" else STATE_STANDBY self._attr_state = STATE_ON if state == "1" else STATE_STANDBY
self._attr_is_volume_muted = mute == "0" self._attr_is_volume_muted = mute == "0"
def _async_push_update(self, event: UDPEvent) -> None:
"""Process an update pushed via UDP."""
LOGGER.debug("Processing push message for %s: %s", self.entity_id, event)
self._update_from_status(event.value)
self.coordinator.async_set_updated_data(self._remote)
self.async_write_ha_state()
async def _async_push_update_device(self, event: UDPEvent) -> None:
"""Process an update pushed via UDP."""
LOGGER.debug("Processing push message for %s: %s", self.entity_id, event)
await self.coordinator.async_refresh()
self._attr_name = self._remote.name
async def async_added_to_hass(self) -> None:
"""Call when the entity is added to hass."""
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.ir,
self._uuid,
self._async_push_update,
)
)
self.async_on_remove(
self._lookin_udp_subs.subscribe_event(
self._lookin_device.id,
UDPCommandType.data,
self._uuid,
self._async_push_update_device,
)
)
def _async_update_from_data(self) -> None:
"""Update attrs from data."""
self._update_from_status(self._remote.status)

View File

@ -18,3 +18,4 @@ class LookinData:
meteo_coordinator: DataUpdateCoordinator meteo_coordinator: DataUpdateCoordinator
devices: list[dict[str, Any]] devices: list[dict[str, Any]]
lookin_protocol: LookInHttpProtocol lookin_protocol: LookInHttpProtocol
device_coordinators: dict[str, DataUpdateCoordinator]