Compare commits

...

32 Commits

Author SHA1 Message Date
Andrey
8146148d0c fix bugs 2021-02-18 10:46:17 +03:00
Andrey
e0eaafd0fa fix bugs 2021-02-18 10:40:09 +03:00
Andrey
51f3eb3b19 fix bugs 2021-02-18 10:23:27 +03:00
Andrey
1716651497 fix bugs 2021-02-18 10:12:01 +03:00
Andrey
a87e8139a7 fix bugs 2021-02-18 09:53:30 +03:00
Andrey
358d29f8fd fix bugs 2021-02-18 09:27:40 +03:00
Andrey
fcce9dcfc1 fix bugs 2021-02-17 22:01:33 +03:00
Andrey
4fe2469a01 fix bugs 2021-02-17 19:10:58 +03:00
Andrey
9fab4fce62 fix config scan for ds2413 2021-02-17 18:08:53 +03:00
Andrey
3c28cf4598 Merge remote-tracking branch 'origin/master' 2021-02-17 18:04:53 +03:00
Andrey
af2360b0a0 edit 2021-02-17 18:04:09 +03:00
andvikt
8f7604c9f6 Update bug-report.md 2021-02-17 17:44:56 +03:00
andvikt
f70d72bf39 Update bug-report.md 2021-02-17 17:44:37 +03:00
Andrey
896e02a457 edit readme 2021-02-17 17:40:26 +03:00
Andrey
bb95c9d312 edit readme 2021-02-17 17:23:23 +03:00
Andrey
7ba1562f12 fix assumed state 2021-02-17 17:11:00 +03:00
Andrey
ec505ac2ef edit readme 2021-02-17 16:50:00 +03:00
Andrey
9544f562ba do not update all if "v" parametr 2021-02-15 21:54:21 +03:00
Andrey
c2422cac9c fix manifest 2021-02-15 21:51:31 +03:00
Andrey
0adba7fc0f add http v parsing 2021-02-15 21:50:49 +03:00
Andrey
39642700ca fix force_d 2021-02-15 19:05:00 +03:00
Andrey
07589e8e3a edit readme 2021-02-11 17:35:58 +03:00
Andrey
5a6903c67e fix config bug 2021-02-11 16:03:10 +03:00
Andrey
6758fd8d8e add server response in mqtt mode 2021-02-11 12:19:17 +03:00
Andrey
a9896c82fe add server response in mqtt mode 2021-02-11 12:00:01 +03:00
Andrey
5ed0b74eff add server response in mqtt mode 2021-02-11 11:57:17 +03:00
Andrey
4fccb23c39 remove port from command sendings 2021-02-10 14:05:16 +03:00
Andrey
03e4d3ff7e Merge remote-tracking branch 'origin/master' 2021-02-09 22:15:27 +03:00
Andrey
7907e0cd85 edit readme 2021-02-09 22:04:39 +03:00
andvikt
5211ee5330 Merge pull request #8 from r7sa/master
Улучшение поддержки I2C устройств

Спасибо большое!
2021-02-09 21:35:05 +03:00
Andrey
4e0f1dddcb add readme about srv loop 2021-02-09 21:30:26 +03:00
Sergey
742a0a9a09 - добавлены два новых класса устройства (давление, освещённость)
- улучшена поддержка I2C устройств, возвращающих только одно значение
2021-02-07 12:29:40 +03:00
14 changed files with 289 additions and 285 deletions

View File

