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.components import mqtt
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
@@ -33,11 +33,7 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
PLATFORMS = [
"light",
"binary_sensor",
"sensor",
]
ALIVE_STATE = 'alive'
DEF_ID = 'def'
_POLL_TASKS = {}
@@ -80,12 +76,22 @@ async def async_setup(hass: HomeAssistant, config: dict):
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)
_mqtt = hass.data.get(mqtt.DOMAIN)
if _mqtt is None:
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():
raise Exception("not authentificated")
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):
id = entry.data.get('id', entry.entry_id)
data = dict(entry.data)
data.update(entry.options or {})
hub = await _add_mega(hass, id, data)
hub = await _add_mega(hass, entry)
_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:
hass.async_create_task(
@@ -111,26 +114,57 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
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.poll_interval = entry.options[CONF_SCAN_INTERVAL]
hub.port_to_scan = entry.options[CONF_PORT_TO_SCAN]
if entry.options[CONF_RELOAD]:
await async_remove_entry(hass, entry)
await async_setup_entry(hass, entry)
hub.port_to_scan = entry.options.get(CONF_PORT_TO_SCAN, 0)
entry.data = entry.options
await async_remove_entry(hass, entry)
await async_setup_entry(hass, entry)
return True
async def async_remove_entry(hass, entry) -> None:
"""Handle removal of an entry."""
id = entry.data.get('id', entry.entry_id)
hass.data[DOMAIN][id].unsubscribe_all()
task: asyncio.Task = _POLL_TASKS.pop(id)
task.cancel()
hub = hass.data[DOMAIN]
if hub is None:
return
_LOGGER.debug(f'remove {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()
hass.data[DOMAIN].pop(id)
if unsub:
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):

View File

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

View File

@@ -8,8 +8,8 @@ from homeassistant import config_entries, core
from homeassistant.components import mqtt
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_ID, CONF_PASSWORD, CONF_SCAN_INTERVAL
from homeassistant.core import callback
from .const import DOMAIN, CONF_PORT_TO_SCAN, CONF_RELOAD # pylint:disable=unused-import
from homeassistant.core import callback, HomeAssistant
from .const import DOMAIN, CONF_PORT_TO_SCAN, CONF_RELOAD, PLATFORMS # pylint:disable=unused-import
from .hub import MegaD
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):
"""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, []):
raise exceptions.DuplicateId('duplicate_id')
_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
hub = await get_hub(hass, data)
return hub
@@ -46,7 +51,7 @@ async def validate_input(hass: core.HomeAssistant, data):
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for mega."""
VERSION = 1
VERSION = 2
CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED
async def async_step_user(self, user_input=None):
@@ -59,7 +64,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors = {}
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:
errors["base"] = "cannot_connect"
except exceptions.InvalidAuth:
@@ -69,11 +81,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
except Exception as exc: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
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(
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):
"""Manage the options."""
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(
title='',
data={**user_input, **{CONF_ID: self.config_entry.data[CONF_ID]}},
data=cfg,
)
e = self.config_entry.data
ret = self.async_show_form(
@@ -103,7 +122,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
data_schema=vol.Schema({
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_RELOAD, default=False): bool,
vol.Optional(CONF_RELOAD, default=False): bool,
# vol.Optional(CONF_INVERT, default=''): str,
}),
)

View File

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

View File

@@ -1,6 +1,7 @@
import asyncio
import json
import logging
from collections import defaultdict
from datetime import datetime
from functools import wraps
@@ -9,10 +10,27 @@ import typing
from bs4 import BeautifulSoup
from homeassistant.components import mqtt
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from .const import TEMP, HUM
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:
"""MegaD Hub"""
@@ -275,3 +293,32 @@ class MegaD:
ret = await self.scan_port(x)
if 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]
devices = []
async def scan_ports():
async for port, pty, m in hub.scan_ports():
if pty == "1" and m in ['0', '1']:
light = MegaLight(mega_id=mid, port=port, dimmer=m == '1', config_entry=config_entry)
devices.append(light)
async_add_devices(devices)
asyncio.create_task(scan_ports())
for port, cfg in config_entry.data.get('light', {}).items():
for data in cfg:
hub.lg.debug(f'add light on port %s with data %s', port, data)
light = MegaLight(mega_id=mid, port=port, config_entry=config_entry, **data)
devices.append(light)
async_add_devices(devices)
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]
devices = []
async def scan():
async for port, pty, m in hub.scan_ports():
if pty == "3":
values = await hub.get_port(port)
lg.debug(f'values: %s', values)
if values is None:
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:
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)
for port, cfg in config_entry.data.get('sensor', {}).items():
for data in cfg:
hub.lg.debug(f'add sensor on port %s with data %s', port, data)
sensor = Mega1WSensor(
mega_id=mid,
port=port,
config_entry=config_entry,
**data,
)
devices.append(sensor)
async_add_devices(devices)
asyncio.create_task(scan())
async_add_devices(devices)
class Mega1WSensor(BaseMegaEntity):