core/homeassistant/components/broadlink/updater.py

179 lines
5.6 KiB
Python

"""Support for fetching data from Broadlink devices."""
from abc import ABC, abstractmethod
from datetime import timedelta
from functools import partial
import logging
import broadlink as blk
from broadlink.exceptions import (
AuthorizationError,
BroadlinkException,
CommandNotSupportedError,
NetworkTimeoutError,
StorageError,
)
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt
_LOGGER = logging.getLogger(__name__)
def get_update_manager(device):
"""Return an update manager for a given Broadlink device."""
if device.api.model.startswith("RM mini"):
return BroadlinkRMMini3UpdateManager(device)
update_managers = {
"A1": BroadlinkA1UpdateManager,
"BG1": BroadlinkBG1UpdateManager,
"MP1": BroadlinkMP1UpdateManager,
"RM2": BroadlinkRMUpdateManager,
"RM4": BroadlinkRMUpdateManager,
"SP1": BroadlinkSP1UpdateManager,
"SP2": BroadlinkSP2UpdateManager,
"SP4": BroadlinkSP4UpdateManager,
"SP4B": BroadlinkSP4UpdateManager,
}
return update_managers[device.api.type](device)
class BroadlinkUpdateManager(ABC):
"""Representation of a Broadlink update manager.
Implement this class to manage fetching data from the device and to
monitor device availability.
"""
SCAN_INTERVAL = timedelta(minutes=1)
def __init__(self, device):
"""Initialize the update manager."""
self.device = device
self.coordinator = DataUpdateCoordinator(
device.hass,
_LOGGER,
name=f"{device.name} ({device.api.model} at {device.api.host[0]})",
update_method=self.async_update,
update_interval=self.SCAN_INTERVAL,
)
self.available = None
self.last_update = None
async def async_update(self):
"""Fetch data from the device and update availability."""
try:
data = await self.async_fetch_data()
except (BroadlinkException, OSError) as err:
if self.available and (
dt.utcnow() - self.last_update > self.SCAN_INTERVAL * 3
or isinstance(err, (AuthorizationError, OSError))
):
self.available = False
_LOGGER.warning(
"Disconnected from %s (%s at %s)",
self.device.name,
self.device.api.model,
self.device.api.host[0],
)
raise UpdateFailed(err) from err
else:
if self.available is False:
_LOGGER.warning(
"Connected to %s (%s at %s)",
self.device.name,
self.device.api.model,
self.device.api.host[0],
)
self.available = True
self.last_update = dt.utcnow()
return data
@abstractmethod
async def async_fetch_data(self):
"""Fetch data from the device."""
class BroadlinkA1UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink A1 devices."""
SCAN_INTERVAL = timedelta(seconds=10)
async def async_fetch_data(self):
"""Fetch data from the device."""
return await self.device.async_request(self.device.api.check_sensors_raw)
class BroadlinkMP1UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink MP1 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
return await self.device.async_request(self.device.api.check_power)
class BroadlinkRMMini3UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink RM mini 3 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
hello = partial(
blk.discover,
discover_ip_address=self.device.api.host[0],
timeout=self.device.api.timeout,
)
devices = await self.device.hass.async_add_executor_job(hello)
if not devices:
raise NetworkTimeoutError("The device is offline")
return {}
class BroadlinkRMUpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink RM2 and RM4 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
return await self.device.async_request(self.device.api.check_sensors)
class BroadlinkSP1UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink SP1 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
return None
class BroadlinkSP2UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink SP2 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
data = {}
data["state"] = await self.device.async_request(self.device.api.check_power)
try:
data["load_power"] = await self.device.async_request(
self.device.api.get_energy
)
except (CommandNotSupportedError, StorageError):
data["load_power"] = None
return data
class BroadlinkBG1UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink BG1 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
return await self.device.async_request(self.device.api.get_state)
class BroadlinkSP4UpdateManager(BroadlinkUpdateManager):
"""Manages updates for Broadlink SP4 devices."""
async def async_fetch_data(self):
"""Fetch data from the device."""
return await self.device.async_request(self.device.api.get_state)