Compare commits

...

15 Commits

Author SHA1 Message Date
andvikt
8dfa5926ad Merge pull request #4 from Foroxon/master
Ukrainian translation updating
2021-01-29 09:09:40 +03:00
Andrey
3978ce2203 fix 1-wire updating while busy 2021-01-29 09:08:34 +03:00
Andrey
72cf516353 fix mqtt sensor 2021-01-28 12:17:38 +03:00
Andrey
3b6459468b fix mqtt sensor 2021-01-28 12:04:43 +03:00
Andrey
c36c1369aa fix binary sensors 2021-01-28 10:29:16 +03:00
Andrey
6d3391bc45 add new events, fix binsensor 2021-01-27 21:40:55 +03:00
Vladyslav Heneraliuk
2e025eb0c2 updated Ukrainian translation 2021-01-26 12:16:42 +02:00
Vladyslav Heneraliuk
20d5b8ff40 Merge pull request #1 from andvikt/master
Sync
2021-01-26 12:10:46 +02:00
Andrey
a7d7738a5c fix mqtt 2021-01-26 08:34:27 +03:00
Andrey
c0b1247b9e smaller headers 2021-01-25 21:35:06 +03:00
Andrey
1548e8c364 fix multiple megas 2021-01-25 20:17:15 +03:00
Andrey
39c4ab0e3b fix device name 2021-01-25 18:45:23 +03:00
Andrey
a002e48e04 fix old mega out type 0 2021-01-25 18:13:25 +03:00
Andrey
dc6bdfc8f4 fix yaml exclusion 2021-01-25 17:56:47 +03:00
Andrey
e51b50797c fix yaml exclusion 2021-01-25 17:46:26 +03:00
14 changed files with 257 additions and 92 deletions

32
.experiment.py Normal file
View File