@@ -1,32 +1,61 @@
import asyncio
from urllib.parse import urlparse, parse_qsl
from asyncio import Event, FIRST_COMPLETED
import signal
import typing
from logging import getLogger, DEBUG
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()
stop = Event()
loop = asyncio.get_event_loop()
lg = getLogger(__name__)
lg.setLevel(DEBUG)
async def main():
def make_handler(get_ans: typing.Callable[[dict], str]):
async def handler(
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
):
data = await reader.read(200)
print(data)
message = data.decode()
addr = writer.get_extra_info('peername')
lg.debug('process msg "%s" from %s', message, addr)
try:
(_, p, *_) = message.split(' ')
p = dict(parse_qsl(urlparse(p).query))
lg.debug('query %s', p)
ans = get_ans(p)
ans = f'''HTTP/1.1 200 OK\nContent-Length: {len(ans)}\n\n{ans}'''.encode() # \nContent-Length: 6
ans = b'HTTP/1.1 200 OK\r\n\r\n7:2'
print(ans)
except Exception as exc:
print(exc)
lg.exception('process msg "%s" from %s', message, addr)
ans = '''HTTP/1.1 500\n\n'''.encode()
writer.write(ans)
await writer.drain()
# writer.transport.close()
writer.close()
await writer.wait_closed()
return handler
async def serve():
server = await asyncio.start_server(
handle_echo, '127.0.0.1', 8888)
make_handler(lambda x: '7:2'),
host='0.0.0.0',
port=1111,
)
addr = server.sockets[0].getsockname()
print(f'Serving on {addr}')
async with server:
await server.serve_forever()
await asyncio.wait((server.serve_forever(), stop.wait()), return_when=FIRST_COMPLETED)
asyncio.run(main())
if __name__ == '__main__':
loop.add_signal_handler(
signal.SIGINT, stop.set
)
loop.run_until_complete(serve())

View File

