Compare commits

..

17 Commits

Author SHA1 Message Date
Andrey
49fcf880d9 add i2c sensors 2021-03-03 12:01:02 +03:00
Andrey
289f52ef73 fix errors 2021-02-28 22:27:57 +03:00
Andrey
ce589c97b9 fix errors 2021-02-28 22:22:17 +03:00
Andrey
22a6f8f444 fix errors 2021-02-28 22:06:36 +03:00
Andrey
9a53de1d5d fix errors 2021-02-28 22:02:22 +03:00
Andrey
bd8b07dd90 fix errors 2021-02-28 21:52:34 +03:00
Andrey
d9b6ba3a50 fix errors 2021-02-28 21:52:06 +03:00
Andrey
1042592a31 fix errors 2021-02-28 21:33:26 +03:00
Andrey
137eb8b6ba fix errors 2021-02-28 21:15:48 +03:00
Andrey
a2f412b89e fix errors 2021-02-28 20:16:04 +03:00
Andrey
8fa14cdbc5 fix errors 2021-02-28 20:15:47 +03:00
Andrey
fc17b82021 support response for extenders 2021-02-28 15:01:39 +03:00
Andrey
1aeaabfb3c fix errors 2021-02-28 14:50:47 +03:00
Andrey
a0bd8acac0 fix errors 2021-02-28 14:49:38 +03:00
andvikt
c48a3632d2 Update http.py 2021-02-28 13:04:00 +03:00
Andrey
e06ba65ead fix errors 2021-02-28 09:49:16 +03:00
Andrey
22720a27bd fix errors 2021-02-28 09:46:06 +03:00
14 changed files with 489 additions and 96 deletions

View File