@@ -0,0 +1,32 @@
import asyncio
async def handle_echo(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
data = await reader.read(100)
message = data.decode()
addr = writer.get_extra_info('peername')
print(f"Received {message!r} from {addr!r}")
print(f"Send: {message!r}")
ans = '''HTTP/1.1 200 OK\nContent-Length: 6\n\nhello\n'''.encode()
writer.write(ans)
await writer.drain()
print("Close the connection")
writer.transport.close()
writer.close()
await writer.wait_closed()
async def main():
server = await asyncio.start_server(
handle_echo, '127.0.0.1', 8888)
addr = server.sockets[0].getsockname()
print(f'Serving on {addr}')
async with server:
await server.serve_forever()
asyncio.run(main())

View File

@@ -1,36 +0,0 @@
import asyncio
from bs4 import BeautifulSoup
import aiohttp
host = '192.168.88.14/sec'
# page = '''
# <html><head><style>input,select{margin:1px}</style></head><body><a href="/sec/?cf=3">Back</a><br>P7/ON<br><a href="/sec/?pt=7&amp;cmd=7:1">ON</a> <a href="/sec/?pt=7&amp;cmd=7:0">OFF</a><br><form action="/sec/"><input type="hidden" name="pn" value="7">Type <select name="pty"><option value="255">NC</option><option value="0">In</option><option value="1" selected="">Out</option><option value="3">DSen</option><option value="4">I2C</option></select><br>Default: <select name="d"><option value="0" selected="">0</option><option value="1">1</option></select><br>Mode <select name="m"><option value="0" selected="">SW</option><option value="3">SW LINK</option><option value="2">DS2413</option></select><br>Group <input name="grp" size="2" value=""><br><input type="submit" value="Save"></form></body></html>
# <head><style>input,select{margin:1px}</style></head>
# <body><a href="/sec/?cf=3">Back</a><br>P7/ON<br><a href="/sec/?pt=7&amp;cmd=7:1">ON</a> <a href="/sec/?pt=7&amp;cmd=7:0">OFF</a><br><form action="/sec/"><input type="hidden" name="pn" value="7">Type <select name="pty"><option value="255">NC</option><option value="0">In</option><option value="1" selected="">Out</option><option value="3">DSen</option><option value="4">I2C</option></select><br>Default: <select name="d"><option value="0" selected="">0</option><option value="1">1</option></select><br>Mode <select name="m"><option value="0" selected="">SW</option><option value="3">SW LINK</option><option value="2">DS2413</option></select><br>Group <input name="grp" size="2" value=""><br><input type="submit" value="Save"></form></body>
# <a href="/sec/?cf=3">Back</a>
# <br>
# P7/ON
# <br>
# <a href="/sec/?pt=7&amp;cmd=7:1">ON</a>
# <a href="/sec/?pt=7&amp;cmd=7:0">OFF</a>
# <br>
# <form action="/sec/"><input type="hidden" name="pn" value="7">Type <select name="pty"><option value="255">NC</option><option value="0">In</option><option value="1" selected="">Out</option><option value="3">DSen</option><option value="4">I2C</option></select><br>Default: <select name="d"><option value="0" selected="">0</option><option value="1">1</option></select><br>Mode <select name="m"><option value="0" selected="">SW</option><option value="3">SW LINK</option><option value="2">DS2413</option></select><br>Group <input name="grp" size="2" value=""><br><input type="submit" value="Save"></form>
# <body><a href="/sec/?cf=3">Back</a><br>P7/ON<br><a href="/sec/?pt=7&amp;cmd=7:1">ON</a> <a href="/sec/?pt=7&amp;cmd=7:0">OFF</a><br><form action="/sec/"><input type="hidden" name="pn" value="7">Type <select name="pty"><option value="255">NC</option><option value="0">In</option><option value="1" selected="">Out</option><option value="3">DSen</option><option value="4">I2C</option></select><br>Default: <select name="d"><option value="0" selected="">0</option><option value="1">1</option></select><br>Mode <select name="m"><option value="0" selected="">SW</option><option value="3">SW LINK</option><option value="2">DS2413</option></select><br>Group <input name="grp" size="2" value=""><br><input type="submit" value="Save"></form></body>
# <html><head><style>input,select{margin:1px}</style></head><body><a href="/sec/?cf=3">Back</a><br>P7/ON<br><a href="/sec/?pt=7&amp;cmd=7:1">ON</a> <a href="/sec/?pt=7&amp;cmd=7:0">OFF</a><br><form action="/sec/"><input type="hidden" name="pn" value="7">Type <select name="pty"><option value="255">NC</option><option value="0">In</option><option value="1" selected="">Out</option><option value="3">DSen</option><option value="4">I2C</option></select><br>Default: <select name="d"><option value="0" selected="">0</option><option value="1">1</option></select><br>Mode <select name="m"><option value="0" selected="">SW</option><option value="3">SW LINK</option><option value="2">DS2413</option></select><br>Group <input name="grp" size="2" value=""><br><input type="submit" value="Save"></form></body></html>
# '''
# tree = BeautifulSoup(page, features="lxml")
# pty = tree.find('select', attrs={'name': 'pty'}).find(selected=True)['value']
# m = tree.find('select', attrs={'name': 'm'})
# if m:
# m = m.find(selected=True)['value']
#
# print(pty, m)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(
scan_port(0)
)

View File

@@ -160,7 +160,8 @@ async def async_remove_entry(hass, entry) -> None:
if hub is None: if hub is None:
return return
_LOGGER.debug(f'remove {id}') _LOGGER.debug(f'remove {id}')
_hubs.pop(entry.entry_id) _hubs.pop(id, None)
hass.data[DOMAIN].pop(id, None)
task: asyncio.Task = _POLL_TASKS.pop(id, None) task: asyncio.Task = _POLL_TASKS.pop(id, None)
if task is not None: if task is not None:
task.cancel() task.cancel()

View File

@@ -78,7 +78,10 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
if val is None and self._state is not None: if val is None and self._state is not None:
return self._state == 'ON' return self._state == 'ON'
elif val is not None: elif val is not None:
return val == 'ON' or val == 1 if val in ['ON', 'OFF']:
return val == 'ON'
else:
return val != 1
def _update(self, payload: dict): def _update(self, payload: dict):
self.mega.values[self.port] = payload self.mega.values[self.port] = payload

View File