@@ -24,10 +24,4 @@ megad firmware version:
If applicable, add screenshots to help explain your problem.
**LOG**
Просьба прикладывать детальный лог, который можно включить в конфиге так:
```yaml
logger:
default: info
logs:
custom_components.mega: debug
```
Просьба прикладывать детальный лог, подробная инструкция как включать отладку по [ссылке](https://github.com/andvikt/mega_hacs/wiki/Отладка)

View File

@@ -17,37 +17,46 @@ from homeassistant.components import mqtt
from homeassistant.config_entries import ConfigEntry
from .const import DOMAIN, CONF_INVERT, CONF_RELOAD, PLATFORMS, CONF_PORTS, CONF_CUSTOM, CONF_SKIP, CONF_PORT_TO_SCAN, \
CONF_MQTT_INPUTS, CONF_HTTP, CONF_RESPONSE_TEMPLATE, CONF_ACTION, CONF_GET_VALUE, CONF_ALLOW_HOSTS, \
CONF_CONV_TEMPLATE, CONF_ALL
CONF_CONV_TEMPLATE, CONF_ALL, CONF_FORCE_D
from .hub import MegaD
from .config_flow import ConfigFlow
from .http import MegaView
_LOGGER = logging.getLogger(__name__)
CUSTOMIZE_PORT = {
vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool,
vol.Optional(CONF_INVERT, default=False): bool,
vol.Optional(CONF_NAME): vol.Any(str, {
vol.Required(str): str
}),
vol.Optional(CONF_DOMAIN): vol.Any('light', 'switch'),
vol.Optional(CONF_UNIT_OF_MEASUREMENT, description='единицы измерений, либо строка либо мепинг'):
vol.Any(str, {
vol.Required(str): str
}),
vol.Optional(
CONF_RESPONSE_TEMPLATE,
description='шаблон ответа когда на этот порт приходит'
'сообщение из меги '): cv.template,
vol.Optional(CONF_ACTION): cv.script_action, # пока не реализовано
vol.Optional(CONF_GET_VALUE, default=True): bool,
vol.Optional(CONF_CONV_TEMPLATE): cv.template
}
CUSTOMIZE_DS2413 = {
vol.Optional(str.lower, description='адрес и индекс устройства'): CUSTOMIZE_PORT
}
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: {
vol.Optional(CONF_ALLOW_HOSTS): [str],
vol.Required(str, description='id меги из веб-интерфейса'): {
vol.Optional(int, description='номер порта'): {
vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool,
vol.Optional(CONF_INVERT, default=False): bool,
vol.Optional(CONF_NAME): vol.Any(str, {
vol.Required(str): str
}),
vol.Optional(CONF_DOMAIN): vol.Any('light', 'switch'),
vol.Optional(CONF_UNIT_OF_MEASUREMENT, description='единицы измерений, либо строка либо мепинг'):
vol.Any(str, {
vol.Required(str): str
}),
vol.Optional(
CONF_RESPONSE_TEMPLATE,
description='шаблон ответа когда на этот порт приходит'
'сообщение из меги '): cv.template,
vol.Optional(CONF_ACTION): cv.script_action,
vol.Optional(CONF_GET_VALUE, default=True): bool,
vol.Optional(CONF_CONV_TEMPLATE): cv.template
}
vol.Optional(CONF_FORCE_D, description='Принудительно слать d после срабатывания входа', default=False): bool,
vol.Optional(int, description='номер порта'): vol.Any(
CUSTOMIZE_PORT,
CUSTOMIZE_DS2413,
)
}
}
},
@@ -81,7 +90,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
)
hass.services.async_register(
DOMAIN, 'run_cmd', partial(_run_cmd, hass), schema=vol.Schema({
vol.Required('port'): int,
vol.Optional('port'): int,
vol.Required('cmd'): str,
vol.Optional('mega_id'): str,
})
@@ -232,12 +241,11 @@ async def _get_port(hass: HomeAssistant, call: ServiceCall):
@bind_hass
async def _run_cmd(hass: HomeAssistant, call: ServiceCall):
port = call.data.get('port')
mega_id = call.data.get('mega_id')
cmd = call.data.get('cmd')
if mega_id:
hub: MegaD = hass.data[DOMAIN][mega_id]
await hub.send_command(port=port, cmd=cmd)
await hub.request(cmd=cmd)
else:
for hub in hass.data[DOMAIN].values():
await hub.send_command(port=port, cmd=cmd)
await hub.request(cmd=cmd)

View File

@@ -16,7 +16,8 @@ from homeassistant.const import (
CONF_ENTITY_ID,
)
from homeassistant.core import HomeAssistant
from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_CUSTOM, CONF_SKIP, CONF_INVERT
from homeassistant.helpers.template import Template
from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_CUSTOM, CONF_SKIP, CONF_INVERT, CONF_RESPONSE_TEMPLATE
from .entities import MegaPushEntity
from .hub import MegaD
@@ -89,4 +90,20 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
def _update(self, payload: dict):
self.mega.values[self.port] = payload
if not self.mega.mqtt_inputs:
return
template: Template = self.customize.get(CONF_RESPONSE_TEMPLATE, None)
if template is not None:
template.hass = self.hass
ret = template.async_render(payload)
self.mega.lg.debug(f'response: %s', ret)
self.hass.async_create_task(
self.mega.request(pt=self.port, cmd=ret)
)
elif self.mega.force_d:
self.mega.lg.debug(f'response d')
self.hass.async_create_task(
self.mega.request(pt=self.port, cmd='d')
)

View File

@@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_ID, default='def'): str,
vol.Required(CONF_ID, default='mega'): str,
vol.Required(CONF_HOST, default="192.168.0.14"): str,
vol.Required(CONF_PASSWORD, default="sec"): str,
vol.Optional(CONF_SCAN_INTERVAL, default=0): int,
@@ -57,7 +57,7 @@ async def validate_input(hass: core.HomeAssistant, data):
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for mega."""
VERSION = 4
VERSION = 6
CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED
async def async_step_user(self, user_input=None):

View File

@@ -27,6 +27,7 @@ CONF_GET_VALUE = 'get_value'
CONF_ALLOW_HOSTS = 'allow_hosts'
CONF_CONV_TEMPLATE = 'conv_template'
CONF_POLL_OUTS = 'poll_outs'
CONF_FORCE_D = 'force_d'
PLATFORMS = [
"light",
"switch",
@@ -42,5 +43,6 @@ LONG = 'long'
RELEASE = 'release'
LONG_RELEASE = 'long_release'
PRESS = 'press'
LUX = 'lux'
SINGLE_CLICK = 'single'
DOUBLE_CLICK = 'double'

View File

@@ -43,8 +43,14 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
id_suffix=None,
name=None,
unique_id=None,
http_cmd='get',
addr: str=None,
index=None,
):
super().__init__(mega.updater)
self.http_cmd = http_cmd
self._state: State = None
self.port = port
self.config_entry = config_entry
@@ -57,6 +63,10 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
self._name = name or f"{mega.id}_{port}" + \
(f"_{id_suffix}" if id_suffix else "")
self._customize: dict = None
self.index = index
self.addr = addr
if self.http_cmd == 'ds2413':
self.mega.ds2413_ports |= {self.port}
@property
def customize(self):
@@ -66,7 +76,11 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
c = self.hass.data.get(DOMAIN, {}).get(CONF_CUSTOM) or {}
c = c.get(self._mega_id) or {}
c = c.get(self.port) or {}
if self.addr is not None and self.index is not None and isinstance(c, dict):
idx = self.addr.lower() + f'_a' if self.index == 0 else '_b'
c = c.get(idx, {})
self._customize = c
return self._customize
@property
@@ -115,7 +129,8 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
_task_set_ev_on = asyncio.create_task(_set_events_on())
async def get_state(self):
if self.mega.mqtt is None:
self.lg.debug(f'state is %s', self.state)
if not self.mega.mqtt_inputs:
self.async_write_ha_state()
@@ -208,12 +223,18 @@ class MegaOutPort(MegaPushEntity):
self._is_on = None
self.dimmer = dimmer
# @property
# def assumed_state(self) -> bool:
# return True if self.index is not None or self.mega.mqtt is None else False
@property
def invert(self):
return self.customize.get(CONF_INVERT, False)
@property
def brightness(self):
if not self.dimmer:
return
val = self.mega.values.get(self.port, {}).get("value")
if val is None and self._state is not None:
return self._state.attributes.get("brightness")
@@ -232,11 +253,36 @@ class MegaOutPort(MegaPushEntity):
return self._state == 'ON'
elif val is not None:
val = val.get("value")
if self.index and self.addr:
if not isinstance(val, dict):
self.mega.lg.warning(f'{self.entity_id}: {val} is not a dict')
return
_val = val.get(self.addr, val.get(self.addr.lower(), val.get(self.addr.upper())))
if not isinstance(val, str):
self.mega.lg.warning(f'{self.entity_id}: can not get {self.addr} from {val}, recieved {_val}')
return
_val = _val.split('/')
if len(_val) >= 2:
val = _val[self.index]
else:
self.mega.lg.warning(f'{self.entity_id}: {_val} has wrong length')
return
elif self.index and self.addr is None:
self.mega.lg.warning(f'{self.entity_id} does not has addr')
return
if not self.invert:
return val == 'ON' or str(val) == '1' or (safe_int(val) is not None and safe_int(val) > 0)
else:
return val == 'OFF' or str(val) == '0' or (safe_int(val) is not None and safe_int(val) == 0)
@property
def cmd_port(self):
if self.index is not None:
return f'{self.port}A' if self.index == 0 else f'{self.port}B'
else:
return self.port
async def async_turn_on(self, brightness=None, **kwargs) -> None:
brightness = brightness or self.brightness or 255
@@ -246,17 +292,39 @@ class MegaOutPort(MegaPushEntity):
cmd = brightness
else:
cmd = 1 if not self.invert else 0
await self.mega.send_command(self.port, f"{self.port}:{cmd}")
self.mega.values[self.port] = {'value': cmd}
_cmd = {"cmd": f"{self.cmd_port}:{cmd}"}
if self.addr:
_cmd['addr'] = self.addr
await self.mega.request(**_cmd)
if self.index is not None:
# обновление текущего стейта для ds2413
await self.mega.get_port(
port=self.port,
force_http=True,
conv=False,
http_cmd='list',
)
else:
self.mega.values[self.port] = {'value': cmd}
await self.get_state()
async def async_turn_off(self, **kwargs) -> None:
cmd = "0" if not self.invert else "1"
await self.mega.send_command(self.port, f"{self.port}:{cmd}")
self.mega.values[self.port] = {'value': cmd}
_cmd = {"cmd": f"{self.cmd_port}:{cmd}"}
if self.addr:
_cmd['addr'] = self.addr
await self.mega.request(**_cmd)
if self.index is not None:
# обновление текущего стейта для ds2413
await self.mega.get_port(
port=self.port,
force_http=True,
conv=False,
http_cmd='list',
)
else:
self.mega.values[self.port] = {'value': cmd}
await self.get_state()

View File

@@ -15,3 +15,7 @@ class DuplicateId(exceptions.HomeAssistantError):
class InvalidAuth(exceptions.HomeAssistantError):
"""Error to indicate there is invalid auth."""
class NoPort(Exception):
pass

View File

@@ -8,16 +8,15 @@ from aiohttp.web_request import Request
from aiohttp.web_response import Response
from homeassistant.helpers.template import Template
from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_RESPONSE_TEMPLATE
from homeassistant.components.http import HomeAssistantView
from homeassistant.core import HomeAssistant
from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_RESPONSE_TEMPLATE
from .tools import make_ints
from . import hub as h
_LOGGER = logging.getLogger(__name__).getChild('http')
class MegaView(HomeAssistantView):
"""Handle Yandex Smart Home unauthorized requests."""
url = '/mega'
name = 'mega'
@@ -31,8 +30,8 @@ class MegaView(HomeAssistantView):
mid: {
pt: cfg[mid][pt][CONF_RESPONSE_TEMPLATE]
for pt in cfg[mid]
if CONF_RESPONSE_TEMPLATE in cfg[mid][pt]
} for mid in cfg
if isinstance(pt, int) and CONF_RESPONSE_TEMPLATE in cfg[mid][pt]
} for mid in cfg if isinstance(cfg[mid], dict)
}
_LOGGER.debug('templates: %s', self.templates)
@@ -62,6 +61,10 @@ class MegaView(HomeAssistantView):
make_ints(data)
port = data.get('pt')
data = data.copy()
update_all = True
if 'v' in data:
update_all = False
data['value'] = data.pop('v')
data['mega_id'] = hub.id
ret = 'd'
if port is not None:
@@ -69,7 +72,7 @@ class MegaView(HomeAssistantView):
for cb in self.callbacks[hub.id][port]:
cb(data)
template: Template = self.templates.get(hub.id, {}).get(port)
if hub.update_all:
if hub.update_all and update_all:
asyncio.create_task(self.later_update(hub))
if template is not None:
template.hass = hass

View File

@@ -10,32 +10,49 @@ import json
from bs4 import BeautifulSoup
from homeassistant.components import mqtt
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY
from homeassistant.const import (
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_ILLUMINANCE, TEMP_CELSIUS, PERCENTAGE, LIGHT_LUX
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import TEMP, HUM, PATT_SPLIT, DOMAIN, CONF_HTTP, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_SKIP
from .const import (
TEMP, HUM, PRESS,
LUX, PATT_SPLIT, DOMAIN,
CONF_HTTP, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_FORCE_D
)
from .entities import set_events_off, BaseMegaEntity
from .exceptions import CannotConnect
from .exceptions import CannotConnect, NoPort
from .tools import make_ints
TEMP_PATT = re.compile(r'temp:([01234567890\.]+)')
HUM_PATT = re.compile(r'hum:([01234567890\.]+)')
PRESS_PATT = re.compile(r'press:([01234567890\.]+)')
LUX_PATT = re.compile(r'lux:([01234567890\.]+)')
PATTERNS = {
TEMP: TEMP_PATT,
HUM: HUM_PATT,
PRESS: PRESS_PATT,
LUX: LUX_PATT
}
UNITS = {
TEMP: '°C',
HUM: '%'
TEMP: TEMP_CELSIUS,
HUM: PERCENTAGE,
PRESS: 'mmHg',
LUX: LIGHT_LUX
}
CLASSES = {
TEMP: DEVICE_CLASS_TEMPERATURE,
HUM: DEVICE_CLASS_HUMIDITY
HUM: DEVICE_CLASS_HUMIDITY,
PRESS: DEVICE_CLASS_PRESSURE,
LUX: DEVICE_CLASS_ILLUMINANCE
}
I2C_DEVICE_TYPES = {
"2": LUX, # BH1750
"3": LUX, # TSL2591
"7": LUX, # MAX44009
"70": LUX, # OPT3001
}
class NoPort(Exception):
pass
class MegaD:
@@ -84,6 +101,7 @@ class MegaD:
self.cnd = asyncio.Condition()
self.online = True
self.entities: typing.List[BaseMegaEntity] = []
self.ds2413_ports = set()
self.poll_interval = scan_interval
self.subs = None
self.lg: logging.Logger = lg.getChild(self.id)
@@ -149,6 +167,10 @@ class MegaD:
self._customize = c
return self._customize
@property
def force_d(self):
return self.customize.get(CONF_FORCE_D, False)
@property
def is_online(self):
return (datetime.now() - self.last_update).total_seconds() < (self.poll_interval + 10)
@@ -170,10 +192,23 @@ class MegaD:
)
self.online = True
async def _get_ds2413(self):
"""
обновление ds2413 устройств
:return:
"""
for x in self.ds2413_ports:
self.lg.debug(f'poll ds2413 for %s', x)
await self.get_port(
port=x,
force_http=True,
http_cmd='list',
conv=False
)
async def poll(self):
"""
Send get port 0 every poll_interval. When answer is received, mega.<id> becomes online else mega.<id> becomes
offline
Polling ports
"""
self.lg.debug('poll')
if self.mqtt is None:
@@ -185,6 +220,7 @@ class MegaD:
await self.get_sensors()
else:
await self.get_port(self.port_to_scan)
await self._get_ds2413()
return self.values
async def get_mqtt_id(self):
@@ -224,7 +260,7 @@ class MegaD:
if 'busy' in ret:
return None
if ':' in ret:
ret = PATT_SPLIT.split(ret)
ret = ret.split(';')
ret = {'value': dict([
x.split(':') for x in ret if x.count(':') == 1
])}
@@ -236,14 +272,14 @@ class MegaD:
ret = {'value': ret}
return ret
async def get_port(self, port, force_http=False, http_cmd='get'):
async def get_port(self, port, force_http=False, http_cmd='get', conv=True):
"""
Запрос состояния порта. Состояние всегда возвращается в виде объекта, всегда сохраняется в центральное
хранилище values
"""
self.lg.debug(f'get port %s', port)
if self.mqtt is None or force_http:
if http_cmd == 'list':
if http_cmd == 'list' and conv:
await self.request(pt=port, cmd='conv')
await asyncio.sleep(1)
ret = self.parse_response(await self.request(pt=port, cmd=http_cmd))
@@ -279,6 +315,8 @@ class MegaD:
if not self.mqtt_inputs:
ret = await self.request(cmd='all')
for port, x in enumerate(ret.split(';')):
if port in self.ds2413_ports:
continue
if check_skip and not port in self.ports:
continue
ret = self.parse_response(x)
@@ -398,8 +436,11 @@ class MegaD:
self._scanned[port] = (pty, m)
return pty, m
elif pty in ('2', '4'): # эта часть не очень проработана, тут есть i2c который может работать неправильно
self._scanned[port] = (pty, '0')
return pty, '0'
m = tree.find('select', attrs={'name': 'd'})
if m:
m = m.find(selected=True)['value']
self._scanned[port] = (pty, m or '0')
return pty, m or '0'
async def scan_ports(self, nports=37):
for x in range(0, nports+1):
@@ -415,6 +456,18 @@ class MegaD:
ret['binary_sensor'][port].append({})
elif pty == "1" and (m in ['0', '1', '3'] or m is None):
ret['light'][port].append({'dimmer': m == '1'})
elif pty == "1" and m == "2":
# ds2413
_data = await self.get_port(port=port, force_http=True, http_cmd='list', conv=False)
data = _data.get('value', {})
if not isinstance(data, dict):
self.lg.warning(f'can not add ds2413 on port {port}, it has wrong data: {_data}')
continue
for addr, state in data.items():
ret['light'][port].extend([
{"index": 0, "addr": addr, "id_suffix": f'{addr}_a', "http_cmd": 'ds2413'},
{"index": 1, "addr": addr, "id_suffix": f'{addr}_b', "http_cmd": 'ds2413'},
])
elif pty in ('3', '2', '4'):
try:
http_cmd = 'get'
@@ -434,7 +487,10 @@ class MegaD:
if isinstance(values, str) and TEMP_PATT.search(values):
values = {TEMP: values}
elif not isinstance(values, dict):
values = {None: values}
if pty == '4' and m in I2C_DEVICE_TYPES:
values = {I2C_DEVICE_TYPES[m]: values}
else:
values = {None: values}
for key in values:
self.lg.debug(f'add sensor {key}')
ret['sensor'][port].append(dict(

View File

@@ -15,5 +15,5 @@
"@andvikt"
],
"issue_tracker": "https://github.com/andvikt/mega_hacs/issues",
"version": "v0.3.13"
"version": "v0.4.1b7"
}

View File

@@ -102,7 +102,6 @@ class Mega1WSensor(MegaPushEntity):
unit_of_measurement,
device_class,
key=None,
http_cmd='get',
*args,
**kwargs
):
@@ -118,7 +117,6 @@ class Mega1WSensor(MegaPushEntity):
self._device_class = device_class
self._unit_of_measurement = unit_of_measurement
self.mega.sensors.append(self)
self.http_cmd = http_cmd
@property
def unit_of_measurement(self):

View File

@@ -25,9 +25,10 @@ run_cmd:
mega_id:
description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги
example: "mega"
port:
description: Номер порта (это не порт, которым мы управляем, а порт с которого шлем команду)
example: 1
cmd:
description: Любая поддерживаемая мегой команда
example: "1:0"
port:
description: (Deprecated, больше не нужен) Номер порта (это не порт, которым мы управляем, а порт с которого шлем команду)
example: 1

210
readme.md
View File

@@ -4,8 +4,14 @@
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs)
[![Donate](https://img.shields.io/badge/donate-Yandex-red.svg)](https://yoomoney.ru/to/410013955329136)
Интеграция с [MegaD-2561](https://www.ab-log.ru/smart-house/ethernet/megad-2561)
Интеграция с [MegaD-2561, MegaD-328](https://www.ab-log.ru/smart-house/ethernet/megad-2561)
Если вам понравилась интеграция, не забудьте поставить звезду на гитхабе - вам не сложно, а мне приятно ) А если
интеграция очень понравилась - еще приятнее, если вы воспользуетесь кнопкой доната )
Обновление прошивки MegaD можно делать прямо из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git)
Подробная документация по [ссылке](https://github.com/andvikt/mega_hacs/wiki)
## Основные особенности:
- Настройка в веб-интерфейсе + yaml
- Все порты автоматически добавляются как устройства (для обычных релейных выходов создается
@@ -17,12 +23,20 @@
- Команды выполняются друг за другом без конкурентного доступа к ресурсам megad, это дает гарантии надежного исполнения
большого кол-ва команд (например в сценах). Каждая следующая команда отправляется только после получения ответа о
выполнении предыдущей.
- поддержка ds2413 (начиная с версии 0.4.1)
## Установка
Рекомендованный способ с поддержкой обновлений - [HACS](https://hacs.xyz/docs/installation/installation):
HACS - Integrations - Explore, в поиске ищем MegaD.
Чтобы включить возможность использования бета-версий, зайдите в HACS, найдите интеграцию MegaD, нажмите три точки,
там кнопка "переустановить" или reinstall, дальше нужно нажать галку "показывать бета-версии"
Обновления выполняются так же в меню HACS.
Информация об обновлениях приходит с некоторым интервалом, чтобы вручную проверить наличие обновлений
нажмите три точки возле интеграции в меню HACS и нажмите `обновить информацию`
Альтернативный способ установки:
```shell
# из папки с конфигом
@@ -35,197 +49,7 @@ wget -q -O - https://raw.githubusercontent.com/andvikt/mega_hacs/master/install.
Все имеющиеся у вас порты будут настроены автоматически. Вы можете менять названия, иконки и entity_id так же из интерфейса.
#### Кастомизация устройств с помощью yaml:
```yaml
# configuration.yaml
mega:
hello: # ID меги, как в UI
7: # номер порта
domain: switch # тип устройства (switch или light, по умолчанию для цифровых выходов используется light)
invert: true # инвертировать или нет (по умолчанию false)
name: Насос # имя устройства
8:
# исключить из сканирования
skip: true
33:
# для датчиков можно кастомизировать только имя и unit_of_measurement
# для температуры и влажность unit определяется автоматически, для остальных юнита нет
name:
hum: "влажность"
temp: "температура"
unit_of_measurement:
hum: "%" # если датчиков несколько, то можно указывать юниты по их ключам
temp: "°C"
# можно так же указать шаблон для конвертации значения, может быть полезно для ацп-входа
# текущее значение порта передается в шаблон в переменной "value"
conv_template: "{{(value|float)/100}}"
14:
name: какой-то датчик
unit_of_measurement: "°C" # если датчик один, то просто строчкой
```
## Зависимости
Для совместимости c mqtt необходимо настроить интеграцию [mqtt](https://www.home-assistant.io/integrations/mqtt/)
в HomeAssistant, а так же обновить ваш контроллер до последней версии, тк были важные обновления в части mqtt
## HTTP in
Начиная с версии `0.3.1` интеграция стала поддерживать обратную связь без mqtt, используя http-сервер. Для этого в настройках
интеграции необходимо снять галку с `использовать mqtt`
В самой меге необходимо прописать настройки:
```yaml
srv: "192.168.1.4:8123" # ip:port вашего HA
script: "mega" # это api интеграции, к которому будет обращаться контроллер
```
#### Ответ на входящие события от контроллера
Контроллер ожидает ответ от сервера, который может быть сценарием (по умолчанию интеграция отвечает `d`, что означает
запустить то что прописано в поле act в настройках порта).
Поддерживаются шаблоны HA. Это может быть использовано, например, для запоминания яркости (тк сам контроллер этого не
умеет). В шаблоне можно использовать параметры, которые передает контроллер (m, click, pt, mdid, mega_id)
Примеры:
```yaml
mega:
mega1: # id меги, который вы сами придумываете в конфиге в UI
4: # номер порта, с которого ожидаются события
response_template: "5:2" # простейший пример без шаблона. Каждый раз когда будет приходить сообщение на этот порт,
# будем менять состояние на противоположное
5:
# пример с использованием шаблона, порт 1 будет выключен если он сейчас включен и включен с последней сохраненной
# яркостью если он сейчас выключен
response_template: >-
{% if is_state('light.some_port_1', 'on') %}
1:0
{% else %}
1:{{state_attr('light.some_port_1', 'brightness')}}
{% endif %}
6:
# в шаблон так же передаются все параметры, которые передает контроллер (pt, cnt, m, click)
# эти параметры можно использовать в условиях или непосредственно в шаблоне в виде {{pt}}
response_template: >-
{% if m==2 %}1:0{% else %}d{% endif %}
```
## binary_sensor и события
Входы будут доступны как binary_sensor, а так же в виде событий `mega.sensor` и `mega.binary`.
Для корректной работы binary_sensor имеет смысл использовать режим P&R, для остальных режимов - лучше пользоваться
событиями.
События можно использовать в автоматизациях, например так:
```yaml
# Пример события с полями как есть прямо из меги
- alias: some double click
trigger:
- platform: event
event_type: mega.sensor
event_data:
pt: 1
click: 2
action:
- service: light.toggle
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`: двойной клик
**гарантия есть только при использовании http-метода синхронизации, mqtt не гарантирует порядок доставки сообщений, хотя
маловероятно, что порядок будет нарушен, но все же сам протокол не дает таких гарантий.
Чтобы понять, какие события происходят, лучше всего воспользоваться панелью разработчика и подписаться
на вкладке события на событие `mega.sensor`, понажимать кнопки.
## Сервисы
Все сервисы доступны в меню разработчика с описанием и примерами использования
```yaml
mega.save:
description: Сохраняет текущее состояние портов (?cmd=s)
fields:
mega_id:
description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги
example: "mega"
mega.get_port:
description: Запросить текущий статус порта (или всех)
fields:
mega_id:
description: ID меги, можно оставить пустым, тогда будут порты всех зарегистрированных мег
example: "mega"
port:
description: Номер порта (если не заполнять, будут запрошены все порты сразу)
example: 1
mega.run_cmd:
description: Выполнить любую произвольную команду
fields:
mega_id:
description: ID меги
example: "mega"
port:
description: Номер порта (это не порт, которым мы управляем, а порт с которого шлем команду)
example: 1
cmd:
description: Любая поддерживаемая мегой команда
example: "1:0"
```
## Отладка
Интеграция находится в активной разработке, при возникновении проблем [заводите issue](https://github.com/andvikt/mega_hacs/issues/new/choose)
Просьба прикладывать детальный лог, который можно включить в конфиге так:
```yaml
logger:
default: info
logs:
custom_components.mega: debug
```
#### Отладка ответов http-сервера
Для отладки ответов сервера можно самим имитировать запросы контроллера, если у вас есть доступ к консоли
HA:
```shell
curl -v -X GET 'http://localhost:8123/mega?pt=5&m=1'
```
Если доступа нет, нужно в файл конфигурации добавить ip, с которого вы хотите делать запросы, например:
```yaml
mega:
allow_hosts:
- 192.168.1.1
```
И тогда можно с локальной машины делать запросы на ваш сервер HA:
```shell
curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1'
```
В ответ будет приходить либо `d`, либо скрипт, который вы настроили
в HomeAssistant, а так же обновить ваш контроллер до последней версии, обновление прошивки MegaD можно делать прямо
из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git)