@@ -1,43 +1,121 @@
import re
from urllib.parse import urlparse, parse_qsl
from bs4 import BeautifulSoup
page = '''
<html><head></head><body><a href="/sec/?pt=33">Back</a><br>0x15 - <a href="/sec/?pt=33&amp;scl=32&amp;i2c_dev=t67xx">T67XX</a><br>0x40 - <a href="/sec/?pt=33&amp;scl=32&amp;i2c_dev=htu21d">HTU21D</a>/PCA9685/HM3301<br>0x4a - <a href="/sec/?pt=33&amp;scl=32&amp;i2c_dev=max44009">MAX44009</a><br>
</body></html>
'''
from urllib.parse import parse_qsl, urlparse
from bs4 import BeautifulSoup
from homeassistant.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_PRESSURE,
)
PATT_FW = re.compile(r'fw:\s(.+)\)')
data = """
<html><head></head><body>MegaD-2561 by <a href="http://ab-log.ru">ab-log.ru</a> (fw: 4.48b7)<br><a href="/sec/?cf=1">Config</a><br>-- MODS --<br><a href="/sec/?cf=3">XP1</a><br><a href="/sec/?cf=4">XP2</a><br>-- XT2 --<br><a href="/sec/?pt=30">P30 - OUT</a><br><a href="/sec/?pt=31">P31 - OUT</a><br><a href="/sec/?pt=32">P32 - IN</a><br><a href="/sec/?pt=33">P33 - I2C/SCL</a><br><a href="/sec/?pt=34">P34 - DS</a><br><a href="/sec/?pt=35">P35 - NC</a><br>-- XP5/6 --<br><a href="/sec/?pt=36">P36 - ADC</a><br><a href="/sec/?pt=37">P37 - NC</a></body></html>
<head></head>
<body>MegaD-2561 by <a href="http://ab-log.ru">ab-log.ru</a> (fw: 4.48b7)<br><a href="/sec/?cf=1">Config</a><br>-- MODS --<br><a href="/sec/?cf=3">XP1</a><br><a href="/sec/?cf=4">XP2</a><br>-- XT2 --<br><a href="/sec/?pt=30">P30 - OUT</a><br><a href="/sec/?pt=31">P31 - OUT</a><br><a href="/sec/?pt=32">P32 - IN</a><br><a href="/sec/?pt=33">P33 - I2C/SCL</a><br><a href="/sec/?pt=34">P34 - DS</a><br><a href="/sec/?pt=35">P35 - NC</a><br>-- XP5/6 --<br><a href="/sec/?pt=36">P36 - ADC</a><br><a href="/sec/?pt=37">P37 - NC</a></body>
MegaD-2561 by
<a href="http://ab-log.ru">ab-log.ru</a>
(fw: 4.48b7)
<br>
<a href="/sec/?cf=1">Config</a>
<br>
-- MODS --
<br>
<a href="/sec/?cf=3">XP1</a>
<br>
<a href="/sec/?cf=4">XP2</a>
<br>
-- XT2 --
<br>
<a href="/sec/?pt=30">P30 - OUT</a>
<br>
<a href="/sec/?pt=31">P31 - OUT</a>
<br>
<a href="/sec/?pt=32">P32 - IN</a>
<br>
<a href="/sec/?pt=33">P33 - I2C/SCL</a>
<br>
<a href="/sec/?pt=34">P34 - DS</a>
<br>
<a href="/sec/?pt=35">P35 - NC</a>
<br>
-- XP5/6 --
<br>
<a href="/sec/?pt=36">P36 - ADC</a>
<br>
<a href="/sec/?pt=37">P37 - NC</a>
<body>MegaD-2561 by <a href="http://ab-log.ru">ab-log.ru</a> (fw: 4.48b7)<br><a href="/sec/?cf=1">Config</a><br>-- MODS --<br><a href="/sec/?cf=3">XP1</a><br><a href="/sec/?cf=4">XP2</a><br>-- XT2 --<br><a href="/sec/?pt=30">P30 - OUT</a><br><a href="/sec/?pt=31">P31 - OUT</a><br><a href="/sec/?pt=32">P32 - IN</a><br><a href="/sec/?pt=33">P33 - I2C/SCL</a><br><a href="/sec/?pt=34">P34 - DS</a><br><a href="/sec/?pt=35">P35 - NC</a><br>-- XP5/6 --<br><a href="/sec/?pt=36">P36 - ADC</a><br><a href="/sec/?pt=37">P37 - NC</a></body>
<html><head></head><body>MegaD-2561 by <a href="http://ab-log.ru">ab-log.ru</a> (fw: 4.48b7)<br><a href="/sec/?cf=1">Config</a><br>-- MODS --<br><a href="/sec/?cf=3">XP1</a><br><a href="/sec/?cf=4">XP2</a><br>-- XT2 --<br><a href="/sec/?pt=30">P30 - OUT</a><br><a href="/sec/?pt=31">P31 - OUT</a><br><a href="/sec/?pt=32">P32 - IN</a><br><a href="/sec/?pt=33">P33 - I2C/SCL</a><br><a href="/sec/?pt=34">P34 - DS</a><br><a href="/sec/?pt=35">P35 - NC</a><br>-- XP5/6 --<br><a href="/sec/?pt=36">P36 - ADC</a><br><a href="/sec/?pt=37">P37 - NC</a></body></html>
"""
print(PATT_FW.search(data).groups()[0])
def parse_scan_page(page: str):
ret = []
req = []
page = BeautifulSoup(page, features="lxml")
for x in page.find_all('a'):
params = x.get('href')
if params is None:
continue
params = dict(parse_qsl(urlparse(params).query))
if 'i2c_dev' in params:
dev = params['i2c_dev']
classes = i2c_classes.get(dev, [])
for i, c in enumerate(classes):
if c is Skip:
continue
elif c is Request:
req.append(params)
continue
elif isinstance(c, tuple):
suffix, c = c
elif isinstance(c, str):
suffix = c
else:
suffix = ''
if 'addr' in params:
suffix += f"_{params['addr']}" if suffix else str(params['addr'])
if suffix:
_dev = f'{dev}_{suffix}'
else:
_dev = dev
params = params.copy()
if i > 0:
params['i2c_par'] = i
ret.append({
'id_suffix': _dev,
'device_class': c,
'params': params,
})
req.append(params)
return req, ret
class Skip:
pass
class Request:
pass
i2c_classes = {
'htu21d': [
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
],
'sht31': [
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
],
'max44009': [
DEVICE_CLASS_ILLUMINANCE
],
'bh1750': [
DEVICE_CLASS_ILLUMINANCE
],
'tsl2591': [
DEVICE_CLASS_ILLUMINANCE
],
'bmp180': [
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
],
'bmx280': [
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY
],
'mlx90614': [
Skip,
('temp', DEVICE_CLASS_TEMPERATURE),
('object', DEVICE_CLASS_TEMPERATURE),
],
'ptsensor': [
Request, # запрос на измерение
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
],
'mcp9600': [
DEVICE_CLASS_TEMPERATURE, # термопара
DEVICE_CLASS_TEMPERATURE, # сенсор встроенный в микросхему
],
't67xx': [
None # для co2 нет класса в HA
],
'tmp117': [
DEVICE_CLASS_TEMPERATURE,
]
}
print(parse_scan_page(page))