@@ -25,7 +25,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
vol.Optional(CONF_PORT_TO_SCAN, default=0): int, vol.Optional(CONF_PORT_TO_SCAN, default=0): int,
vol.Optional(CONF_MQTT_INPUTS, default=True): bool, vol.Optional(CONF_MQTT_INPUTS, default=True): bool,
vol.Optional(CONF_NPORTS, default=37): int, vol.Optional(CONF_NPORTS, default=37): int,
# vol.Optional(CONF_UPDATE_ALL, default=True): bool, vol.Optional(CONF_UPDATE_ALL, default=True): bool,
}, },
) )
@@ -135,7 +135,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
vol.Optional(CONF_MQTT_INPUTS, default=e.get(CONF_MQTT_INPUTS, True)): bool, vol.Optional(CONF_MQTT_INPUTS, default=e.get(CONF_MQTT_INPUTS, True)): bool,
vol.Optional(CONF_NPORTS, default=e.get(CONF_NPORTS, 37)): int, vol.Optional(CONF_NPORTS, default=e.get(CONF_NPORTS, 37)): int,
vol.Optional(CONF_RELOAD, default=False): bool, vol.Optional(CONF_RELOAD, default=False): bool,
# vol.Optional(CONF_UPDATE_ALL, default=e.get(CONF_UPDATE_ALL, True)): bool, vol.Optional(CONF_UPDATE_ALL, default=e.get(CONF_UPDATE_ALL, True)): bool,
# vol.Optional(CONF_INVERT, default=''): str, # vol.Optional(CONF_INVERT, default=''): str,
}), }),
) )

View File

@@ -31,4 +31,13 @@ PLATFORMS = [
"sensor", "sensor",
] ]
EVENT_BINARY_SENSOR = f'{DOMAIN}.sensor' EVENT_BINARY_SENSOR = f'{DOMAIN}.sensor'
PATT_SPLIT = re.compile('[;/]') EVENT_BINARY = f'{DOMAIN}.binary'
PATT_SPLIT = re.compile('[;/]')
LONG = 'long'
RELEASE = 'release'
LONG_RELEASE = 'long_release'
PRESS = 'press'
SINGLE_CLICK = 'single'
DOUBLE_CLICK = 'double'

View File

