Compare commits

...

18 Commits

Author SHA1 Message Date
Andrey
8539b8a6ee fix eng lang in options 2021-01-18 09:32:25 +03:00
Andrey
405fbe4777 fix eng lang in options 2021-01-18 09:28:38 +03:00
Andrey
ed4928011b add refresh devices 2021-01-15 16:05:10 +03:00
Andrey
ed9011a6e1 edit readme 2021-01-15 09:23:32 +03:00
Andrey
c7e8bcb83e edit readme 2021-01-15 09:20:52 +03:00
Andrey
21fd00083c edit readme 2021-01-15 09:13:12 +03:00
Andrey
c4f4510941 add device registry 2021-01-15 09:10:42 +03:00
Andrey
254015be4c edit readme 2021-01-15 08:45:27 +03:00
Andrey
adb65529a2 hotfix 2021-01-14 23:05:30 +03:00
Andrey
768d46d952 hotfix 2021-01-14 22:48:07 +03:00
Andrey
359c6b99b7 hotfix 2021-01-14 22:27:07 +03:00
Andrey
79dc46226a hotfix 2021-01-14 22:19:56 +03:00
Andrey
012d12437b hotfix 2021-01-14 21:40:19 +03:00
Andrey
7063575957 hotfix 2021-01-14 21:35:15 +03:00
Andrey
6a43198d81 hotfix 2021-01-14 21:33:44 +03:00
Andrey
242386bfe8 edit readme 2021-01-14 21:25:48 +03:00
Andrey
34d31d2879 edit readme 2021-01-14 21:07:30 +03:00
Andrey
6a02a7e98c башфикс сервисов, ускорение загрузки 2021-01-14 20:46:05 +03:00
10 changed files with 300 additions and 152 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,29 +114,61 @@ 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:
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):
mega_id = call.data['mega_id'] mega_id = call.data.get('mega_id')
if mega_id: if mega_id:
hub: MegaD = hass.data[DOMAIN][mega_id] hub: MegaD = hass.data[DOMAIN][mega_id]
await hub.save() await hub.save()
@@ -144,8 +179,8 @@ async def _save_service(hass: HomeAssistant, call: ServiceCall):
@bind_hass @bind_hass
async def _get_port(hass: HomeAssistant, call: ServiceCall): async def _get_port(hass: HomeAssistant, call: ServiceCall):
port = call.data['port'] port = call.data.get('port')
mega_id = call.data['mega_id'] mega_id = call.data.get('mega_id')
if mega_id: if mega_id:
hub: MegaD = hass.data[DOMAIN][mega_id] hub: MegaD = hass.data[DOMAIN][mega_id]
if port is None: if port is None:
@@ -162,9 +197,9 @@ async def _get_port(hass: HomeAssistant, call: ServiceCall):
@bind_hass @bind_hass
async def _run_cmd(hass: HomeAssistant, call: ServiceCall): async def _run_cmd(hass: HomeAssistant, call: ServiceCall):
port = call.data['port'] port = call.data.get('port')
mega_id = call.data['mega_id'] mega_id = call.data.get('mega_id')
cmd = call.data['cmd'] cmd = call.data.get('cmd')
if mega_id: if mega_id:
hub: MegaD = hass.data[DOMAIN][mega_id] hub: MegaD = hass.data[DOMAIN][mega_id]
await hub.send_command(port=port, cmd=cmd) await hub.send_command(port=port, cmd=cmd)

View File