View File

@@ -7,7 +7,7 @@ import voluptuous as vol
from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_ID, CONF_NAME, CONF_DOMAIN,
CONF_UNIT_OF_MEASUREMENT, CONF_HOST, CONF_VALUE_TEMPLATE
CONF_UNIT_OF_MEASUREMENT, CONF_HOST, CONF_VALUE_TEMPLATE, CONF_DEVICE_CLASS
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.service import bind_hass
@@ -34,6 +34,10 @@ CUSTOMIZE_PORT = {
vol.Any(str, {
vol.Required(str): str
}),
vol.Optional(CONF_DEVICE_CLASS):
vol.Any(str, {
vol.Required(str): str
}),
vol.Optional(
CONF_RESPONSE_TEMPLATE,
description='шаблон ответа когда на этот порт приходит'

View File

@@ -67,6 +67,10 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
super().__init__(*args, **kwargs)
self._is_on = None
self._attrs = None
self._click_task = None
async def _click(self):
await self.customize.get
@property
def state_attributes(self):

View File

@@ -63,7 +63,7 @@ async def validate_input(hass: core.HomeAssistant, data):
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for mega."""
VERSION = 13
VERSION = 18
CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED
async def async_step_user(self, user_input=None):
@@ -118,7 +118,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
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)
hub = await get_hub(self.hass, cfg)
if reload:
await hub.start()
new = await hub.get_config(nports=user_input.get(CONF_NPORTS, 37))

View File

@@ -1,6 +1,19 @@
from dataclasses import dataclass, field
from bs4 import BeautifulSoup
inputs = [
'eact',
'inta',
'misc',
]
selectors = [
'pty',
'm',
'gr',
'd',
'ety',
]
@dataclass(frozen=True, eq=True)
class Config:
@@ -8,21 +21,17 @@ class Config:
m: str = None
gr: str = None
d: str = None
inta: str = field(compare=False, hash=False, default=None)
ety: str = None
inta: str = field(compare=False, hash=False, default=None)
misc: str = field(compare=False, hash=False, default=None)
eact: str = field(compare=False, hash=False, default=None)
src: BeautifulSoup = field(compare=False, hash=False, default=None)
def parse_config(page: str):
page = BeautifulSoup(page, features="lxml")
ret = {}
for x in [
'pty',
'm',
'gr',
'd',
'ety',
]:
for x in selectors:
v = page.find('select', attrs={'name': x})
if v is None:
continue
@@ -31,13 +40,11 @@ def parse_config(page: str):
if v:
v = v['value']
ret[x] = v
v = page.find('input', attrs={'name': 'inta'})
for x in inputs:
v = page.find('input', attrs={'name': x})
if v:
ret['inta'] = v['value']
v = page.find('input', attrs={'name': 'misc'})
if v:
ret['misc'] = v.get('checked', False)
return Config(**ret)
ret[x] = v['value']
return Config(**ret, src=page)
DIGITAL_IN = Config(pty="0")

View File

@@ -32,6 +32,8 @@ CONF_POLL_OUTS = 'poll_outs'
CONF_FORCE_D = 'force_d'
CONF_DEF_RESPONSE = 'def_response'
CONF_RESTORE_ON_RESTART = 'restore_on_restart'
CONF_CLICK_TIME = 'click_time'
CONF_LONG_TIME = 'long_time'
PLATFORMS = [
"light",
"switch",

View File

@@ -318,7 +318,7 @@ class MegaOutPort(MegaPushEntity):
_cmd = {"cmd": f"{self.cmd_port}:{cmd}"}
if self.addr:
_cmd['addr'] = self.addr
await self.mega.request(**_cmd)
await self.mega.request(**_cmd, priority=-1)
if self.index is not None:
# обновление текущего стейта для ds2413
await self.mega.get_port(
@@ -342,7 +342,7 @@ class MegaOutPort(MegaPushEntity):
_cmd = {"cmd": f"{self.cmd_port}:{cmd}"}
if self.addr:
_cmd['addr'] = self.addr
await self.mega.request(**_cmd)
await self.mega.request(**_cmd, priority=-1)
if self.index is not None:
# обновление текущего стейта для ds2413
await self.mega.get_port(

View File

@@ -15,7 +15,11 @@ from .tools import make_ints
from . import hub as h
_LOGGER = logging.getLogger(__name__).getChild('http')
ext = {f'ext{x}' for x in range(16)}
def is_ext(data: typing.Dict[str, typing.Any]):
for x in data:
if x.startswith('ext'):
return True
class MegaView(HomeAssistantView):
@@ -95,22 +99,37 @@ class MegaView(HomeAssistantView):
data['mega_id'] = hub.id
ret = 'd' if hub.force_d else ''
if port is not None:
if set(data).issubset(ext):
ret = '' # пока ответ всегда пустой, неясно какая будет реакция на непустой ответ
pt_orig = hub.ext_in.get(port)
if is_ext(data):
# ret = '' # пока ответ всегда пустой, неясно какая будет реакция на непустой ответ
if port in hub.extenders:
pt_orig = port
else:
pt_orig = hub.ext_in.get(port, hub.ext_in.get(str(port)))
if pt_orig is None:
hub.lg.warning(f'can not find extender for int port {port}')
hub.lg.warning(f'can not find extender for int port {port}, '
f'have ext_int: {hub.ext_in}, ext: {hub.extenders}')
return Response(status=200)
for e in ext:
if e in data:
idx = e[-1]
for e, v in data.items():
_data = data.copy()
if e.startswith('ext'):
idx = e[3:]
pt = f'{pt_orig}e{idx}'
data['pt'] = pt_orig
data['value'] = 'ON' if data[e] == '1' else 'OFF'
data['m'] = 1 if data[e] == '0' else 0 # имитация поведения обычного входа, чтобы события обрабатывались аналогично
hub.values[pt] = data
_data['pt_orig'] = pt_orig
_data['value'] = 'ON' if v == '1' else 'OFF'
_data['m'] = 1 if _data[e] == '0' else 0 # имитация поведения обычного входа, чтобы события обрабатывались аналогично
hub.values[pt] = _data
for cb in self.callbacks[hub.id][pt]:
cb(data)
cb(_data)
act = hub.ext_act.get(pt)
hub.lg.debug(f'act on port {pt}: {act}, all acts are: {hub.ext_act}')
template: Template = self.templates.get(hub.id, {}).get(port, hub.def_response)
if template is not None:
template.hass = hass
ret = template.async_render(_data)
hub.lg.debug(f'response={ret}, template={template}')
if ret == 'd' and act:
await hub.request(cmd=act.replace(':3', f':{v}'))
ret = 'd' if hub.force_d else ''
else:
hub.values[port] = data
for cb in self.callbacks[hub.id][port]:

View File

@@ -24,7 +24,8 @@ from .const import (
)
from .entities import set_events_off, BaseMegaEntity, MegaOutPort
from .exceptions import CannotConnect, NoPort
from .tools import make_ints, int_ignore
from .i2c import parse_scan_page
from .tools import make_ints, int_ignore, PriorityLock
TEMP_PATT = re.compile(r'temp:([01234567890\.]+)')
HUM_PATT = re.compile(r'hum:([01234567890\.]+)')
@@ -82,6 +83,8 @@ class MegaD:
restore_on_restart=False,
extenders=None,
ext_in=None,
ext_acts=None,
i2c_sensors=None,
**kwargs,
):
"""Initialize."""
@@ -98,6 +101,8 @@ class MegaD:
self.http = None
self.extenders = extenders or []
self.ext_in = ext_in or {}
self.ext_act = ext_acts or {}
self.i2c_sensors = i2c_sensors or []
self.poll_outs = poll_outs
self.update_all = update_all if update_all is not None else True
self.nports = nports
@@ -111,7 +116,7 @@ class MegaD:
self.id = id
self.lck = asyncio.Lock()
self.last_long = {}
self._http_lck = asyncio.Lock()
self._http_lck = PriorityLock()
self._notif_lck = asyncio.Lock()
self.cnd = asyncio.Condition()
self.online = True
@@ -243,6 +248,12 @@ class MegaD:
Polling ports
"""
self.lg.debug('poll')
for x in self.i2c_sensors:
if not isinstance(x, dict):
continue
ret = await self._update_i2c(x)
if isinstance(ret, dict):
self.values.update(ret)
for x in self.extenders:
ret = await self._update_extender(x)
if not isinstance(ret, dict):
@@ -279,13 +290,13 @@ class MegaD:
async def send_command(self, port=None, cmd=None):
return await self.request(pt=port, cmd=cmd)
async def request(self, **kwargs):
async def request(self, priority=0, **kwargs):
cmd = '&'.join([f'{k}={v}' for k, v in kwargs.items() if v is not None])
url = f"http://{self.host}/{self.sec}"
if cmd:
url = f"{url}/?{cmd}"
self.lg.debug('request: %s', url)
async with self._http_lck:
async with self._http_lck(priority):
async with aiohttp.request("get", url=url) as req:
if req.status != 200:
self.lg.warning('%s returned %s (%s)', url, req.status, await req.text())
@@ -512,11 +523,24 @@ class MegaD:
ret[f'{port}e{i}'] = x
return ret
async def _update_i2c(self, params):
"""
Обновление портов i2c
:param params: параметры url
:return:
"""
_params = tuple(params.items())
return {
_params: await self.request(**params)
}
async def get_config(self, nports=37):
ret = defaultdict(lambda: defaultdict(list))
ret['mqtt_id'] = await self.get_mqtt_id()
ret['extenders'] = extenders = []
ret['ext_in'] = ext_int = {}
ret['ext_acts'] = ext_acts = {}
ret['i2c_sensors'] = i2c_sensors = []
async for port, cfg in self.scan_ports(nports):
if cfg.pty == "0":
ret['binary_sensor'][port].append({})
@@ -536,22 +560,36 @@ class MegaD:
])
elif cfg == MCP230:
extenders.append(port)
ext_int[cfg.inta] = port
if cfg.inta:
ext_int[int_ignore(cfg.inta)] = port
values = await self.request(pt=port, cmd='get')
values = values.split(';')
for n in range(len(values)):
ext_page = await self.request(pt=port, ext=n)
ext_cfg = parse_config(ext_page)
pt = f'{port}e{n}'
if ext_cfg.ety == '1':
ret['light'][f'{port}e{n}'].append({})
ret['light'][pt].append({})
elif ext_cfg.ety == '0':
ret['binary_sensor'][f'{port}e{n}'].append({})
if ext_cfg.eact:
ext_acts[pt] = ext_cfg.eact
ret['binary_sensor'][pt].append({})
elif cfg == PCA9685:
extenders.append(port)
values = await self.request(pt=port, cmd='get')
values = values.split(';')
for n in range(len(values)):
ret['light'][f'{port}e{n}'].append({'dimmer': True, 'dimmer_scale': 16})
elif cfg.pty == '4' and cfg.gr == '0':
# i2c в режиме ANY
scan = cfg.src.find('a', text='I2C Scan')
self.lg.debug(f'find scan link: %s', scan)
if scan is not None:
page = await self.request(pt=port, cmd='scan')
req, parsed = parse_scan_page(page)
self.lg.debug(f'scan results: %s', (req, parsed))
ret['i2c'][port].append(parsed)
i2c_sensors.extend(req)
elif cfg.pty in ('3', '2', '4'):
http_cmd = 'get'
if cfg.d == '5' and cfg.pty == '3':