@@ -1,12 +1,30 @@
import logging import logging
import asyncio import asyncio
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import State from homeassistant.core import State
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from .hub import MegaD from . import hub as h
from .const import DOMAIN, CONF_CUSTOM, CONF_INVERT from .const import DOMAIN, CONF_CUSTOM, CONF_INVERT, EVENT_BINARY_SENSOR, LONG, \
LONG_RELEASE, RELEASE, PRESS, SINGLE_CLICK, DOUBLE_CLICK, EVENT_BINARY
_events_on = False
_LOGGER = logging.getLogger(__name__)
async def _set_events_on():
global _events_on
await asyncio.sleep(10)
_LOGGER.debug('events on')
_events_on = True
def set_events_off():
global _events_on
_events_on = False
_task_set_ev_on = None
class BaseMegaEntity(CoordinatorEntity, RestoreEntity): class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
@@ -17,7 +35,7 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
""" """
def __init__( def __init__(
self, self,
mega: MegaD, mega: 'h.MegaD',
port: int, port: int,
config_entry: ConfigEntry = None, config_entry: ConfigEntry = None,
id_suffix=None, id_suffix=None,
@@ -59,7 +77,7 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
"config_entries": [ "config_entries": [
self.config_entry, self.config_entry,
], ],
"name": f'port {self.port}', "name": f'{self._mega_id} port {self.port}',
"manufacturer": 'ab-log.ru', "manufacturer": 'ab-log.ru',
# "model": self.light.productname, # "model": self.light.productname,
# "sw_version": self.light.swversion, # "sw_version": self.light.swversion,
@@ -88,8 +106,11 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
return self._unique_id return self._unique_id
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
global _task_set_ev_on
await super().async_added_to_hass() await super().async_added_to_hass()
self._state = await self.async_get_last_state() self._state = await self.async_get_last_state()
if self.mega.mqtt_inputs and _task_set_ev_on is None:
_task_set_ev_on = asyncio.create_task(_set_events_on())
async def get_state(self): async def get_state(self):
if self.mega.mqtt is None: if self.mega.mqtt is None:
@@ -111,7 +132,55 @@ class MegaPushEntity(BaseMegaEntity):
self._update(value) self._update(value)
self.async_write_ha_state() self.async_write_ha_state()
self.lg.debug(f'state after update %s', self.state) self.lg.debug(f'state after update %s', self.state)
self.is_first_update = False if self.mega.mqtt_inputs and not _events_on:
_LOGGER.debug('skip event because events are off')
return
if not self.entity_id.startswith('binary_sensor'):
_LOGGER.debug('skip event because not a bnary sens')
return
ll: bool = self.mega.last_long.get(self.port, False)
if safe_int(value.get('click', 0)) == 1:
self.hass.bus.async_fire(
event_type=EVENT_BINARY,
event_data={
'entity_id': self.entity_id,
'type': SINGLE_CLICK
}
)
elif safe_int(value.get('click', 0)) == 2:
self.hass.bus.async_fire(
event_type=EVENT_BINARY,
event_data={
'entity_id': self.entity_id,
'type': DOUBLE_CLICK
}
)
elif safe_int(value.get('m', 0)) == 2:
self.mega.last_long[self.port] = True
self.hass.bus.async_fire(
event_type=EVENT_BINARY,
event_data={
'entity_id': self.entity_id,
'type': LONG
}
)
elif safe_int(value.get('m', 0)) == 1:
self.hass.bus.async_fire(
event_type=EVENT_BINARY,
event_data={
'entity_id': self.entity_id,
'type': LONG_RELEASE if ll else RELEASE,
}
)
elif safe_int(value.get('m', None)) == 0:
self.hass.bus.async_fire(
event_type=EVENT_BINARY,
event_data={
'entity_id': self.entity_id,
'type': PRESS,
}
)
self.mega.last_long[self.port] = False
return return
def _update(self, payload: dict): def _update(self, payload: dict):
@@ -155,10 +224,12 @@ class MegaOutPort(MegaPushEntity):
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
val = self.mega.values.get(self.port, {}).get("value") val = self.mega.values.get(self.port, {})
if val is None and self._state is not None: if val is None and self._state is not None:
return self._state == 'ON' return self._state == 'ON'
elif val is not None: elif val is not None:
val = val.get("value")
if not self.invert: if not self.invert:
return val == 'ON' or str(val) == '1' or (safe_int(val) is not None and safe_int(val) > 0) return val == 'ON' or str(val) == '1' or (safe_int(val) is not None and safe_int(val) > 0)
else: else:
@@ -186,10 +257,11 @@ class MegaOutPort(MegaPushEntity):
self.mega.values[self.port] = {'value': cmd} self.mega.values[self.port] = {'value': cmd}
await self.get_state() await self.get_state()
def safe_int(v): def safe_int(v):
if v in ['ON', 'OFF']: if v in ['ON', 'OFF']:
return None return None
try: try:
return int(v) return int(v)
except ValueError: except (ValueError, TypeError):
return None return None

View File

@@ -8,11 +8,11 @@ from aiohttp.web_request import Request
from aiohttp.web_response import Response from aiohttp.web_response import Response
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from .const import EVENT_BINARY_SENSOR, CONF_HTTP, DOMAIN, CONF_CUSTOM, CONF_RESPONSE_TEMPLATE from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_RESPONSE_TEMPLATE
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.core import callback, HomeAssistant from homeassistant.core import HomeAssistant
from . import hub from .tools import make_ints
from . import hub as h
_LOGGER = logging.getLogger(__name__).getChild('http') _LOGGER = logging.getLogger(__name__).getChild('http')
@@ -26,8 +26,7 @@ class MegaView(HomeAssistantView):
def __init__(self, cfg: dict): def __init__(self, cfg: dict):
self._try = 0 self._try = 0
self.allowed_hosts = {'::1'} self.allowed_hosts = {'::1'}
self.callbacks: typing.DefaultDict[int, typing.List[typing.Callable[[dict], typing.Coroutine]]] \ self.callbacks = defaultdict(lambda: defaultdict(list))
= defaultdict(list)
self.templates: typing.Dict[str, typing.Dict[str, Template]] = { self.templates: typing.Dict[str, typing.Dict[str, Template]] = {
mid: { mid: {
pt: cfg[mid][pt][CONF_RESPONSE_TEMPLATE] pt: cfg[mid][pt][CONF_RESPONSE_TEMPLATE]
@@ -38,6 +37,7 @@ class MegaView(HomeAssistantView):
_LOGGER.debug('templates: %s', self.templates) _LOGGER.debug('templates: %s', self.templates)
async def get(self, request: Request) -> Response: async def get(self, request: Request) -> Response:
auth = False auth = False
for x in self.allowed_hosts: for x in self.allowed_hosts:
if request.remote.startswith(x): if request.remote.startswith(x):
@@ -48,7 +48,7 @@ class MegaView(HomeAssistantView):
return Response(status=401) return Response(status=401)
hass: HomeAssistant = request.app['hass'] hass: HomeAssistant = request.app['hass']
hub: 'hub.MegaD' = hass.data.get(DOMAIN).get(request.remote) # TODO: проверить какой remote hub: 'h.MegaD' = hass.data.get(DOMAIN).get(request.remote) # TODO: проверить какой remote
if hub is None and request.remote == '::1': if hub is None and request.remote == '::1':
hub = hass.data.get(DOMAIN).get('__def') hub = hass.data.get(DOMAIN).get('__def')
if hub is None: if hub is None:
@@ -62,9 +62,11 @@ class MegaView(HomeAssistantView):
make_ints(data) make_ints(data)
port = data.get('pt') port = data.get('pt')
data = data.copy() data = data.copy()
data['mega_id'] = hub.id
ret = 'd' ret = 'd'
if port is not None: if port is not None:
for cb in self.callbacks[port]: hub.values[port] = data
for cb in self.callbacks[hub.id][port]:
cb(data) cb(data)
template: Template = self.templates.get(hub.id, {}).get(port) template: Template = self.templates.get(hub.id, {}).get(port)
if hub.update_all: if hub.update_all:
@@ -73,8 +75,7 @@ class MegaView(HomeAssistantView):
template.hass = hass template.hass = hass
ret = template.async_render(data) ret = template.async_render(data)
_LOGGER.debug('response %s', ret) _LOGGER.debug('response %s', ret)
ret = Response(body=ret or 'd', content_type='text/plain', headers={}) ret = Response(body=ret or 'd', content_type='text/plain', headers={'Server': 's', 'Date': 'n'})
ret.headers.clear()
return ret return ret
async def later_update(self, hub): async def later_update(self, hub):
@@ -82,14 +83,3 @@ class MegaView(HomeAssistantView):
await asyncio.sleep(1) await asyncio.sleep(1)
await hub.updater.async_refresh() await hub.updater.async_refresh()
def make_ints(d: dict):
for x in d:
try:
d[x] = float(d[x])
except ValueError:
pass
if 'm' not in d:
d['m'] = 0
if 'click' not in d:
d['click'] = 0

View File

@@ -14,9 +14,10 @@ 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 homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import TEMP, HUM, PATT_SPLIT, DOMAIN, CONF_HTTP from .const import TEMP, HUM, PATT_SPLIT, DOMAIN, CONF_HTTP, EVENT_BINARY_SENSOR
from .exceptions import CannotConnect, MqttNotConfigured from .entities import set_events_off
from .http import MegaView from .exceptions import CannotConnect
from .tools import make_ints
TEMP_PATT = re.compile(r'temp:([01234567890\.]+)') TEMP_PATT = re.compile(r'temp:([01234567890\.]+)')
HUM_PATT = re.compile(r'hum:([01234567890\.]+)') HUM_PATT = re.compile(r'hum:([01234567890\.]+)')
@@ -60,8 +61,9 @@ class MegaD:
): ):
"""Initialize.""" """Initialize."""
if mqtt_inputs is None or mqtt_inputs == 'None' or mqtt_inputs is False: if mqtt_inputs is None or mqtt_inputs == 'None' or mqtt_inputs is False:
self.http = hass.data[DOMAIN][CONF_HTTP] self.http = hass.data.get(DOMAIN, {}).get(CONF_HTTP)
self.http.allowed_hosts |= {host} if not self.http is None:
self.http.allowed_hosts |= {host}
else: else:
self.http = None self.http = None
self.update_all = update_all if update_all is not None else True self.update_all = update_all if update_all is not None else True
@@ -74,6 +76,7 @@ class MegaD:
self.mqtt = mqtt self.mqtt = mqtt
self.id = id self.id = id
self.lck = asyncio.Lock() self.lck = asyncio.Lock()
self.last_long = {}
self._http_lck = asyncio.Lock() self._http_lck = asyncio.Lock()
self._notif_lck = asyncio.Lock() self._notif_lck = asyncio.Lock()
self.cnd = asyncio.Condition() self.cnd = asyncio.Condition()
@@ -107,6 +110,7 @@ class MegaD:
async def start(self): async def start(self):
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
if self.mqtt is not None: if self.mqtt is not None:
set_events_off()
self.subs = await self.mqtt.async_subscribe( self.subs = await self.mqtt.async_subscribe(
topic=f"{self.mqtt_id}/+", topic=f"{self.mqtt_id}/+",
msg_callback=self._process_msg, msg_callback=self._process_msg,
@@ -205,11 +209,13 @@ class MegaD:
def parse_response(self, ret): def parse_response(self, ret):
if ret is None: if ret is None:
raise NoPort() raise NoPort()
if 'busy' in ret:
return None
if ':' in ret: if ':' in ret:
ret = PATT_SPLIT.split(ret) ret = PATT_SPLIT.split(ret)
ret = dict([ ret = {'value': dict([
x.split(':') for x in ret if x.count(':') == 1 x.split(':') for x in ret if x.count(':') == 1
]) ])}
elif 'ON' in ret: elif 'ON' in ret:
ret = {'value': 'ON'} ret = {'value': 'ON'}
elif 'OFF' in ret: elif 'OFF' in ret:
@@ -225,13 +231,16 @@ class MegaD:
""" """
self.lg.debug(f'get port %s', port) self.lg.debug(f'get port %s', port)
if self.mqtt is None or force_http: if self.mqtt is None or force_http:
ret = await self.request(pt=port, cmd=http_cmd) if http_cmd == 'list':
ret = self.parse_response(ret) await self.request(pt=port, cmd='conv')
self.lg.debug('parsed: %s', ret)
if http_cmd == 'list' and isinstance(ret, dict) and 'value' in ret:
await asyncio.sleep(1) await asyncio.sleep(1)
ret = await self.request(pt=port, http_cmd=http_cmd) ret = self.parse_response(await self.request(pt=port, cmd=http_cmd))
ret = self.parse_response(ret) ntry = 0
while http_cmd == 'list' and ret is None and ntry < 3:
await asyncio.sleep(1)
ret = self.parse_response(await self.request(pt=port, cmd=http_cmd))
ntry += 1
self.lg.debug('parsed: %s', ret)
self.values[port] = ret self.values[port] = ret
return ret return ret
@@ -250,7 +259,7 @@ class MegaD:
except asyncio.TimeoutError: except asyncio.TimeoutError:
self.lg.error(f'timeout when getting port {port}') self.lg.error(f'timeout when getting port {port}')
async def get_all_ports(self): async def get_all_ports(self, only_out=False):
if not self.mqtt_inputs: if not self.mqtt_inputs:
ret = await self.request(cmd='all') ret = await self.request(cmd='all')
for port, x in enumerate(ret.split(';')): for port, x in enumerate(ret.split(';')):
@@ -289,9 +298,18 @@ class MegaD:
value = None value = None
try: try:
value = json.loads(msg.payload) value = json.loads(msg.payload)
if isinstance(value, dict):
make_ints(value)
self.values[port] = value self.values[port] = value
for cb in self._callbacks[port]: for cb in self._callbacks[port]:
cb(value) cb(value)
if isinstance(value, dict):
value = value.copy()
value['mega_id'] = self.id
self.hass.bus.async_fire(
EVENT_BINARY_SENSOR,
value,
)
except Exception as exc: except Exception as exc:
self.lg.warning(f'could not parse json ({msg.payload}): {exc}') self.lg.warning(f'could not parse json ({msg.payload}): {exc}')
return return
@@ -306,7 +324,7 @@ class MegaD:
if self.mqtt_inputs: if self.mqtt_inputs:
self._callbacks[port].append(callback) self._callbacks[port].append(callback)
else: else:
self.http.callbacks[port].append(callback) self.http.callbacks[self.id][port].append(callback)
async def authenticate(self) -> bool: async def authenticate(self) -> bool:
"""Test if we can authenticate with the host.""" """Test if we can authenticate with the host."""
@@ -371,7 +389,7 @@ class MegaD:
async for port, pty, m in self.scan_ports(nports): async for port, pty, m in self.scan_ports(nports):
if pty == "0": if pty == "0":
ret['binary_sensor'][port].append({}) ret['binary_sensor'][port].append({})
elif pty == "1" and m in ['0', '1', '3']: elif pty == "1" and (m in ['0', '1', '3'] or m is None):
ret['light'][port].append({'dimmer': m == '1'}) ret['light'][port].append({'dimmer': m == '1'})
elif pty == '3': elif pty == '3':
try: try:

View File

@@ -149,7 +149,9 @@ class Mega1WSensor(MegaPushEntity):
try: try:
ret = self.mega.values.get(self.port, {}) ret = self.mega.values.get(self.port, {})
if isinstance(ret, dict): if isinstance(ret, dict):
ret = ret.get(self.key) ret = ret.get('value', {})
if isinstance(ret, dict):
ret = ret.get(self.key)
except: except:
self.lg.error(self.mega.values.get(self.port, {}).get('value', {})) self.lg.error(self.mega.values.get(self.port, {}).get('value', {}))
return return

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_DOMAIN, CONF_DOMAIN,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .entities import MegaD from . import hub as h
from .entities import MegaOutPort from .entities import MegaOutPort
from .const import CONF_DIMMER, CONF_SWITCH, DOMAIN, CONF_CUSTOM, CONF_SKIP from .const import CONF_DIMMER, CONF_SWITCH, DOMAIN, CONF_CUSTOM, CONF_SKIP
@@ -45,7 +45,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_devices): async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_devices):
mid = config_entry.data[CONF_ID] mid = config_entry.data[CONF_ID]
hub: MegaD = hass.data['mega'][mid] hub: 'h.MegaD' = hass.data['mega'][mid]
devices = [] devices = []
customize = hass.data.get(DOMAIN, {}).get(CONF_CUSTOM, {}) customize = hass.data.get(DOMAIN, {}).get(CONF_CUSTOM, {})

View File

@@ -0,0 +1,12 @@
_params = ['m', 'click', 'cnt', 'pt']
def make_ints(d: dict):
for x in _params:
try:
d[x] = int(d.get(x, 0))
except (ValueError, TypeError):
pass
if 'm' not in d:
d['m'] = 0
if 'click' not in d:
d['click'] = 0

View File

@@ -17,8 +17,11 @@
"username": "Користувач", "username": "Користувач",
"id": "ID", "id": "ID",
"mqtt_id": "MQTT id", "mqtt_id": "MQTT id",
"scan_interval": "Період оновлення (сек.)", "scan_interval": "Період оновлення (сек.), 0 - не оновлювати",
"port_to_scan": "Порт для сканування при відсутності датчиків" "port_to_scan": "Порт для сканування при відсутності датчиків",
"mqtt_inputs": "Використовувати MQTT",
"nports": "Кількість портів",
"update_all": "Оновити всі виходи коли спрацьовує вхід"
} }
} }
} }
@@ -30,7 +33,10 @@
"scan_interval": "Період оновлення (сек.)", "scan_interval": "Період оновлення (сек.)",
"port_to_scan": "Порт для сканування при відсутності датчиків", "port_to_scan": "Порт для сканування при відсутності датчиків",
"reload": "Оновити об'єкти", "reload": "Оновити об'єкти",
"invert": "Список портів з інвертованою логікою (через ,)" "invert": "Список портів з інвертованою логікою (через ,)",
"mqtt_inputs": "Використовувати MQTT",
"nports": "Кількість портів",
"update_all": "Оновити всі виходи коли спрацьовує вхід"
} }
} }
} }

