mirror of
https://github.com/andvikt/mega_hacs.git
synced 2025-12-12 01:24:29 +05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9544f562ba | ||
|
|
c2422cac9c | ||
|
|
0adba7fc0f | ||
|
|
39642700ca | ||
|
|
07589e8e3a | ||
|
|
5a6903c67e | ||
|
|
6758fd8d8e | ||
|
|
a9896c82fe | ||
|
|
5ed0b74eff | ||
|
|
4fccb23c39 | ||
|
|
03e4d3ff7e | ||
|
|
7907e0cd85 | ||
|
|
5211ee5330 | ||
|
|
4e0f1dddcb | ||
|
|
742a0a9a09 | ||
|
|
59443989a0 | ||
|
|
5b86ceefe4 | ||
|
|
8cf000beae | ||
|
|
99317da9f6 |
13
.github/ISSUE_TEMPLATE/bug-report.md
vendored
13
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -8,19 +8,26 @@ assignees: ''
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Описание**
|
**Описание**
|
||||||
A clear and concise description of what the bug is.
|
Описание проблемы
|
||||||
|
|
||||||
**Версии систем**
|
**Версии систем**
|
||||||
Enviroment: raspberry/linux/windows/macos/docker
|
Enviroment: raspberry/linux/windows/macos/docker
|
||||||
HA version:
|
HA version:
|
||||||
mega_hacs version:
|
mega_hacs version:
|
||||||
megad firmware version:
|
megad firmware version:
|
||||||
|
используется mqtt: true/false
|
||||||
|
|
||||||
**Ожидаемое поведение**
|
**Ожидаемое поведение**
|
||||||
A clear and concise description of what you expected to happen.
|
Описание правильного поведения
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**LOG**
|
**LOG**
|
||||||
Прочитайте в документации как включить подробный лог интеграции и приложите его здесь
|
Просьба прикладывать детальный лог, который можно включить в конфиге так:
|
||||||
|
```yaml
|
||||||
|
logger:
|
||||||
|
default: info
|
||||||
|
logs:
|
||||||
|
custom_components.mega: debug
|
||||||
|
```
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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, PLATFORMS, CONF_PORTS, CONF_CUSTOM, CONF_SKIP, CONF_PORT_TO_SCAN, \
|
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_MQTT_INPUTS, CONF_HTTP, CONF_RESPONSE_TEMPLATE, CONF_ACTION, CONF_GET_VALUE, CONF_ALLOW_HOSTS, \
|
||||||
CONF_CONV_TEMPLATE
|
CONF_CONV_TEMPLATE, CONF_ALL, CONF_FORCE_D
|
||||||
from .hub import MegaD
|
from .hub import MegaD
|
||||||
from .config_flow import ConfigFlow
|
from .config_flow import ConfigFlow
|
||||||
from .http import MegaView
|
from .http import MegaView
|
||||||
@@ -28,7 +28,9 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
{
|
{
|
||||||
DOMAIN: {
|
DOMAIN: {
|
||||||
vol.Optional(CONF_ALLOW_HOSTS): [str],
|
vol.Optional(CONF_ALLOW_HOSTS): [str],
|
||||||
|
# vol.Optional(CONF_FORCE_D, description='Принудительно слать d после срабатывания входа', default=False): bool,
|
||||||
vol.Required(str, description='id меги из веб-интерфейса'): {
|
vol.Required(str, description='id меги из веб-интерфейса'): {
|
||||||
|
vol.Optional(CONF_FORCE_D, description='Принудительно слать d после срабатывания входа', default=False): bool,
|
||||||
vol.Optional(int, description='номер порта'): {
|
vol.Optional(int, description='номер порта'): {
|
||||||
vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool,
|
vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool,
|
||||||
vol.Optional(CONF_INVERT, default=False): bool,
|
vol.Optional(CONF_INVERT, default=False): bool,
|
||||||
@@ -44,7 +46,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
CONF_RESPONSE_TEMPLATE,
|
CONF_RESPONSE_TEMPLATE,
|
||||||
description='шаблон ответа когда на этот порт приходит'
|
description='шаблон ответа когда на этот порт приходит'
|
||||||
'сообщение из меги '): cv.template,
|
'сообщение из меги '): cv.template,
|
||||||
vol.Optional(CONF_ACTION): cv.script_action,
|
vol.Optional(CONF_ACTION): cv.script_action, # пока не реализовано
|
||||||
vol.Optional(CONF_GET_VALUE, default=True): bool,
|
vol.Optional(CONF_GET_VALUE, default=True): bool,
|
||||||
vol.Optional(CONF_CONV_TEMPLATE): cv.template
|
vol.Optional(CONF_CONV_TEMPLATE): cv.template
|
||||||
}
|
}
|
||||||
@@ -65,6 +67,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
"""YAML-конфигурация содержит только кастомизации портов"""
|
"""YAML-конфигурация содержит только кастомизации портов"""
|
||||||
hass.data[DOMAIN] = {CONF_CUSTOM: config.get(DOMAIN, {})}
|
hass.data[DOMAIN] = {CONF_CUSTOM: config.get(DOMAIN, {})}
|
||||||
hass.data[DOMAIN][CONF_HTTP] = view = MegaView(cfg=config.get(DOMAIN, {}))
|
hass.data[DOMAIN][CONF_HTTP] = view = MegaView(cfg=config.get(DOMAIN, {}))
|
||||||
|
hass.data[DOMAIN][CONF_ALL] = {}
|
||||||
view.allowed_hosts |= set(config.get(DOMAIN, {}).get(CONF_ALLOW_HOSTS, []))
|
view.allowed_hosts |= set(config.get(DOMAIN, {}).get(CONF_ALLOW_HOSTS, []))
|
||||||
hass.http.register_view(view)
|
hass.http.register_view(view)
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
@@ -80,7 +83,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
)
|
)
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN, 'run_cmd', partial(_run_cmd, hass), schema=vol.Schema({
|
DOMAIN, 'run_cmd', partial(_run_cmd, hass), schema=vol.Schema({
|
||||||
vol.Required('port'): int,
|
vol.Optional('port'): int,
|
||||||
vol.Required('cmd'): str,
|
vol.Required('cmd'): str,
|
||||||
vol.Optional('mega_id'): str,
|
vol.Optional('mega_id'): str,
|
||||||
})
|
})
|
||||||
@@ -115,6 +118,7 @@ async def _add_mega(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
hub = await get_hub(hass, entry)
|
hub = await get_hub(hass, entry)
|
||||||
hass.data[DOMAIN][id] = hass.data[DOMAIN]['__def'] = hub
|
hass.data[DOMAIN][id] = hass.data[DOMAIN]['__def'] = hub
|
||||||
hass.data[DOMAIN][entry.data.get(CONF_HOST)] = hub
|
hass.data[DOMAIN][entry.data.get(CONF_HOST)] = hub
|
||||||
|
hass.data[DOMAIN][CONF_ALL][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()
|
||||||
@@ -164,6 +168,7 @@ async def async_remove_entry(hass, entry) -> None:
|
|||||||
_LOGGER.debug(f'remove {id}')
|
_LOGGER.debug(f'remove {id}')
|
||||||
_hubs.pop(id, None)
|
_hubs.pop(id, None)
|
||||||
hass.data[DOMAIN].pop(id, None)
|
hass.data[DOMAIN].pop(id, None)
|
||||||
|
hass.data[DOMAIN][CONF_ALL].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()
|
||||||
@@ -215,23 +220,25 @@ async def _get_port(hass: HomeAssistant, call: ServiceCall):
|
|||||||
for x in port:
|
for x in port:
|
||||||
await hub.get_port(x)
|
await hub.get_port(x)
|
||||||
else:
|
else:
|
||||||
for hub in hass.data[DOMAIN].values():
|
for hub in hass.data[DOMAIN][CONF_ALL].values():
|
||||||
if not isinstance(hub, MegaD):
|
if not isinstance(hub, MegaD):
|
||||||
continue
|
continue
|
||||||
if port is None:
|
if port is None:
|
||||||
await hub.get_all_ports()
|
await hub.get_all_ports(check_skip=True)
|
||||||
else:
|
elif isinstance(port, int):
|
||||||
await hub.get_port(port)
|
await hub.get_port(port)
|
||||||
|
elif isinstance(port, list):
|
||||||
|
for x in port:
|
||||||
|
await hub.get_port(x)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
async def _run_cmd(hass: HomeAssistant, call: ServiceCall):
|
async def _run_cmd(hass: HomeAssistant, call: ServiceCall):
|
||||||
port = call.data.get('port')
|
|
||||||
mega_id = call.data.get('mega_id')
|
mega_id = call.data.get('mega_id')
|
||||||
cmd = call.data.get('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.request(cmd=cmd)
|
||||||
else:
|
else:
|
||||||
for hub in hass.data[DOMAIN].values():
|
for hub in hass.data[DOMAIN].values():
|
||||||
await hub.send_command(port=port, cmd=cmd)
|
await hub.request(cmd=cmd)
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ from homeassistant.const import (
|
|||||||
CONF_ENTITY_ID,
|
CONF_ENTITY_ID,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
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 .entities import MegaPushEntity
|
||||||
from .hub import MegaD
|
from .hub import MegaD
|
||||||
|
|
||||||
@@ -89,4 +90,20 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
|
|||||||
|
|
||||||
def _update(self, payload: dict):
|
def _update(self, payload: dict):
|
||||||
self.mega.values[self.port] = payload
|
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')
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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, HomeAssistant
|
from homeassistant.core import callback, HomeAssistant
|
||||||
from .const import DOMAIN, CONF_PORT_TO_SCAN, CONF_RELOAD, PLATFORMS, CONF_MQTT_INPUTS, \
|
from .const import DOMAIN, CONF_PORT_TO_SCAN, CONF_RELOAD, PLATFORMS, CONF_MQTT_INPUTS, \
|
||||||
CONF_NPORTS, CONF_UPDATE_ALL # pylint:disable=unused-import
|
CONF_NPORTS, CONF_UPDATE_ALL, CONF_POLL_OUTS # pylint:disable=unused-import
|
||||||
from .hub import MegaD
|
from .hub import MegaD
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
|
||||||
@@ -18,10 +18,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
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_HOST, default="192.168.0.14"): str,
|
||||||
vol.Required(CONF_PASSWORD, default="sec"): str,
|
vol.Required(CONF_PASSWORD, default="sec"): str,
|
||||||
vol.Optional(CONF_SCAN_INTERVAL, default=0): int,
|
vol.Optional(CONF_SCAN_INTERVAL, default=0): int,
|
||||||
|
vol.Optional(CONF_POLL_OUTS, default=False): bool,
|
||||||
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,
|
||||||
@@ -131,6 +132,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
step_id="init",
|
step_id="init",
|
||||||
data_schema=vol.Schema({
|
data_schema=vol.Schema({
|
||||||
vol.Optional(CONF_SCAN_INTERVAL, default=e.get(CONF_SCAN_INTERVAL, 0)): int,
|
vol.Optional(CONF_SCAN_INTERVAL, default=e.get(CONF_SCAN_INTERVAL, 0)): int,
|
||||||
|
vol.Optional(CONF_POLL_OUTS, default=e.get(CONF_POLL_OUTS, False)): bool,
|
||||||
vol.Optional(CONF_PORT_TO_SCAN, default=e.get(CONF_PORT_TO_SCAN, 0)): int,
|
vol.Optional(CONF_PORT_TO_SCAN, default=e.get(CONF_PORT_TO_SCAN, 0)): int,
|
||||||
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,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ CONF_INVERT = 'invert'
|
|||||||
CONF_PORTS = 'ports'
|
CONF_PORTS = 'ports'
|
||||||
CONF_CUSTOM = '__custom'
|
CONF_CUSTOM = '__custom'
|
||||||
CONF_HTTP = '__http'
|
CONF_HTTP = '__http'
|
||||||
|
CONF_ALL = '__all'
|
||||||
CONF_SKIP = 'skip'
|
CONF_SKIP = 'skip'
|
||||||
CONF_MQTT_INPUTS = 'mqtt_inputs'
|
CONF_MQTT_INPUTS = 'mqtt_inputs'
|
||||||
CONF_NPORTS = 'nports'
|
CONF_NPORTS = 'nports'
|
||||||
@@ -25,6 +26,8 @@ CONF_UPDATE_ALL = 'update_all'
|
|||||||
CONF_GET_VALUE = 'get_value'
|
CONF_GET_VALUE = 'get_value'
|
||||||
CONF_ALLOW_HOSTS = 'allow_hosts'
|
CONF_ALLOW_HOSTS = 'allow_hosts'
|
||||||
CONF_CONV_TEMPLATE = 'conv_template'
|
CONF_CONV_TEMPLATE = 'conv_template'
|
||||||
|
CONF_POLL_OUTS = 'poll_outs'
|
||||||
|
CONF_FORCE_D = 'force_d'
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
"light",
|
"light",
|
||||||
"switch",
|
"switch",
|
||||||
@@ -40,5 +43,6 @@ LONG = 'long'
|
|||||||
RELEASE = 'release'
|
RELEASE = 'release'
|
||||||
LONG_RELEASE = 'long_release'
|
LONG_RELEASE = 'long_release'
|
||||||
PRESS = 'press'
|
PRESS = 'press'
|
||||||
|
LUX = 'lux'
|
||||||
SINGLE_CLICK = 'single'
|
SINGLE_CLICK = 'single'
|
||||||
DOUBLE_CLICK = 'double'
|
DOUBLE_CLICK = 'double'
|
||||||
|
|||||||
@@ -246,16 +246,15 @@ class MegaOutPort(MegaPushEntity):
|
|||||||
cmd = brightness
|
cmd = brightness
|
||||||
else:
|
else:
|
||||||
cmd = 1 if not self.invert else 0
|
cmd = 1 if not self.invert else 0
|
||||||
await self.mega.send_command(self.port, f"{self.port}:{cmd}")
|
await self.mega.request(cmd=f"{self.port}:{cmd}")
|
||||||
self.mega.values[self.port] = {'value': cmd}
|
self.mega.values[self.port] = {'value': cmd}
|
||||||
await self.get_state()
|
await self.get_state()
|
||||||
|
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs) -> None:
|
async def async_turn_off(self, **kwargs) -> None:
|
||||||
|
|
||||||
cmd = "0" if not self.invert else "1"
|
cmd = "0" if not self.invert else "1"
|
||||||
|
|
||||||
await self.mega.send_command(self.port, f"{self.port}:{cmd}")
|
await self.mega.request(cmd=f"{self.port}:{cmd}")
|
||||||
self.mega.values[self.port] = {'value': cmd}
|
self.mega.values[self.port] = {'value': cmd}
|
||||||
await self.get_state()
|
await self.get_state()
|
||||||
|
|
||||||
|
|||||||
@@ -15,3 +15,7 @@ class DuplicateId(exceptions.HomeAssistantError):
|
|||||||
|
|
||||||
class InvalidAuth(exceptions.HomeAssistantError):
|
class InvalidAuth(exceptions.HomeAssistantError):
|
||||||
"""Error to indicate there is invalid auth."""
|
"""Error to indicate there is invalid auth."""
|
||||||
|
|
||||||
|
|
||||||
|
class NoPort(Exception):
|
||||||
|
pass
|
||||||
@@ -8,16 +8,15 @@ 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, DOMAIN, CONF_RESPONSE_TEMPLATE
|
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_RESPONSE_TEMPLATE
|
||||||
from .tools import make_ints
|
from .tools import make_ints
|
||||||
from . import hub as h
|
from . import hub as h
|
||||||
_LOGGER = logging.getLogger(__name__).getChild('http')
|
_LOGGER = logging.getLogger(__name__).getChild('http')
|
||||||
|
|
||||||
|
|
||||||
class MegaView(HomeAssistantView):
|
class MegaView(HomeAssistantView):
|
||||||
"""Handle Yandex Smart Home unauthorized requests."""
|
|
||||||
|
|
||||||
url = '/mega'
|
url = '/mega'
|
||||||
name = 'mega'
|
name = 'mega'
|
||||||
@@ -31,8 +30,8 @@ class MegaView(HomeAssistantView):
|
|||||||
mid: {
|
mid: {
|
||||||
pt: cfg[mid][pt][CONF_RESPONSE_TEMPLATE]
|
pt: cfg[mid][pt][CONF_RESPONSE_TEMPLATE]
|
||||||
for pt in cfg[mid]
|
for pt in cfg[mid]
|
||||||
if CONF_RESPONSE_TEMPLATE in cfg[mid][pt]
|
if isinstance(pt, int) and CONF_RESPONSE_TEMPLATE in cfg[mid][pt]
|
||||||
} for mid in cfg
|
} for mid in cfg if isinstance(cfg[mid], dict)
|
||||||
}
|
}
|
||||||
_LOGGER.debug('templates: %s', self.templates)
|
_LOGGER.debug('templates: %s', self.templates)
|
||||||
|
|
||||||
@@ -62,6 +61,10 @@ class MegaView(HomeAssistantView):
|
|||||||
make_ints(data)
|
make_ints(data)
|
||||||
port = data.get('pt')
|
port = data.get('pt')
|
||||||
data = data.copy()
|
data = data.copy()
|
||||||
|
update_all = True
|
||||||
|
if 'v' in data:
|
||||||
|
update_all = False
|
||||||
|
data['value'] = data.pop('v')
|
||||||
data['mega_id'] = hub.id
|
data['mega_id'] = hub.id
|
||||||
ret = 'd'
|
ret = 'd'
|
||||||
if port is not None:
|
if port is not None:
|
||||||
@@ -69,7 +72,7 @@ class MegaView(HomeAssistantView):
|
|||||||
for cb in self.callbacks[hub.id][port]:
|
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 and update_all:
|
||||||
asyncio.create_task(self.later_update(hub))
|
asyncio.create_task(self.later_update(hub))
|
||||||
if template is not None:
|
if template is not None:
|
||||||
template.hass = hass
|
template.hass = hass
|
||||||
|
|||||||
@@ -10,32 +10,49 @@ import json
|
|||||||
|
|
||||||
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.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.core import HomeAssistant
|
||||||
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, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_SKIP
|
from .const import (
|
||||||
from .entities import set_events_off
|
TEMP, HUM, PRESS,
|
||||||
from .exceptions import CannotConnect
|
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, NoPort
|
||||||
from .tools import make_ints
|
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\.]+)')
|
||||||
|
PRESS_PATT = re.compile(r'press:([01234567890\.]+)')
|
||||||
|
LUX_PATT = re.compile(r'lux:([01234567890\.]+)')
|
||||||
PATTERNS = {
|
PATTERNS = {
|
||||||
TEMP: TEMP_PATT,
|
TEMP: TEMP_PATT,
|
||||||
HUM: HUM_PATT,
|
HUM: HUM_PATT,
|
||||||
|
PRESS: PRESS_PATT,
|
||||||
|
LUX: LUX_PATT
|
||||||
}
|
}
|
||||||
UNITS = {
|
UNITS = {
|
||||||
TEMP: '°C',
|
TEMP: TEMP_CELSIUS,
|
||||||
HUM: '%'
|
HUM: PERCENTAGE,
|
||||||
|
PRESS: 'mmHg',
|
||||||
|
LUX: LIGHT_LUX
|
||||||
}
|
}
|
||||||
CLASSES = {
|
CLASSES = {
|
||||||
TEMP: DEVICE_CLASS_TEMPERATURE,
|
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:
|
class MegaD:
|
||||||
@@ -57,6 +74,7 @@ class MegaD:
|
|||||||
nports=38,
|
nports=38,
|
||||||
inverted: typing.List[int] = None,
|
inverted: typing.List[int] = None,
|
||||||
update_all=True,
|
update_all=True,
|
||||||
|
poll_outs=False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
@@ -66,6 +84,7 @@ class MegaD:
|
|||||||
self.http.allowed_hosts |= {host}
|
self.http.allowed_hosts |= {host}
|
||||||
else:
|
else:
|
||||||
self.http = None
|
self.http = None
|
||||||
|
self.poll_outs = poll_outs
|
||||||
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
|
||||||
self.nports = nports
|
self.nports = nports
|
||||||
self.mqtt_inputs = mqtt_inputs
|
self.mqtt_inputs = mqtt_inputs
|
||||||
@@ -81,7 +100,7 @@ class MegaD:
|
|||||||
self._notif_lck = asyncio.Lock()
|
self._notif_lck = asyncio.Lock()
|
||||||
self.cnd = asyncio.Condition()
|
self.cnd = asyncio.Condition()
|
||||||
self.online = True
|
self.online = True
|
||||||
self.entities: typing.List[Entity] = []
|
self.entities: typing.List[BaseMegaEntity] = []
|
||||||
self.poll_interval = scan_interval
|
self.poll_interval = scan_interval
|
||||||
self.subs = None
|
self.subs = None
|
||||||
self.lg: logging.Logger = lg.getChild(self.id)
|
self.lg: logging.Logger = lg.getChild(self.id)
|
||||||
@@ -147,6 +166,10 @@ class MegaD:
|
|||||||
self._customize = c
|
self._customize = c
|
||||||
return self._customize
|
return self._customize
|
||||||
|
|
||||||
|
@property
|
||||||
|
def force_d(self):
|
||||||
|
return self.customize.get(CONF_FORCE_D, False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_online(self):
|
def is_online(self):
|
||||||
return (datetime.now() - self.last_update).total_seconds() < (self.poll_interval + 10)
|
return (datetime.now() - self.last_update).total_seconds() < (self.poll_interval + 10)
|
||||||
@@ -170,15 +193,15 @@ class MegaD:
|
|||||||
|
|
||||||
async def poll(self):
|
async def poll(self):
|
||||||
"""
|
"""
|
||||||
Send get port 0 every poll_interval. When answer is received, mega.<id> becomes online else mega.<id> becomes
|
Polling ports
|
||||||
offline
|
|
||||||
"""
|
"""
|
||||||
self.lg.debug('poll')
|
self.lg.debug('poll')
|
||||||
if self.mqtt is None:
|
if self.mqtt is None:
|
||||||
await self.get_all_ports()
|
await self.get_all_ports()
|
||||||
await self.get_sensors(only_list=True)
|
await self.get_sensors(only_list=True)
|
||||||
return
|
elif self.poll_outs:
|
||||||
if len(self.sensors) > 0:
|
await self.get_all_ports(check_skip=True)
|
||||||
|
elif len(self.sensors) > 0:
|
||||||
await self.get_sensors()
|
await self.get_sensors()
|
||||||
else:
|
else:
|
||||||
await self.get_port(self.port_to_scan)
|
await self.get_port(self.port_to_scan)
|
||||||
@@ -268,20 +291,23 @@ 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}')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ports(self):
|
||||||
|
return {e.port for e in self.entities}
|
||||||
|
|
||||||
async def get_all_ports(self, only_out=False, check_skip=False):
|
async def get_all_ports(self, only_out=False, check_skip=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(';')):
|
||||||
if check_skip:
|
if check_skip and not port in self.ports:
|
||||||
if self.customize.get(port, {}).get(CONF_SKIP, False):
|
continue
|
||||||
continue
|
|
||||||
ret = self.parse_response(x)
|
ret = self.parse_response(x)
|
||||||
self.values[port] = ret
|
self.values[port] = ret
|
||||||
else:
|
elif not check_skip:
|
||||||
for x in range(self.nports + 1):
|
for x in range(self.nports + 1):
|
||||||
if check_skip:
|
await self.get_port(x)
|
||||||
if self.customize.get(x, {}).get(CONF_SKIP, False):
|
else:
|
||||||
continue
|
for x in self.ports:
|
||||||
await self.get_port(x)
|
await self.get_port(x)
|
||||||
|
|
||||||
async def reboot(self, save=True):
|
async def reboot(self, save=True):
|
||||||
@@ -392,8 +418,11 @@ class MegaD:
|
|||||||
self._scanned[port] = (pty, m)
|
self._scanned[port] = (pty, m)
|
||||||
return pty, m
|
return pty, m
|
||||||
elif pty in ('2', '4'): # эта часть не очень проработана, тут есть i2c который может работать неправильно
|
elif pty in ('2', '4'): # эта часть не очень проработана, тут есть i2c который может работать неправильно
|
||||||
self._scanned[port] = (pty, '0')
|
m = tree.find('select', attrs={'name': 'd'})
|
||||||
return pty, '0'
|
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):
|
async def scan_ports(self, nports=37):
|
||||||
for x in range(0, nports+1):
|
for x in range(0, nports+1):
|
||||||
@@ -428,7 +457,10 @@ class MegaD:
|
|||||||
if isinstance(values, str) and TEMP_PATT.search(values):
|
if isinstance(values, str) and TEMP_PATT.search(values):
|
||||||
values = {TEMP: values}
|
values = {TEMP: values}
|
||||||
elif not isinstance(values, dict):
|
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:
|
for key in values:
|
||||||
self.lg.debug(f'add sensor {key}')
|
self.lg.debug(f'add sensor {key}')
|
||||||
ret['sensor'][port].append(dict(
|
ret['sensor'][port].append(dict(
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
"@andvikt"
|
"@andvikt"
|
||||||
],
|
],
|
||||||
"issue_tracker": "https://github.com/andvikt/mega_hacs/issues",
|
"issue_tracker": "https://github.com/andvikt/mega_hacs/issues",
|
||||||
"version": "v0.3.13"
|
"version": "v0.3.19"
|
||||||
}
|
}
|
||||||
@@ -25,9 +25,10 @@ run_cmd:
|
|||||||
mega_id:
|
mega_id:
|
||||||
description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги
|
description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги
|
||||||
example: "mega"
|
example: "mega"
|
||||||
port:
|
|
||||||
description: Номер порта (это не порт, которым мы управляем, а порт с которого шлем команду)
|
|
||||||
example: 1
|
|
||||||
cmd:
|
cmd:
|
||||||
description: Любая поддерживаемая мегой команда
|
description: Любая поддерживаемая мегой команда
|
||||||
example: "1:0"
|
example: "1:0"
|
||||||
|
port:
|
||||||
|
description: (Deprecated, больше не нужен) Номер порта (это не порт, которым мы управляем, а порт с которого шлем команду)
|
||||||
|
example: 1
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"invert": "[%key:common::config_flow::data::invert%]",
|
"invert": "[%key:common::config_flow::data::invert%]",
|
||||||
"mqtt_inputs": "[%key:common::config_flow::data::mqtt_inputs%]",
|
"mqtt_inputs": "[%key:common::config_flow::data::mqtt_inputs%]",
|
||||||
"nports": "[%key:common::config_flow::data::nports%]",
|
"nports": "[%key:common::config_flow::data::nports%]",
|
||||||
"update_all": "[%key:common::config_flow::data::update_all%]"
|
"update_all": "[%key:common::config_flow::data::update_all%]",
|
||||||
|
"poll_outs": "[%key:common::config_flow::data::poll_outs%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -38,7 +39,8 @@
|
|||||||
"invert": "[%key:common::config_flow::data::invert%]",
|
"invert": "[%key:common::config_flow::data::invert%]",
|
||||||
"mqtt_inputs": "[%key:common::config_flow::data::mqtt_inputs%]",
|
"mqtt_inputs": "[%key:common::config_flow::data::mqtt_inputs%]",
|
||||||
"nports": "[%key:common::config_flow::data::nports%]",
|
"nports": "[%key:common::config_flow::data::nports%]",
|
||||||
"update_all": "[%key:common::config_flow::data::update_all%]"
|
"update_all": "[%key:common::config_flow::data::update_all%]",
|
||||||
|
"poll_outs": "[%key:common::config_flow::data::poll_outs%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
"port_to_scan": "Port to poll aliveness (needed only if no sensors used)",
|
"port_to_scan": "Port to poll aliveness (needed only if no sensors used)",
|
||||||
"nports": "Number of ports",
|
"nports": "Number of ports",
|
||||||
"update_all": "Update all outs when input",
|
"update_all": "Update all outs when input",
|
||||||
"mqtt_inputs": "Use MQTT"
|
"mqtt_inputs": "Use MQTT",
|
||||||
|
"poll_outs": "Poll outs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +36,8 @@
|
|||||||
"port_to_scan": "Port to poll aliveness (needed only if no sensors used)",
|
"port_to_scan": "Port to poll aliveness (needed only if no sensors used)",
|
||||||
"reload": "Reload objects",
|
"reload": "Reload objects",
|
||||||
"mqtt_inputs": "Use MQTT",
|
"mqtt_inputs": "Use MQTT",
|
||||||
"update_all": "Update all outs when input"
|
"update_all": "Update all outs when input",
|
||||||
|
"poll_outs": "Poll outs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
"port_to_scan": "Порт, который сканируется когда нет датчиков",
|
"port_to_scan": "Порт, который сканируется когда нет датчиков",
|
||||||
"mqtt_inputs": "Использовать MQTT",
|
"mqtt_inputs": "Использовать MQTT",
|
||||||
"nports": "Кол-во портов",
|
"nports": "Кол-во портов",
|
||||||
"update_all": "Обновить все выходы когда срабатывает вход"
|
"update_all": "Обновить все выходы когда срабатывает вход",
|
||||||
|
"poll_outs": "Обновлять выходы (регулярно)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
"invert": "Список портов (через ,) с инвертированной логикой",
|
"invert": "Список портов (через ,) с инвертированной логикой",
|
||||||
"mqtt_inputs": "Использовать MQTT",
|
"mqtt_inputs": "Использовать MQTT",
|
||||||
"nports": "Кол-во портов",
|
"nports": "Кол-во портов",
|
||||||
"update_all": "Обновить все выходы когда срабатывает вход"
|
"update_all": "Обновить все выходы когда срабатывает вход",
|
||||||
|
"poll_outs": "Обновлять выходы (регулярно)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
"port_to_scan": "Порт для сканування при відсутності датчиків",
|
"port_to_scan": "Порт для сканування при відсутності датчиків",
|
||||||
"mqtt_inputs": "Використовувати MQTT",
|
"mqtt_inputs": "Використовувати MQTT",
|
||||||
"nports": "Кількість портів",
|
"nports": "Кількість портів",
|
||||||
"update_all": "Оновити всі виходи коли спрацьовує вхід"
|
"update_all": "Оновити всі виходи коли спрацьовує вхід",
|
||||||
|
"poll_outs": "Оновити виходи"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
"invert": "Список портів з інвертованою логікою (через ,)",
|
"invert": "Список портів з інвертованою логікою (через ,)",
|
||||||
"mqtt_inputs": "Використовувати MQTT",
|
"mqtt_inputs": "Використовувати MQTT",
|
||||||
"nports": "Кількість портів",
|
"nports": "Кількість портів",
|
||||||
"update_all": "Оновити всі виходи коли спрацьовує вхід"
|
"update_all": "Оновити всі виходи коли спрацьовує вхід",
|
||||||
|
"poll_outs": "Оновити виходи"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
readme.md
29
readme.md
@@ -4,7 +4,7 @@
|
|||||||
[](https://github.com/custom-components/hacs)
|
[](https://github.com/custom-components/hacs)
|
||||||
[](https://yoomoney.ru/to/410013955329136)
|
[](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)
|
||||||
|
|
||||||
## Основные особенности:
|
## Основные особенности:
|
||||||
- Настройка в веб-интерфейсе + yaml
|
- Настройка в веб-интерфейсе + yaml
|
||||||
@@ -73,6 +73,9 @@ mega:
|
|||||||
Начиная с версии `0.3.1` интеграция стала поддерживать обратную связь без mqtt, используя http-сервер. Для этого в настройках
|
Начиная с версии `0.3.1` интеграция стала поддерживать обратную связь без mqtt, используя http-сервер. Для этого в настройках
|
||||||
интеграции необходимо снять галку с `использовать mqtt`
|
интеграции необходимо снять галку с `использовать mqtt`
|
||||||
|
|
||||||
|
**Внимание!** Не используйте srv loop на контроллере, это может приводить к ложным срабатываниям входов. Вместо srv loop
|
||||||
|
интеграция будет сама обновлять все состояния портов с заданным интервалом
|
||||||
|
|
||||||
В самой меге необходимо прописать настройки:
|
В самой меге необходимо прописать настройки:
|
||||||
```yaml
|
```yaml
|
||||||
srv: "192.168.1.4:8123" # ip:port вашего HA
|
srv: "192.168.1.4:8123" # ip:port вашего HA
|
||||||
@@ -107,9 +110,26 @@ mega:
|
|||||||
# эти параметры можно использовать в условиях или непосредственно в шаблоне в виде {{pt}}
|
# эти параметры можно использовать в условиях или непосредственно в шаблоне в виде {{pt}}
|
||||||
response_template: >-
|
response_template: >-
|
||||||
{% if m==2 %}1:0{% else %}d{% endif %}
|
{% if m==2 %}1:0{% else %}d{% endif %}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Начиная с версии v0.3.17 ответ можно слать так же и в режиме MQTT. Аналогично, темплейт должен возвращать готовую команду
|
||||||
|
такую же как требует команда cmd, так же можно использовать d, но d не отправляется по умолчанию, это сделано чтобы не
|
||||||
|
сломать текущую логику у пользователей предыдущих версий. Чтобы включить для всех входов в режиме mqtt отправку команды
|
||||||
|
d необходимо в конфиге прописать следующее:
|
||||||
|
```yaml
|
||||||
|
mega:
|
||||||
|
mega1:
|
||||||
|
force_d: true
|
||||||
|
```
|
||||||
|
**Внимание!** Нельзя использовать чекбокс напротив поля act если планируется использовать ответ сервера - у вас и
|
||||||
|
сработает act и команда от сервера, а вслучае ответа d сработает act два раза.
|
||||||
|
|
||||||
|
Так же следует понимать, что это не "ответ" в нормальном понимании - это вызов следом за полученным mqtt-сообщением
|
||||||
|
http команды такого вида `http://megaurl/?pt=port&cmd=rendered_template`, где `port` - это номер порта сработавшего входа,
|
||||||
|
а `cmd` - текст команды, который получен из темплейта. Те это имитация ответа. У этого подхода есть минус - задержка в
|
||||||
|
исполнении будет значительно выше чем при ответе в режиме http, но тем не менее эта задержка скорее всего не будет
|
||||||
|
сильно заметна.
|
||||||
|
|
||||||
## binary_sensor и события
|
## binary_sensor и события
|
||||||
|
|
||||||
Входы будут доступны как binary_sensor, а так же в виде событий `mega.sensor` и `mega.binary`.
|
Входы будут доступны как binary_sensor, а так же в виде событий `mega.sensor` и `mega.binary`.
|
||||||
@@ -155,12 +175,15 @@ mega:
|
|||||||
```
|
```
|
||||||
Возможные варианты поля `type`:
|
Возможные варианты поля `type`:
|
||||||
- `long`: долгое нажатие
|
- `long`: долгое нажатие
|
||||||
- `release`: размыкание (с гарантией что не было долгого нажатия)
|
- `release`: размыкание (с гарантией** что не было долгого нажатия)
|
||||||
- `long_release`: размыкание после долгого нажатия
|
- `long_release`: размыкание после долгого нажатия
|
||||||
- `press`: замыкание
|
- `press`: замыкание
|
||||||
- `single`: одинарный клик (в режиме кликов)
|
- `single`: одинарный клик (в режиме кликов)
|
||||||
- `double`: двойной клик
|
- `double`: двойной клик
|
||||||
|
|
||||||
|
**гарантия есть только при использовании http-метода синхронизации, mqtt не гарантирует порядок доставки сообщений, хотя
|
||||||
|
маловероятно, что порядок будет нарушен, но все же сам протокол не дает таких гарантий.
|
||||||
|
|
||||||
Чтобы понять, какие события происходят, лучше всего воспользоваться панелью разработчика и подписаться
|
Чтобы понять, какие события происходят, лучше всего воспользоваться панелью разработчика и подписаться
|
||||||
на вкладке события на событие `mega.sensor`, понажимать кнопки.
|
на вкладке события на событие `mega.sensor`, понажимать кнопки.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user