add refresh devices

This commit is contained in:
Andrey
2021-01-15 16:05:10 +03:00
parent ed9011a6e1
commit ed4928011b
7 changed files with 168 additions and 85 deletions

View File

@@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.service import bind_hass from homeassistant.helpers.service import bind_hass
from homeassistant.components import mqtt from homeassistant.components import mqtt
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from .const import DOMAIN, CONF_INVERT, CONF_RELOAD from .const import DOMAIN, CONF_INVERT, CONF_RELOAD, PLATFORMS
from .hub import MegaD from .hub import MegaD
@@ -33,11 +33,7 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
PLATFORMS = [
"light",
"binary_sensor",
"sensor",
]
ALIVE_STATE = 'alive' ALIVE_STATE = 'alive'
DEF_ID = 'def' DEF_ID = 'def'
_POLL_TASKS = {} _POLL_TASKS = {}
@@ -80,12 +76,22 @@ async def async_setup(hass: HomeAssistant, config: dict):
return True return True
async def _add_mega(hass: HomeAssistant, id, data: dict): async def get_hub(hass, entry):
id = entry.data.get('id', entry.entry_id)
data = dict(entry.data)
data.update(entry.options or {})
data.update(id=id) data.update(id=id)
_mqtt = hass.data.get(mqtt.DOMAIN) _mqtt = hass.data.get(mqtt.DOMAIN)
if _mqtt is None: if _mqtt is None:
raise Exception('mqtt not configured, please configure mqtt first') raise Exception('mqtt not configured, please configure mqtt first')
hass.data[DOMAIN][id] = hub = MegaD(hass, **data, mqtt=_mqtt, lg=_LOGGER) hub = MegaD(hass, **data, mqtt=_mqtt, lg=_LOGGER)
return hub
async def _add_mega(hass: HomeAssistant, entry: ConfigEntry):
id = entry.data.get('id', entry.entry_id)
hub = await get_hub(hass, entry)
hass.data[DOMAIN][id] = hub
if not await hub.authenticate(): if not await hub.authenticate():
raise Exception("not authentificated") raise Exception("not authentificated")
mid = await hub.get_mqtt_id() mid = await hub.get_mqtt_id()
@@ -94,12 +100,9 @@ async def _add_mega(hass: HomeAssistant, id, data: dict):
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
id = entry.data.get('id', entry.entry_id) hub = await _add_mega(hass, entry)
data = dict(entry.data)
data.update(entry.options or {})
hub = await _add_mega(hass, id, data)
_hubs[entry.entry_id] = hub _hubs[entry.entry_id] = hub
_subs[entry.entry_id] = entry.add_update_listener(update) _subs[entry.entry_id] = entry.add_update_listener(updater)
for platform in PLATFORMS: for platform in PLATFORMS:
hass.async_create_task( hass.async_create_task(
@@ -111,26 +114,57 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
return True return True
async def update(hass: HomeAssistant, entry: ConfigEntry): async def updater(hass: HomeAssistant, entry: ConfigEntry):
"""
Обновляется конфигурация
:param hass:
:param entry:
:return:
"""
hub: MegaD = hass.data[DOMAIN][entry.data[CONF_ID]] hub: MegaD = hass.data[DOMAIN][entry.data[CONF_ID]]
hub.poll_interval = entry.options[CONF_SCAN_INTERVAL] hub.poll_interval = entry.options[CONF_SCAN_INTERVAL]
hub.port_to_scan = entry.options[CONF_PORT_TO_SCAN] hub.port_to_scan = entry.options.get(CONF_PORT_TO_SCAN, 0)
if entry.options[CONF_RELOAD]: entry.data = entry.options
await async_remove_entry(hass, entry) await async_remove_entry(hass, entry)
await async_setup_entry(hass, entry) await async_setup_entry(hass, entry)
return True return True
async def async_remove_entry(hass, entry) -> None: async def async_remove_entry(hass, entry) -> None:
"""Handle removal of an entry.""" """Handle removal of an entry."""
id = entry.data.get('id', entry.entry_id) id = entry.data.get('id', entry.entry_id)
hass.data[DOMAIN][id].unsubscribe_all() hub = hass.data[DOMAIN]
task: asyncio.Task = _POLL_TASKS.pop(id) if hub is None:
task.cancel() return
_LOGGER.debug(f'remove {id}')
_hubs.pop(entry.entry_id) _hubs.pop(entry.entry_id)
task: asyncio.Task = _POLL_TASKS.pop(id, None)
if task is None:
return
task.cancel()
if hub is None:
return
hub.unsubscribe_all()
unsub = _subs.pop(entry.entry_id) unsub = _subs.pop(entry.entry_id)
unsub() if unsub:
hass.data[DOMAIN].pop(id) unsub()
async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s to version 2", config_entry.version)
hub = await get_hub(hass, config_entry)
new = dict(config_entry.data)
if config_entry.version == 1:
cfg = await hub.get_config()
new.update(cfg)
_LOGGER.debug(f'new config: %s', new)
config_entry.data = new
config_entry.version = 2
_LOGGER.info("Migration to version %s successful", config_entry.version)
return True
async def _save_service(hass: HomeAssistant, call: ServiceCall): async def _save_service(hass: HomeAssistant, call: ServiceCall):

View File

@@ -62,15 +62,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
hub: MegaD = hass.data['mega'][mid] hub: MegaD = hass.data['mega'][mid]
devices = [] devices = []
async def scan(): for port, cfg in config_entry.data.get('binary_sensor', {}).items():
async for port, pty, m in hub.scan_ports(): hub.lg.debug(f'add binary_sensor on port %s', port)
if pty == "0": sensor = MegaBinarySensor(mega_id=mid, port=port, config_entry=config_entry)
sensor = MegaBinarySensor(mega_id=mid, port=port, config_entry=config_entry) devices.append(sensor)
devices.append(sensor) async_add_devices(devices)
async_add_devices(devices)
asyncio.create_task(scan())
class MegaBinarySensor(BinarySensorEntity, BaseMegaEntity): class MegaBinarySensor(BinarySensorEntity, BaseMegaEntity):

View File

@@ -8,8 +8,8 @@ from homeassistant import config_entries, core
from homeassistant.components import mqtt from homeassistant.components import mqtt
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_ID, CONF_PASSWORD, CONF_SCAN_INTERVAL from homeassistant.const import CONF_HOST, CONF_ID, CONF_PASSWORD, CONF_SCAN_INTERVAL
from homeassistant.core import callback from homeassistant.core import callback, HomeAssistant
from .const import DOMAIN, CONF_PORT_TO_SCAN, CONF_RELOAD # pylint:disable=unused-import from .const import DOMAIN, CONF_PORT_TO_SCAN, CONF_RELOAD, PLATFORMS # pylint:disable=unused-import
from .hub import MegaD from .hub import MegaD
from . import exceptions from . import exceptions
@@ -26,6 +26,16 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
) )
async def get_hub(hass: HomeAssistant, data):
_mqtt = hass.data.get(mqtt.DOMAIN)
if not isinstance(_mqtt, mqtt.MQTT):
raise exceptions.MqttNotConfigured("mqtt must be configured first")
hub = MegaD(hass, **data, lg=_LOGGER, mqtt=_mqtt)
if not await hub.authenticate():
raise exceptions.InvalidAuth
return hub
async def validate_input(hass: core.HomeAssistant, data): async def validate_input(hass: core.HomeAssistant, data):
"""Validate the user input allows us to connect. """Validate the user input allows us to connect.
@@ -33,12 +43,7 @@ async def validate_input(hass: core.HomeAssistant, data):
""" """
if data[CONF_ID] in hass.data.get(DOMAIN, []): if data[CONF_ID] in hass.data.get(DOMAIN, []):
raise exceptions.DuplicateId('duplicate_id') raise exceptions.DuplicateId('duplicate_id')
_mqtt = hass.data.get(mqtt.DOMAIN) hub = await get_hub(hass, data)
if not isinstance(_mqtt, mqtt.MQTT):
raise exceptions.MqttNotConfigured("mqtt must be configured first")
hub = MegaD(hass, **data, lg=_LOGGER, mqtt=_mqtt)
if not await hub.authenticate():
raise exceptions.InvalidAuth
return hub return hub
@@ -46,7 +51,7 @@ async def validate_input(hass: core.HomeAssistant, data):
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for mega.""" """Handle a config flow for mega."""
VERSION = 1 VERSION = 2
CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
@@ -59,7 +64,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {} errors = {}
try: try:
await validate_input(self.hass, user_input) hub = await validate_input(self.hass, user_input)
config = await hub.get_config()
hub.lg.debug(f'config loaded: %s', config)
config.update(user_input)
return self.async_create_entry(
title=user_input.get(CONF_ID, user_input[CONF_HOST]),
data=config,
)
except exceptions.CannotConnect: except exceptions.CannotConnect:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except exceptions.InvalidAuth: except exceptions.InvalidAuth:
@@ -69,11 +81,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
errors[CONF_ID] = str(exc) errors[CONF_ID] = str(exc)
else:
return self.async_create_entry(
title=user_input.get(CONF_ID, user_input[CONF_HOST]),
data=user_input,
)
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
@@ -92,10 +99,22 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
async def async_step_init(self, user_input=None): async def async_step_init(self, user_input=None):
"""Manage the options.""" """Manage the options."""
if user_input is not None: if user_input is not None:
reload = user_input.pop(CONF_RELOAD)
cfg = dict(self.config_entry.data)
cfg.update(user_input)
hub = await get_hub(self.hass, self.config_entry.data)
if reload:
new = await hub.get_config()
_LOGGER.debug(f'new config: %s', new)
cfg = dict(self.config_entry.data)
for x in PLATFORMS:
cfg.pop(x, None)
cfg.update(new)
return self.async_create_entry( return self.async_create_entry(
title='', title='',
data={**user_input, **{CONF_ID: self.config_entry.data[CONF_ID]}}, data=cfg,
) )
e = self.config_entry.data e = self.config_entry.data
ret = self.async_show_form( ret = self.async_show_form(
@@ -103,7 +122,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
data_schema=vol.Schema({ data_schema=vol.Schema({
vol.Optional(CONF_SCAN_INTERVAL, default=e[CONF_SCAN_INTERVAL]): int, vol.Optional(CONF_SCAN_INTERVAL, default=e[CONF_SCAN_INTERVAL]): int,
vol.Optional(CONF_PORT_TO_SCAN, default=e.get(CONF_PORT_TO_SCAN, 0)): int, vol.Optional(CONF_PORT_TO_SCAN, default=e.get(CONF_PORT_TO_SCAN, 0)): int,
# vol.Optional(CONF_RELOAD, default=False): bool, vol.Optional(CONF_RELOAD, default=False): bool,
# vol.Optional(CONF_INVERT, default=''): str, # vol.Optional(CONF_INVERT, default=''): str,
}), }),
) )

View File

@@ -11,4 +11,9 @@ W1 = 'w1'
W1BUS = 'w1bus' W1BUS = 'w1bus'
CONF_PORT_TO_SCAN = 'port_to_scan' CONF_PORT_TO_SCAN = 'port_to_scan'
CONF_RELOAD = 'reload' CONF_RELOAD = 'reload'
CONF_INVERT = 'invert' CONF_INVERT = 'invert'
PLATFORMS = [
"light",
"binary_sensor",
"sensor",
]

View File

@@ -1,6 +1,7 @@
import asyncio import asyncio
import json import json
import logging import logging
from collections import defaultdict
from datetime import datetime from datetime import datetime
from functools import wraps from functools import wraps
@@ -9,10 +10,27 @@ import typing
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from homeassistant.components import mqtt from homeassistant.components import mqtt
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from .const import TEMP, HUM
from .exceptions import CannotConnect from .exceptions import CannotConnect
import re
TEMP_PATT = re.compile(r'temp:([01234567890\.]+)')
HUM_PATT = re.compile(r'hum:([01234567890\.]+)')
PATTERNS = {
TEMP: TEMP_PATT,
HUM: HUM_PATT,
}
UNITS = {
TEMP: '°C',
HUM: '%'
}
CLASSES = {
TEMP: DEVICE_CLASS_TEMPERATURE,
HUM: DEVICE_CLASS_HUMIDITY
}
class MegaD: class MegaD:
"""MegaD Hub""" """MegaD Hub"""
@@ -275,3 +293,32 @@ class MegaD:
ret = await self.scan_port(x) ret = await self.scan_port(x)
if ret: if ret:
yield [x, *ret] yield [x, *ret]
async def get_config(self):
ret = defaultdict(lambda: defaultdict(list))
async for port, pty, m in self.scan_ports():
if pty == "0":
ret['binary_sensor'][port].append({})
elif pty == "1" and m in ['0', '1']:
ret['light'][port].append({'dimmer': m == '1'})
elif pty == '3':
values = await self.get_port(port)
self.lg.debug(f'values: %s', values)
if values is None:
self.lg.warning(f'port {port} is of type sensor but did not respond, skipping it')
continue
if isinstance(values, str) and TEMP_PATT.search(values):
values = {TEMP: values}
elif not isinstance(values, dict):
values = {None: values}
for key in values:
self.lg.debug(f'add sensor {key}')
ret['sensor'][port].append(dict(
key=key,
patt=PATTERNS.get(key),
unit_of_measurement=UNITS.get(key, UNITS[TEMP]),
# TODO: make other units, make options in config flow
device_class=CLASSES.get(key, CLASSES[TEMP]),
id_suffix=key,
))
return ret

View File

@@ -78,14 +78,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
hub: MegaD = hass.data['mega'][mid] hub: MegaD = hass.data['mega'][mid]
devices = [] devices = []
async def scan_ports(): for port, cfg in config_entry.data.get('light', {}).items():
async for port, pty, m in hub.scan_ports(): for data in cfg:
if pty == "1" and m in ['0', '1']: hub.lg.debug(f'add light on port %s with data %s', port, data)
light = MegaLight(mega_id=mid, port=port, dimmer=m == '1', config_entry=config_entry) light = MegaLight(mega_id=mid, port=port, config_entry=config_entry, **data)
devices.append(light) devices.append(light)
async_add_devices(devices) async_add_devices(devices)
asyncio.create_task(scan_ports())
class MegaLight(LightEntity, BaseMegaEntity): class MegaLight(LightEntity, BaseMegaEntity):

View File

@@ -88,34 +88,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
hub: MegaD = hass.data['mega'][mid] hub: MegaD = hass.data['mega'][mid]
devices = [] devices = []
async def scan(): for port, cfg in config_entry.data.get('sensor', {}).items():
async for port, pty, m in hub.scan_ports(): for data in cfg:
if pty == "3": hub.lg.debug(f'add sensor on port %s with data %s', port, data)
values = await hub.get_port(port) sensor = Mega1WSensor(
lg.debug(f'values: %s', values) mega_id=mid,
if values is None: port=port,
continue config_entry=config_entry,
if isinstance(values, str) and TEMP_PATT.search(values): **data,
values = {TEMP: values} )
elif not isinstance(values, dict): devices.append(sensor)
values = {None: values}
for key in values:
hub.lg.debug(f'add sensor {W1}:{key}')
sensor = _make_entity(
mid=mid,
port=port,
conf={
CONF_TYPE: W1,
CONF_KEY: key,
},
config_entry=config_entry,
)
devices.append(sensor)
hub.sensors.append(sensor)
async_add_devices(devices) async_add_devices(devices)
asyncio.create_task(scan())
class Mega1WSensor(BaseMegaEntity): class Mega1WSensor(BaseMegaEntity):