View File

@@ -81,18 +81,42 @@ script: "mega" # это api интеграции, к которому будет
event_type: mega.sensor event_type: mega.sensor
event_data: event_data:
pt: 1 pt: 1
click: 2
action: action:
- service: light.toggle - service: light.toggle
entity_id: light.some_light entity_id: light.some_light
``` ```
Для binary_sensor имеет смысл использовать режим P&R, для остальных режимов - лучше пользоваться событиями. Для binary_sensor имеет смысл использовать режим P&R, для остальных режимов - лучше пользоваться событиями.
Примеры использования binary_sensor:
```yaml
- alias: обработка долгих/коротких нажатий
trigger:
- platform: state
entity_id: binary_sensor.some_sensor
to: on
for: 1 # задержка на секунду
action:
- choose:
# если кнопка все еще нажата - значит это долгое нажатие
- conditions: "{{ is_state('binary_sensor.some_sensor', 'on')}}"
sequence:
- service: light.turn_on
entity_id: light.some_light
# если кнопка уже не нажата - значит это короткое нажатие
- conditions: "{{ is_state('binary_sensor.some_sensor', 'off')}}"
sequence:
- service: light.turn_off
entity_id: light.some_light
```
## Ответ на входящие события от контроллера ## Ответ на входящие события от контроллера
Контроллер ожидает ответ от сервера, который может быть сценарием (по умолчанию интеграция отвечает `d`, что означает Контроллер ожидает ответ от сервера, который может быть сценарием (по умолчанию интеграция отвечает `d`, что означает
запустить то что прописано в поле act в настройках порта). запустить то что прописано в поле act в настройках порта).
Поддерживаеются шаблоны HA. Это может быть использовано, например, для запоминания яркости (тк сам контроллер этого не Поддерживаеются шаблоны HA. Это может быть использовано, например, для запоминания яркости (тк сам контроллер этого не
умеет). В шаблоне можно использовать параметры, которые передает контроллер (m, click, pt, value) умеет). В шаблоне можно использовать параметры, которые передает контроллер (m, click, pt, mdid, mega_id)
Примеры: Примеры:
```yaml ```yaml
@@ -143,6 +167,7 @@ curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1'
При каждом срабатывании `binary_sensor` так же сообщает о событии типа `mega.sensor`. При каждом срабатывании `binary_sensor` так же сообщает о событии типа `mega.sensor`.
События можно использовать в автоматизациях, например так: События можно использовать в автоматизациях, например так:
```yaml ```yaml
# Пример события с полями как есть прямо из меги
- alias: some double click - alias: some double click
trigger: trigger:
- platform: event - platform: event
@@ -154,6 +179,37 @@ curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1'
- service: light.toggle - service: light.toggle
entity_id: light.some_light entity_id: light.some_light
``` ```
События могут содержать следующие поля:
- mega_id: id как в конфиге HA
- pt: номер порта
- cnt: счетчик срабатываний
- mdid: if как в конфиге контроллера
- click: клик (подробнее в документации меги)
- value: текущее значение (только для mqtt)
- port: номер порта
Начиная с версии 0.3.7 появилось так же событие типа mega.binary:
```yaml
# Пример события с полями как есть прямо из меги
- alias: some long click
trigger:
- platform: event
event_type: mega.binary
event_data:
entity_id: binary_sensor.some_id
type: long
action:
- service: light.toggle
entity_id: light.some_light
```
Возможные варианты поля `type`:
- `long`: долгое нажатие
- `release`: размыкание (с гарантией что не было долгого нажатия)
- `long_release`: размыкание после долгого нажатия
- `press`: замыкание
- `single`: одинарный клик (в режиме кликов)
- `double`: двойной клик
Чтобы понять, какие события происходят, лучше всего воспользоваться панелью разработчика и подписаться Чтобы понять, какие события происходят, лучше всего воспользоваться панелью разработчика и подписаться
на вкладке события на событие `mega.sensor`, понажимать кнопки. на вкладке события на событие `mega.sensor`, понажимать кнопки.