View File

@@ -0,0 +1,108 @@
from urllib.parse import parse_qsl, urlparse
from bs4 import BeautifulSoup
from homeassistant.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_PRESSURE,
)
def parse_scan_page(page: str):
ret = []
req = []
page = BeautifulSoup(page, features="lxml")
for x in page.find_all('a'):
params = x.get('href')
if params is None:
continue
params = dict(parse_qsl(urlparse(params).query))
if 'i2c_dev' in params:
dev = params['i2c_dev']
classes = i2c_classes.get(dev, [])
for i, c in enumerate(classes):
if c is Skip:
continue
elif c is Request:
req.append(params)
continue
elif isinstance(c, tuple):
suffix, c = c
elif isinstance(c, str):
suffix = c
else:
suffix = ''
if 'addr' in params:
suffix += f"_{params['addr']}" if suffix else str(params['addr'])
if suffix:
_dev = f'{dev}_{suffix}'
else:
_dev = dev
params = params.copy()
if i > 0:
params['i2c_par'] = i
ret.append({
'id_suffix': _dev,
'device_class': c,
'params': params,
})
req.append(params)
return req, ret
class Skip:
pass
class Request:
pass
i2c_classes = {
'htu21d': [
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
],
'sht31': [
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
],
'max44009': [
DEVICE_CLASS_ILLUMINANCE
],
'bh1750': [
DEVICE_CLASS_ILLUMINANCE
],
'tsl2591': [
DEVICE_CLASS_ILLUMINANCE
],
'bmp180': [
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
],
'bmx280': [
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY
],
'mlx90614': [
Skip,
('temp', DEVICE_CLASS_TEMPERATURE),
('object', DEVICE_CLASS_TEMPERATURE),
],
'ptsensor': [
Request, # запрос на измерение
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
],
'mcp9600': [
DEVICE_CLASS_TEMPERATURE, # термопара
DEVICE_CLASS_TEMPERATURE, # сенсор встроенный в микросхему
],
't67xx': [
None # для co2 нет класса в HA
],
'tmp117': [
DEVICE_CLASS_TEMPERATURE,
]
}

