From 9f5baa0bf70ab6e80c9140d4865742ff3a9dc0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 2 Sep 2020 12:50:57 +0300 Subject: [PATCH] Syncthru device registry (#36750) * Store printer instances in hass.data * Add SyncThru device registry support * Use config entry id as hass.data key Co-authored-by: Paulus Schoutsen * Use printer hostname as device registry name * Handle non-syncthru device more gracefully on entry setup * Use device identifiers rather than connections to link entities with devices Co-authored-by: Paulus Schoutsen --- homeassistant/components/syncthru/__init__.py | 63 ++++++++++++++++++- .../components/syncthru/exceptions.py | 7 --- homeassistant/components/syncthru/sensor.py | 32 ++++------ 3 files changed, 72 insertions(+), 30 deletions(-) delete mode 100644 homeassistant/components/syncthru/exceptions.py diff --git a/homeassistant/components/syncthru/__init__.py b/homeassistant/components/syncthru/__init__.py index 512db1b7527..83d32eb9b47 100644 --- a/homeassistant/components/syncthru/__init__.py +++ b/homeassistant/components/syncthru/__init__.py @@ -1,23 +1,82 @@ """The syncthru component.""" +import logging +from typing import Set, Tuple + +from pysyncthru import SyncThru + from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_URL +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client, device_registry as dr from homeassistant.helpers.typing import ConfigType, HomeAssistantType +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up.""" + hass.data.setdefault(DOMAIN, {}) return True async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Set up config entry.""" + + session = aiohttp_client.async_get_clientsession(hass) + printer = hass.data[DOMAIN][entry.entry_id] = SyncThru( + entry.data[CONF_URL], session + ) + + try: + await printer.update() + except ValueError: + _LOGGER.error( + "Device at %s not appear to be a SyncThru printer, aborting setup", + printer.url, + ) + return False + else: + if printer.is_unknown_state(): + raise ConfigEntryNotReady + + device_registry = await dr.async_get_registry(hass) + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections=device_connections(printer), + identifiers=device_identifiers(printer), + model=printer.model(), + name=printer.hostname(), + ) + hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, SENSOR_DOMAIN) ) return True -async def async_unload_entry(hass, entry): +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Unload the config entry.""" - return await hass.config_entries.async_forward_entry_unload(entry, SENSOR_DOMAIN) + await hass.config_entries.async_forward_entry_unload(entry, SENSOR_DOMAIN) + hass.data[DOMAIN].pop(entry.entry_id, None) + return True + + +def device_identifiers(printer: SyncThru) -> Set[Tuple[str, str]]: + """Get device identifiers for device registry.""" + return {(DOMAIN, printer.serial_number())} + + +def device_connections(printer: SyncThru) -> Set[Tuple[str, str]]: + """Get device connections for device registry.""" + connections = set() + try: + mac = printer.raw()["identity"]["mac_addr"] + if mac: + connections.add((dr.CONNECTION_NETWORK_MAC, mac)) + except AttributeError: + pass + return connections diff --git a/homeassistant/components/syncthru/exceptions.py b/homeassistant/components/syncthru/exceptions.py deleted file mode 100644 index 0bb4b8229ce..00000000000 --- a/homeassistant/components/syncthru/exceptions.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Samsung SyncThru exceptions.""" - -from homeassistant.exceptions import HomeAssistantError - - -class SyncThruNotSupported(HomeAssistantError): - """Error to indicate SyncThru is not supported.""" diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index c9aa6d8de9c..869a2a8e997 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -8,13 +8,11 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_NAME, CONF_RESOURCE, CONF_URL, UNIT_PERCENTAGE -from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers import aiohttp_client import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from . import device_identifiers from .const import DEFAULT_MODEL, DEFAULT_NAME_TEMPLATE, DOMAIN -from .exceptions import SyncThruNotSupported _LOGGER = logging.getLogger(__name__) @@ -61,25 +59,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry(hass, config_entry, async_add_entities): """Set up from config entry.""" - session = aiohttp_client.async_get_clientsession(hass) + printer = hass.data[DOMAIN][config_entry.entry_id] - printer = SyncThru(config_entry.data[CONF_URL], session) - # Test if the discovered device actually is a syncthru printer - # and fetch the available toner/drum/etc - try: - # No error is thrown when the device is off - # (only after user added it manually) - # therefore additional catches are inside the Sensor below - await printer.update() - supp_toner = printer.toner_status(filter_supported=True) - supp_drum = printer.drum_status(filter_supported=True) - supp_tray = printer.input_tray_status(filter_supported=True) - supp_output_tray = printer.output_tray_status() - except ValueError as ex: - raise SyncThruNotSupported from ex - else: - if printer.is_unknown_state(): - raise PlatformNotReady + supp_toner = printer.toner_status(filter_supported=True) + supp_drum = printer.drum_status(filter_supported=True) + supp_tray = printer.input_tray_status(filter_supported=True) + supp_output_tray = printer.output_tray_status() name = config_entry.data[CONF_NAME] devices = [SyncThruMainSensor(printer, name)] @@ -140,6 +125,11 @@ class SyncThruSensor(Entity): """Return the state attributes of the device.""" return self._attributes + @property + def device_info(self): + """Return device information.""" + return {"identifiers": device_identifiers(self.syncthru)} + class SyncThruMainSensor(SyncThruSensor): """Implementation of the main sensor, conducting the actual polling."""