@@ -1,5 +1,6 @@
"""Platform for light integration.""" """Platform for light integration."""
import logging import logging
import asyncio
import voluptuous as vol import voluptuous as vol
@@ -60,11 +61,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
mid = config_entry.data[CONF_ID] mid = config_entry.data[CONF_ID]
hub: MegaD = hass.data['mega'][mid] hub: MegaD = hass.data['mega'][mid]
devices = [] devices = []
async for port, pty, m in hub.scan_ports():
if pty == "0":
sensor = MegaBinarySensor(mega_id=mid, port=port)
devices.append(sensor)
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) async_add_devices(devices)

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(

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

@@ -3,6 +3,7 @@ import asyncio
import json import json
import logging import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import State from homeassistant.core import State
from .hub import MegaD from .hub import MegaD
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
@@ -19,12 +20,14 @@ class BaseMegaEntity(RestoreEntity):
self, self,
mega_id: str, mega_id: str,
port: int, port: int,
config_entry: ConfigEntry = None,
id_suffix=None, id_suffix=None,
name=None, name=None,
unique_id=None unique_id=None,
): ):
self._state: State = None self._state: State = None
self.port = port self.port = port
self.config_entry = config_entry
self._mega_id = mega_id self._mega_id = mega_id
self._lg = None self._lg = None
self._unique_id = unique_id or f"mega_{mega_id}_{port}" + \ self._unique_id = unique_id or f"mega_{mega_id}_{port}" + \
@@ -32,6 +35,23 @@ class BaseMegaEntity(RestoreEntity):
self._name = name or f"{mega_id}_{port}" + \ self._name = name or f"{mega_id}_{port}" + \
(f"_{id_suffix}" if id_suffix else "") (f"_{id_suffix}" if id_suffix else "")
@property
def device_info(self):
return {
"identifiers": {
# Serial numbers are unique identifiers within a specific domain
(DOMAIN, f'{self._mega_id}', self.port),
},
"config_entries": [
self.config_entry,
],
"name": f'port {self.port}',
"manufacturer": 'ab-log.ru',
# "model": self.light.productname,
# "sw_version": self.light.swversion,
"via_device": (DOMAIN, self._mega_id),
}
@property @property
def lg(self) -> logging.Logger: def lg(self) -> logging.Logger:
if self._lg is None: if self._lg is None:

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"""
@@ -140,36 +158,47 @@ class MegaD:
async def save(self): async def save(self):
await self.send_command(cmd='s') await self.send_command(cmd='s')
async def get_port(self, port, get_value=False): async def get_port(self, port):
if get_value: """
ftr = asyncio.get_event_loop().create_future() Опрашивает порт с помощью mqtt. Ждет ответ, возвращает ответ.
def cb(msg): :param port:
try: :return:
ftr.set_result(json.loads(msg.payload).get('value')) """
except Exception as exc: ftr = asyncio.get_event_loop().create_future()
self.lg.warning(f'could not parse {msg.payload}: {exc}')
ftr.set_result(None) def cb(msg):
try:
if '"value":NA' in msg.payload.decode():
if not ftr.done():
ftr.set_result(None)
return
ret = json.loads(msg.payload).get('value')
if not ftr.done():
ftr.set_result(ret)
except Exception as exc:
ret = None
self.lg.exception(f'while parsing response from port {port}: {msg.payload}')
ftr.set_result(None)
self.lg.debug(
f'port: %s response: %s', port, ret
)
async with self.lck:
unsub = await self.mqtt.async_subscribe( unsub = await self.mqtt.async_subscribe(
topic=f'{self.mqtt_id}/{port}', topic=f'{self.mqtt_id}/{port}',
msg_callback=cb, msg_callback=cb,
qos=1, qos=1,
) )
self.lg.debug(
f'get port: %s', port
)
async with self.lck:
await self.mqtt.async_publish(
topic=f'{self.mqtt_id}/cmd',
payload=f'get:{port}',
qos=0,
retain=False,
)
await asyncio.sleep(0.1)
if get_value:
try: try:
await self.mqtt.async_publish(
topic=f'{self.mqtt_id}/cmd',
payload=f'get:{port}',
qos=1,
retain=False,
)
return await asyncio.wait_for(ftr, timeout=2) return await asyncio.wait_for(ftr, timeout=2)
except asyncio.TimeoutError: except asyncio.TimeoutError:
self.lg.warning(f'timeout on port {port}') self.lg.warning(f'timeout on port {port}')
@@ -227,39 +256,69 @@ class MegaD:
return await req.text() return await req.text()
async def scan_port(self, port): async def scan_port(self, port):
if port in self._scanned: async with self.lck:
return self._scanned[port] if port in self._scanned:
url = f'http://{self.host}/{self.sec}/?pt={port}' return self._scanned[port]
self.lg.debug( url = f'http://{self.host}/{self.sec}/?pt={port}'
f'scan port %s: %s', port, url self.lg.debug(
) f'scan port %s: %s', port, url
async with aiohttp.request('get', url) as req: )
html = await req.text() async with aiohttp.request('get', url) as req:
tree = BeautifulSoup(html, features="lxml") html = await req.text()
pty = tree.find('select', attrs={'name': 'pty'}) tree = BeautifulSoup(html, features="lxml")
if pty is None: pty = tree.find('select', attrs={'name': 'pty'})
return if pty is None:
else:
pty = pty.find(selected=True)
if pty:
pty = pty['value']
else:
return return
if pty in ['0', '1']: else:
m = tree.find('select', attrs={'name': 'm'}) pty = pty.find(selected=True)
if m: if pty:
m = m.find(selected=True)['value'] pty = pty['value']
self._scanned[port] = (pty, m) else:
return pty, m return
elif pty == '3': if pty in ['0', '1']:
m = tree.find('select', attrs={'name': 'd'}) m = tree.find('select', attrs={'name': 'm'})
if m: if m:
m = m.find(selected=True)['value'] m = m.find(selected=True)['value']
self._scanned[port] = (pty, m) self._scanned[port] = (pty, m)
return pty, m return pty, m
elif pty == '3':
m = tree.find('select', attrs={'name': 'd'})
if m:
m = m.find(selected=True)['value']
self._scanned[port] = (pty, m)
return pty, m
async def scan_ports(self,): async def scan_ports(self,):
for x in range(38): for x in range(38):
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

@@ -1,6 +1,6 @@
"""Platform for light integration.""" """Platform for light integration."""
import logging import logging
import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.components.light import ( from homeassistant.components.light import (
@@ -77,9 +77,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
mid = config_entry.data[CONF_ID] mid = config_entry.data[CONF_ID]
hub: MegaD = hass.data['mega'][mid] hub: MegaD = hass.data['mega'][mid]
devices = [] devices = []
async for port, pty, m in hub.scan_ports():
if pty == "1" and m in ['0', '1']: for port, cfg in config_entry.data.get('light', {}).items():
light = MegaLight(mega_id=mid, port=port, dimmer=m == '1') 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) devices.append(light)
async_add_devices(devices) async_add_devices(devices)

View File

@@ -1,4 +1,5 @@
"""Platform for light integration.""" """Platform for light integration."""
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@@ -68,7 +69,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
return True return True
def _make_entity(mid: str, port: int, conf: dict): def _make_entity(config_entry, mid: str, port: int, conf: dict):
key = conf[CONF_KEY] key = conf[CONF_KEY]
return Mega1WSensor( return Mega1WSensor(
key=key, key=key,
@@ -77,7 +78,8 @@ def _make_entity(mid: str, port: int, conf: dict):
patt=PATTERNS.get(key), patt=PATTERNS.get(key),
unit_of_measurement=UNITS.get(key, UNITS[TEMP]), # TODO: make other units, make options in config flow unit_of_measurement=UNITS.get(key, UNITS[TEMP]), # TODO: make other units, make options in config flow
device_class=CLASSES.get(key, CLASSES[TEMP]), device_class=CLASSES.get(key, CLASSES[TEMP]),
id_suffix=key id_suffix=key,
config_entry=config_entry
) )
@@ -85,27 +87,17 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
mid = config_entry.data[CONF_ID] mid = config_entry.data[CONF_ID]
hub: MegaD = hass.data['mega'][mid] hub: MegaD = hass.data['mega'][mid]
devices = [] devices = []
async for port, pty, m in hub.scan_ports():
if pty == "3": for port, cfg in config_entry.data.get('sensor', {}).items():
values = await hub.get_port(port, get_value=True) for data in cfg:
lg.debug(f'values: %s', values) hub.lg.debug(f'add sensor on port %s with data %s', port, data)
if values is None: sensor = Mega1WSensor(
continue mega_id=mid,
if isinstance(values, str) and TEMP_PATT.search(values): port=port,
values = {TEMP: values} config_entry=config_entry,
elif not isinstance(values, dict): **data,
values = {None: values} )
for key in values: devices.append(sensor)
hub.lg.debug(f'add sensor {W1}:{key}')
sensor = _make_entity(
mid=mid,
port=port,
conf={
CONF_TYPE: W1,
CONF_KEY: key,
})
devices.append(sensor)
hub.sensors.append(sensor)
async_add_devices(devices) async_add_devices(devices)

View File

@@ -23,5 +23,16 @@
} }
} }
}, },
"options": {
"step": {
"init": {
"data": {
"scan_interval": "Scan interval (sec) (used for aliveness and sensors)",
"port_to_scan": "Port to poll aliveness (needed only if no sensors used)",
"reload": "Reload objects"
}
}
}
},
"title": "mega" "title": "mega"
} }

View File

@@ -1,50 +1,54 @@
# MegaD HomeAssistant custom component # MegaD HomeAssistant integration
Интеграция с [MegaD-2561](https://www.ab-log.ru/smart-house/ethernet/megad-2561) Интеграция с [MegaD-2561](https://www.ab-log.ru/smart-house/ethernet/megad-2561)
## Основные особенности: ## Основные особенности:
- Настройка в веб-интерфейсе
- Все порты автоматически добавляются как устройства (для обычных релейных выходов создается - Все порты автоматически добавляются как устройства (для обычных релейных выходов создается
`light`, для шим - `light` с поддержкой яркости, для цифровых входов `binary_sensor`, для температурных датчиков `light`, для шим - `light` с поддержкой яркости, для цифровых входов `binary_sensor`, для датчиков
`sensor`) `sensor`)
- Возможность работы с несколькими megad - Возможность работы с несколькими megad
- Обратная связь по mqtt - Обратная связь по mqtt
- Команды выполняются друг за другом без конкурентного доступа к ресурсам megad - Команды выполняются друг за другом без конкурентного доступа к ресурсам megad, это дает гарантии надежного исполнения
- Поддержка температурных датчиков в режиме шины большого кол-ва команд (например в сценах). Каждая следующая команда отправляется только после получения ответа о
выполнении предыдущей.
## Устройства ## Зависимости
Поддерживаются устройства: light, switch, binary_sensor, sensor. light может работать как диммер **Важно!!** Перед использованием необходимо настроить интеграцию mqtt в HomeAssistant
Для максимальной совместимости необходимо обновить ваш контроллер до последней версии, тк были важные обновления в части
mqtt
## Установка ## Установка
Рекомендованнй способ - через [HACS](https://hacs.xyz/docs/installation/installation). Рекомендованный способ с поддержкой обновлений - [HACS](https://hacs.xyz/docs/installation/installation):
После установки HACS, нужно перейти в меню hacs -> integrations, далее в верхнем правом углу
нажать три точки, где будет `Custom repositories`, открыть, нажать add и добавить `https://github.com/andvikt/mega_hacs.git` HACS - Integrations - Explore, в поиске ищем MegaD.
Альтернативный способ установки: Альтернативный способ установки:
```shell ```shell
# из папки с конфигом # из папки с конфигом
wget -q -O - https://raw.githubusercontent.com/andvikt/mega_hacs/master/install.sh | bash - wget -q -O - https://raw.githubusercontent.com/andvikt/mega_hacs/master/install.sh | bash -
``` ```
Перезагрузить HA Не забываем перезагрузить HA
Для обновления повторить ## Настройка
## Зависимости
Перед использованием необходимо настроить интеграцию mqtt в HomeAssistant
## Настройка из веб-интерфейса
`Настройки` -> `Интеграции` -> `Добавить интеграцию` в поиске ищем mega `Настройки` -> `Интеграции` -> `Добавить интеграцию` в поиске ищем mega
Все имеющиеся у вас порты будут настроены автоматически.
Вы можете менять названия, иконки и entity_id так же из интерфейса.
## Сервисы ## Сервисы
Все сервисы доступны в меню разработчика с описанием и примерами использования
```yaml ```yaml
save: mega.save:
description: Сохраняет текущее состояние портов (?cmd=s) description: Сохраняет текущее состояние портов (?cmd=s)
fields: fields:
mega_id: mega_id:
description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги
example: "mega" example: "mega"
get_port: mega.get_port:
description: Запросить текущий статус порта (или всех) description: Запросить текущий статус порта (или всех)
fields: fields:
mega_id: mega_id:
@@ -54,10 +58,9 @@ get_port:
description: Номер порта (если не заполнять, будут запрошены все порты сразу) description: Номер порта (если не заполнять, будут запрошены все порты сразу)
example: 1 example: 1
run_cmd: mega.run_cmd:
description: Выполнить любую произвольную команду description: Выполнить любую произвольную команду
fields: fields:
mega_id: mega_id:
description: ID меги description: ID меги
example: "mega" example: "mega"
@@ -67,11 +70,12 @@ run_cmd:
cmd: cmd:
description: Любая поддерживаемая мегой команда description: Любая поддерживаемая мегой команда
example: "1:0" example: "1:0"
``` ```
## Отладка ## Отладка
Если возникают проблемы, можно включить детальный лог, для этого в конфиг добавить: Интеграция находится в активной разработке, при возникновении проблем [заводите issue](https://github.com/andvikt/mega_hacs/issues/new/choose)
Просьба прикладывать детальный лог, который можно включить в конфиге так:
```yaml ```yaml
logger: logger:
default: info default: info