From bd2c3e7894e62269d953eb05be06f878e0a0f7a0 Mon Sep 17 00:00:00 2001 From: Vaughan Zeng Date: Thu, 5 Aug 2021 18:40:15 +0800 Subject: [PATCH] Cleanup --- custom_components/uvfive/__init__.py | 2 +- custom_components/uvfive/config_flow.py | 55 +++++--- custom_components/uvfive/const.py | 2 +- custom_components/uvfive/device.py | 91 ++++++++++++ custom_components/uvfive/switch.py | 133 +++++++----------- custom_components/uvfive/translations/en.json | 15 +- .../uvfive/translations/zh-Hans.json | 5 +- 7 files changed, 183 insertions(+), 120 deletions(-) create mode 100644 custom_components/uvfive/device.py diff --git a/custom_components/uvfive/__init__.py b/custom_components/uvfive/__init__.py index 7fa9168..705ea73 100644 --- a/custom_components/uvfive/__init__.py +++ b/custom_components/uvfive/__init__.py @@ -1,4 +1,4 @@ -"""Support for uvFive MIoT device.""" +"""Support for Five MIoT device.""" from datetime import timedelta import logging diff --git a/custom_components/uvfive/config_flow.py b/custom_components/uvfive/config_flow.py index 4a9d588..8e41f1d 100644 --- a/custom_components/uvfive/config_flow.py +++ b/custom_components/uvfive/config_flow.py @@ -1,4 +1,4 @@ -"""Config flow to configure uvFive MIoT device.""" +"""Config flow to configure Five MIoT device.""" import logging from re import search @@ -18,21 +18,25 @@ DOMAIN, UVFIVE_MODELS, ) +from .device import ConnectFiveDevice _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "uvFive MIoT device" +DEFAULT_NAME = "Five MIoT device" +DEFAULT_LAMP_NAME = 'Five Sterilization Lamp' +DEFAULT_RACK_NAME = 'Five Sterilization Rack' DEVICE_CONFIG = vol.Schema({ vol.Required(CONF_HOST): str, vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), - vol.Optional(CONF_NAME, default = DEFAULT_NAME): str, + # vol.Optional(CONF_NAME, default = DEFAULT_NAME): str, + vol.Optional(CONF_NAME): str, # vol.Optional(CONF_MODEL, default = ''): vol.In(UVFIVE_MODELS), }) class uvFiveMiotFlowHandler(config_entries.ConfigFlow, domain = DOMAIN): - """Handle a uvFive MIoT config flow.""" + """Handle a Five MIoT config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL @@ -85,45 +89,46 @@ async def async_step_zeroconf(self, discovery_info): # Discovered device is not yet supported _LOGGER.debug( - "Not yet supported uvFive MIoT device '%s' discovered with host %s", + "Not yet supported Five MIoT device '%s' discovered with host %s", name, self.host, ) return self.async_abort(reason="not_uvfive_miot") async def async_step_device(self, user_input=None): - """Handle a flow initialized by the user to configure a uvfive miot device.""" + """Handle a flow initialized by the user to configure a Five MIoT device.""" errors = {} if user_input is not None: self.host = user_input[CONF_HOST] token = user_input[CONF_TOKEN] - name = user_input[CONF_NAME] if user_input.get(CONF_MODEL): model = user_input.get(CONF_MODEL) else: model = None - try: - miot_device = Device(self.host, token) - device_info = miot_device.info() - if model is None and device_info is not None: - model = device_info.model - - _LOGGER.info( - "%s %s %s detected", - model, - device_info.firmware_version, - device_info.hardware_version, - ) - except DeviceException as ex: - raise PlatformNotReady from ex + # Try to connect to a Five MIoT Device. + connect_device_class = ConnectFiveDevice(self.hass) + await connect_device_class.async_connect_device(self.host, token) + device_info = connect_device_class.device_info + + if model is None and device_info is not None: + model = device_info.model if model is not None: if self.mac is None and device_info is not None: self.mac = format_mac(device_info.mac_address) - # Setup uvFive MIoT device - # name = user_input.get(CONF_NAME, model) + # Setup Five MIoT device + if user_input.get(CONF_NAME): + name = user_input.get(CONF_NAME) + else: + if model == 'uvfive.s_lamp.slmap2': + name = DEFAULT_LAMP_NAME + elif model == 'uvfive.steriliser.tiger': + name = DEFAULT_RACK_NAME + else: + name = DEFAULT_NAME + for device_model in UVFIVE_MODELS: if model.startswith(device_model): unique_id = f"{model}-{device_info.mac_address}" @@ -142,6 +147,10 @@ async def async_step_device(self, user_input=None): }, ) + errors["base"] = "unknown_device" + else: + errors["base"] = "cannot_connect" + schema = DEVICE_CONFIG return self.async_show_form(step_id="device", data_schema=schema, errors=errors) diff --git a/custom_components/uvfive/const.py b/custom_components/uvfive/const.py index ac2f5a2..a9ed81c 100644 --- a/custom_components/uvfive/const.py +++ b/custom_components/uvfive/const.py @@ -1,4 +1,4 @@ -"""Constants for the uvFive MIoT device component.""" +"""Constants for the Five MIoT device component.""" DOMAIN = "uvfive" CONF_FLOW_TYPE = "config_flow_device" diff --git a/custom_components/uvfive/device.py b/custom_components/uvfive/device.py new file mode 100644 index 0000000..076e957 --- /dev/null +++ b/custom_components/uvfive/device.py @@ -0,0 +1,91 @@ +"""Code to handle a Five MIoT Device.""" +import logging + +from miio import Device, DeviceException + +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.entity import Entity + +from .const import CONF_MAC, CONF_MODEL, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class ConnectFiveDevice: + """Class to async connect to a Five MIoT Device.""" + + def __init__(self, hass): + """Initialize the entity.""" + self._hass = hass + self._device = None + self._device_info = None + + @property + def device(self): + """Return the class containing all connections to the device.""" + return self._device + + @property + def device_info(self): + """Return the class containing device info.""" + return self._device_info + + async def async_connect_device(self, host, token): + """Connect to the Five MIoT Device.""" + _LOGGER.debug("Initializing Five MIoT device with host %s (token %s...)", host, token[:5]) + try: + self._device = Device(host, token) + # get the device info + self._device_info = await self._hass.async_add_executor_job( + self._device.info + ) + except DeviceException: + _LOGGER.error( + "DeviceException during setup of Five MIoT device with host %s", host + ) + return False + _LOGGER.debug( + "%s %s %s detected", + self._device_info.model, + self._device_info.firmware_version, + self._device_info.hardware_version, + ) + return True + + +class FiveMIoTEntity(Entity): + """Representation of a base Five MIoT Entity.""" + + def __init__(self, name, device, entry, unique_id): + """Initialize the Five MIoT Device.""" + self._device = device + self._model = entry.data[CONF_MODEL] + self._mac = entry.data[CONF_MAC] + self._device_id = entry.unique_id + self._unique_id = unique_id + self._name = name + + @property + def unique_id(self): + """Return an unique ID.""" + return self._unique_id + + @property + def name(self): + """Return the name of this entity, if any.""" + return self._name + + @property + def device_info(self): + """Return the device info.""" + device_info = { + "identifiers": {(DOMAIN, self._device_id)}, + "manufacturer": "Five", + "name": self._name, + "model": self._model, + } + + if self._mac is not None: + device_info["connections"] = {(dr.CONNECTION_NETWORK_MAC, self._mac)} + + return device_info \ No newline at end of file diff --git a/custom_components/uvfive/switch.py b/custom_components/uvfive/switch.py index c723635..cccea05 100644 --- a/custom_components/uvfive/switch.py +++ b/custom_components/uvfive/switch.py @@ -1,4 +1,4 @@ -"""Support for uvFive MIoT device.""" +"""Support for Five MIoT device.""" import asyncio from functools import partial import logging @@ -8,8 +8,6 @@ from enum import IntEnum from datetime import datetime, timedelta -from homeassistant.helpers.entity import Entity -from homeassistant.helpers import device_registry as dr from homeassistant.components.switch import ( # PLATFORM_SCHEMA, SwitchEntity, @@ -41,10 +39,11 @@ SERVICE_SET_RACK_ALARM_OFF, SERVICE_SET_RACK_RUNNING_MODE, ) +from .device import FiveMIoTEntity _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "uvFive MIoT device" +# DEFAULT_NAME = "Five MIoT device" DATA_KEY = "switch.uvfive_miot" # PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -140,9 +139,9 @@ class RackMode(IntEnum): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Import uvFive MIoT device configuration from YAML.""" + """Import Five MIoT device configuration from YAML.""" _LOGGER.warning( - "Loading uvFive MIoT device via platform setup is deprecated; Please remove it from your configuration" + "Loading Five MIoT device via platform setup is deprecated; Please remove it from your configuration" ) hass.async_create_task( hass.config_entries.flow.async_init( @@ -154,7 +153,7 @@ 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 the uvFive MIoT switch from a config entry.""" + """Set up the Five MIoT switch from a config entry.""" entities = [] host = config_entry.data[CONF_HOST] @@ -171,12 +170,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if model in ["uvfive.s_lamp.slmap2"]: uvfive_device = Device(host, token) - device = uvFiveSterilizationLampSwitch(name, uvfive_device, config_entry, unique_id) + device = FiveSterilizationLampSwitch(name, uvfive_device, config_entry, unique_id) entities.append(device) hass.data[DATA_KEY][host] = device elif model in ["uvfive.steriliser.tiger"]: uvfive_device = Device(host, token) - device = uvFiveSterilizationRackSwitch(name, uvfive_device, config_entry, unique_id) + device = FiveSterilizationRackSwitch(name, uvfive_device, config_entry, unique_id) entities.append(device) hass.data[DATA_KEY][host] = device else: @@ -188,7 +187,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) async def async_service_handler(service): - """Map services to methods on uvFive MIoT device.""" + """Map services to methods on Five MIoT device.""" method = SERVICE_TO_METHOD.get(service.service) params = { key: value @@ -224,49 +223,11 @@ async def async_service_handler(service): async_add_entities(entities, update_before_add=True) -class uvFiveMIoTEntity(Entity): - """Representation of a base uvFive MIoT Entity.""" +class FiveMiotGenericSwitch(FiveMIoTEntity, SwitchEntity): + """Representation of Five MIoT Switch Generic.""" def __init__(self, name, device, entry, unique_id): - """Initialize the uvFive MIoT Device.""" - self._device = device - self._model = entry.data[CONF_MODEL] - self._mac = entry.data[CONF_MAC] - self._device_id = entry.unique_id - self._unique_id = unique_id - self._name = name - - @property - def unique_id(self): - """Return an unique ID.""" - return self._unique_id - - @property - def name(self): - """Return the name of this entity, if any.""" - return self._name - - @property - def device_info(self): - """Return the device info.""" - device_info = { - "identifiers": {(DOMAIN, self._device_id)}, - "manufacturer": "uvFive", - "name": self._name, - "model": self._model, - } - - if self._mac is not None: - device_info["connections"] = {(dr.CONNECTION_NETWORK_MAC, self._mac)} - - return device_info - - -class uvFiveGenericSwitch(uvFiveMIoTEntity, SwitchEntity): - """Representation of uvFive MIoT Switch Generic.""" - - def __init__(self, name, device, entry, unique_id): - """Initialize the uvFive MIoT Switch.""" + """Initialize the Five MIoT Switch.""" super().__init__(name, device, entry, unique_id) self._available = False @@ -298,7 +259,7 @@ async def _try_command(self, mask_error, func, *args, **kwargs): """Call a device command handling error messages.""" try: result = await self.hass.async_add_executor_job(partial(func, *args, **kwargs)) - _LOGGER.debug("Response received from uvFive MIoT device: %s", result) + _LOGGER.debug("Response received from Five MIoT device: %s", result) return result == SUCCESS except DeviceException as exc: if self._available: @@ -307,7 +268,7 @@ async def _try_command(self, mask_error, func, *args, **kwargs): return False async def _uvfive_turn_on(self, **kwargs): - """Turn the uvFive MIoT Switch on.""" + """Turn the Five MIoT Switch on.""" result = await self._try_command(kwargs['error_info'], self._device.send, 'set_properties', [{"siid":kwargs['siid'], "piid":kwargs['piid'], "value":True}] ) @@ -317,7 +278,7 @@ async def _uvfive_turn_on(self, **kwargs): self._skip_update = True async def _uvfive_turn_off(self, **kwargs): - """Turn the uvFive MIoT Switch off.""" + """Turn the Five MIoT Switch off.""" result = await self._try_command(kwargs['error_info'], self._device.send, 'set_properties', [{"siid":kwargs['siid'], "piid":kwargs['piid'], "value":False}] ) @@ -333,12 +294,12 @@ async def _uvfive_set_child_lock(self, **kwargs): ) -class uvFiveSterilizationLampSwitch(uvFiveGenericSwitch): - """Representation of uvFive MIoT Sterilization Lamp.""" +class FiveSterilizationLampSwitch(FiveMiotGenericSwitch): + """Representation of Five Sterilization Lamp.""" - def __init__(self, name, uvfive_device, model, unique_id): - """Initialize the uvFive MIoT Sterilization Lamp.""" - super().__init__(name, uvfive_device, model, unique_id) + def __init__(self, name, uvfive_device, entry, unique_id): + """Initialize the Five Sterilization Lamp.""" + super().__init__(name, uvfive_device, entry, unique_id) self._icon = 'mdi:lightbulb-cfl' self._state_attrs[ATTR_SLAMP_STERILIZATION_TIME] = None @@ -350,14 +311,14 @@ def icon(self): return self._icon async def async_turn_on(self): - """Turn the uvFive MIoT Sterilization Lamp on.""" - await self._uvfive_turn_on(error_info = "Turning the uvFive MIoT Sterilization Lamp on failed.", + """Turn the Five Sterilization Lamp on.""" + await self._uvfive_turn_on(error_info = "Turning the Five Sterilization Lamp on failed.", siid = 2, piid = 2, ) async def async_turn_off(self): - """Turn the uvFive MIoT Sterilization Lamp off.""" - await self._uvfive_turn_off(error_info = "Turning the uvFive MIoT Sterilization Lamp off failed.", + """Turn the Five Sterilization Lamp off.""" + await self._uvfive_turn_off(error_info = "Turning the Five Sterilization Lamp off failed.", siid = 2, piid = 2, ) @@ -380,7 +341,7 @@ async def async_update(self): {"siid":4,"piid":1}, {"siid":5,"piid":1}] ) - _LOGGER.debug("Got the uvFive MIoT Sterilization Lamp new state: %s", state) + _LOGGER.debug("Got the Five Sterilization Lamp new state: %s", state) self._available = True @@ -428,12 +389,12 @@ async def async_set_slamp_disable_radar_off(self): ) -class uvFiveSterilizationRackSwitch(uvFiveGenericSwitch): - """Representation of uvFive MIoT Sterilization Rack.""" +class FiveSterilizationRackSwitch(FiveMiotGenericSwitch): + """Representation of Five Sterilization Rack.""" - def __init__(self, name, uvfive_device, model, unique_id): - """Initialize the uvFive MIoT Sterilization Rack.""" - super().__init__(name, uvfive_device, model, unique_id) + def __init__(self, name, uvfive_device, entry, unique_id): + """Initialize the Five Sterilization Rack.""" + super().__init__(name, uvfive_device, entry, unique_id) self._icon = 'mdi:toaster' self._state_attrs[ATTR_MODE] = None @@ -447,14 +408,14 @@ def icon(self): return self._icon async def async_turn_on(self): - """Turn the uvFive MIoT Sterilization Rack on.""" - await self._uvfive_turn_on(error_info = "Turning the uvFive MIoT Sterilization Rack on failed.", + """Turn the Five Sterilization Rack on.""" + await self._uvfive_turn_on(error_info = "Turning the Five Sterilization Rack on failed.", siid = 2, piid = 3, ) async def async_turn_off(self): - """Turn the uvFive MIoT Sterilization Rack off.""" - await self._uvfive_turn_off(error_info = "Turning the uvFive MIoT Sterilization Rack off failed.", + """Turn the Five Sterilization Rack off.""" + await self._uvfive_turn_off(error_info = "Turning the Five Sterilization Rack off failed.", siid = 2, piid = 3, ) @@ -480,7 +441,7 @@ async def async_update(self): {"siid":3,"piid":1}, {"siid":4,"piid":1}] ) - _LOGGER.debug("Got the uvFive MIoT Sterilization Rack new state: %s", state) + _LOGGER.debug("Got the Five Sterilization Rack new state: %s", state) self._available = True @@ -500,38 +461,38 @@ async def async_update(self): _LOGGER.error("Got exception while fetching the state: %s", ex) async def async_set_rack_running_mode(self, mode: str): - """Set the uvFive Rack running mode.""" - await self._try_command("Setting the uvFive Rack running mode failed.", + """Set the Five Sterilization Rack running mode.""" + await self._try_command("Setting the Five Sterilization Rack running mode failed.", self._device.send, 'set_properties', [{"siid":2, "piid":2, "value":RackMode[mode].value}] ) async def async_set_rack_target_time(self, minutes: int): - """Set the uvFive Rack target time.""" - await self._try_command("Setting the uvFive Rack target time failed.", + """Set the Five Sterilization Rack target time.""" + await self._try_command("Setting the Five Sterilization Rack target time failed.", self._device.send, 'set_properties', [{"siid":2, "piid":5, "value":minutes}] ) async def async_set_rack_alarm_on(self): - """Turn the uvFive Rack alarm on.""" - await self._try_command("Turning the uvFive Rack alarm on failed.", + """Turn the Five Sterilization Rack alarm on.""" + await self._try_command("Turning the Five Sterilization Rack alarm on failed.", self._device.send, 'set_properties', [{"siid":3, "piid":1, "value":True}] ) async def async_set_rack_alarm_off(self): - """Turn the uvFive Rack alarm off.""" - await self._try_command("Turning the uvFive Rack alarm off failed.", + """Turn the Five Sterilization Rack alarm off.""" + await self._try_command("Turning the Five Sterilization Rack alarm off failed.", self._device.send, 'set_properties', [{"siid":3, "piid":1, "value":False}] ) async def async_set_child_lock_on(self): - """Turn the child lock on.""" - await self._uvfive_set_child_lock(error_info = "Turning the uvFive Rack child lock on failed.", + """Turn the Five Sterilization Rack child lock on.""" + await self._uvfive_set_child_lock(error_info = "Turning the Five Sterilization Rack child lock on failed.", siid = 4, piid = 1, value = True, ) async def async_set_child_lock_off(self): - """Turn the child lock off.""" - await self._uvfive_set_child_lock(error_info = "Turning the uvFive Rack child lock off failed.", + """Turn the Five Sterilization Rack child lock off.""" + await self._uvfive_set_child_lock(error_info = "Turning the Five Sterilization Rack child lock off failed.", siid = 4, piid = 1, value = False, ) diff --git a/custom_components/uvfive/translations/en.json b/custom_components/uvfive/translations/en.json index 6d6e921..d61da37 100644 --- a/custom_components/uvfive/translations/en.json +++ b/custom_components/uvfive/translations/en.json @@ -1,23 +1,24 @@ { "config": { - "flow_title": "uvFive MIoT device **: {name}", + "flow_title": "Five MIoT device **: {name}", "step": { "device": { "data": { - "host": "Device IP", - "token": "Token", - "name": "Device name" + "host": "IP Address", + "token": "API Token", + "name": "Name of the device" }, "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", - "title": "uvFive MIoT device" + "title": "Five MIoT device" } }, "error": { - "cannot_connect": "Failed to connect the device" + "cannot_connect": "Failed to connect", + "unknown_device": "The device model is not supported, not able to setup the device using config flow." }, "abort": { "already_configured": "Device is already configured.", - "not_uvfive_miot": "Not a uvFive MIoT device." + "not_uvfive_miot": "Not a Five MIoT device." } } } \ No newline at end of file diff --git a/custom_components/uvfive/translations/zh-Hans.json b/custom_components/uvfive/translations/zh-Hans.json index 1362ede..6cb38e0 100644 --- a/custom_components/uvfive/translations/zh-Hans.json +++ b/custom_components/uvfive/translations/zh-Hans.json @@ -6,14 +6,15 @@ "data": { "host": "设备IP", "token": "Token", - "name": "名称", + "name": "名称" }, "description": "You will need the 32 character API Token, see https://www.home-assistant.io/integrations/xiaomi_miio#retrieving-the-access-token for instructions. Please note, that this API Token is different from the key used by the Xiaomi Aqara integration.", "title": "uvFive MIoT device" } }, "error": { - "cannot_connect": "连接设备失败" + "cannot_connect": "连接设备失败", + "unknown_device": "不支持的设备型号,无法通过此组件配置." }, "abort": { "already_configured": "该设备已经配置过",