View File

@@ -15,5 +15,5 @@
"@andvikt"
],
"issue_tracker": "https://github.com/andvikt/mega_hacs/issues",
"version": "v0.5.1b2"
"version": "v0.6.1b1"
}

View File

@@ -14,6 +14,7 @@ from homeassistant.const import (
CONF_UNIQUE_ID,
CONF_ID,
CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
CONF_DEVICE_CLASS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.template import Template
@@ -82,11 +83,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
mid = config_entry.data[CONF_ID]
hub: MegaD = hass.data['mega'][mid]
devices = []
for port, cfg in config_entry.data.get('sensor', {}).items():
for tp in ['sensor', 'i2c']:
for port, cfg in config_entry.data.get(tp, {}).items():
port = int_ignore(port)
for data in cfg:
hub.lg.debug(f'add sensor on port %s with data %s', port, data)
sensor = Mega1WSensor(
sensor = _constructors[tp](
mega=hub,
port=port,
config_entry=config_entry,
@@ -97,6 +99,24 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
async_add_devices(devices)
class MegaI2C(MegaPushEntity):
def __init__(self, *args, device_class: str, params: dict, **kwargs):
self._device_class = device_class
self._params = tuple(params.items())
super().__init__(*args, **kwargs)
def device_class(self):
return self._device_class
def state(self):
return self.mega.values[self._params]
@property
def device_class(self):
return self._device_class
class Mega1WSensor(MegaPushEntity):
def __init__(
@@ -141,6 +161,14 @@ class Mega1WSensor(MegaPushEntity):
@property
def device_class(self):
_u = self.customize.get(CONF_DEVICE_CLASS, None)
if _u is None:
return self._device_class
elif isinstance(_u, str):
return _u
elif isinstance(_u, dict) and self.key in _u:
return _u[self.key]
else:
return self._device_class
@property
@@ -178,3 +206,9 @@ class Mega1WSensor(MegaPushEntity):
if isinstance(c, dict):
c = c.get(self.key)
return c or n
_constructors = {
'sensor': Mega1WSensor,
'i2c': MegaI2C,
}

View File

@@ -1,3 +1,9 @@
import asyncio
import itertools
from heapq import heappush
from contextlib import asynccontextmanager
_params = ['m', 'click', 'cnt', 'pt']
@@ -18,3 +24,94 @@ def int_ignore(x):
return int(x)
except (TypeError, ValueError):
return x
class PriorityLock(asyncio.Lock):
"""
You can acquire lock with some kind of priority in mind, so that locks with higher priority will be released first.
priority can be set with lck.acquire(1)
or by using context manager:
>>> lck = PriorityLock()
... async with lck(1):
... # do something
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._cnt = itertools.count()
def __call__(self, priority=0):
return self._with_priority(priority)
@asynccontextmanager
async def _with_priority(self, p):
await self.acquire(p)
try:
yield
finally:
self.release()
async def acquire(self, priority=0) -> bool:
"""Acquire a lock.
This method blocks until the lock is unlocked, then sets it to
locked and returns True.
"""
if (not self._locked and (self._waiters is None or
all(w.cancelled() for _, w in self._waiters))):
self._locked = True
return True
if self._waiters is None:
self._waiters = []
fut = self._loop.create_future()
cnt = next(self._cnt)
heappush(self._waiters, (priority, cnt, fut))
# Finally block should be called before the CancelledError
# handling as we don't want CancelledError to call
# _wake_up_first() and attempt to wake up itself.
try:
try:
await fut
finally:
self._waiters.remove((priority, cnt, fut))
except asyncio.exceptions.CancelledError:
if not self._locked:
self._wake_up_first()
raise
self._locked = True
return True
def release(self):
"""Release a lock.
When the lock is locked, reset it to unlocked, and return.
If any other coroutines are blocked waiting for the lock to become
unlocked, allow exactly one of them to proceed.
When invoked on an unlocked lock, a RuntimeError is raised.
There is no return value.
"""
if self._locked:
self._locked = False
self._wake_up_first()
else:
raise RuntimeError('Lock is not acquired.')
def _wake_up_first(self):
"""Wake up the first waiter if it isn't done."""
if not self._waiters:
return
try:
_, _, fut = self._waiters[0]
except IndexError:
return
# .done() necessarily means that a waiter will wake up later on and
# either take the lock, or, if it was cancelled and lock wasn't
# taken already, will hit this again and wake up a new waiter.
if not fut.done():
fut.set_result(True)

View File

@@ -30,6 +30,8 @@
выполнении предыдущей.
- поддержка [ds2413](https://www.ab-log.ru/smart-house/ethernet/megad-2w) (начиная с версии 0.4.1)
- поддержка MCP23008/MCP23017/PCA9685 (начиная с версии 0.5.1)
- поддержка всех возможных датчиков в режиме I2C-ANY, полный список поддерживаемых датчиков датчиков
[по ссылке](https://github.com/andvikt/mega_hacs/wiki/ш2с)
## Установка
Рекомендованный способ с поддержкой обновлений - [HACS](https://hacs.xyz/docs/installation/installation):