Compare commits

..

19 Commits

Author SHA1 Message Date
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
Andrey
59443989a0 add poll outs 2021-02-06 09:43:10 +03:00
Andrey
5b86ceefe4 Merge remote-tracking branch 'origin/master' 2021-02-05 21:06:40 +03:00
Andrey
8cf000beae fix get_port 2021-02-05 21:06:27 +03:00
andvikt
99317da9f6 Update bug-report.md 2021-02-05 20:47:58 +03:00
Andrey
97911d0241 fix get_port 2021-02-05 20:21:14 +03:00
Andrey
d9925a2de0 i2c sensors 2021-02-05 20:08:28 +03:00
Andrey
f197a09072 invert inputs 2021-01-31 17:38:44 +03:00
Andrey
e8d92cfa36 invert inputs 2021-01-31 17:36:56 +03:00
Andrey
5f94186a14 edit readme 2021-01-31 12:10:17 +03:00
15 changed files with 207 additions and 124 deletions

View File

@@ -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
```

View File

@@ -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,6 +28,7 @@ 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(int, description='номер порта'): { vol.Optional(int, description='номер порта'): {
vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool, vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool,
@@ -44,7 +45,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 +66,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(
@@ -75,12 +77,12 @@ async def async_setup(hass: HomeAssistant, config: dict):
hass.services.async_register( hass.services.async_register(
DOMAIN, 'get_port', partial(_get_port, hass), schema=vol.Schema({ DOMAIN, 'get_port', partial(_get_port, hass), schema=vol.Schema({
vol.Optional('mega_id'): str, vol.Optional('mega_id'): str,
vol.Optional('port'): int, vol.Optional('port'): vol.Any(int, [int]),
}) })
) )
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 +117,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 +167,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()
@@ -208,27 +212,32 @@ async def _get_port(hass: HomeAssistant, call: ServiceCall):
if mega_id: if mega_id:
hub: MegaD = hass.data[DOMAIN][mega_id] hub: MegaD = hass.data[DOMAIN][mega_id]
if port is None: if port is None:
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)
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)

View File

@@ -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 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
@@ -71,6 +72,10 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
def state_attributes(self): def state_attributes(self):
return self._attrs return self._attrs
@property
def invert(self):
return self.customize.get(CONF_INVERT, False)
@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, {}).get("value") \
@@ -79,10 +84,26 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
return self._state == 'ON' return self._state == 'ON'
elif val is not None: elif val is not None:
if val in ['ON', 'OFF']: if val in ['ON', 'OFF']:
return val == 'ON' return val == 'ON' if not self.invert else val == 'OFF'
else: else:
return val != 1 return val != 1 if not self.invert else val == 1
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')
)

View File

@@ -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,

View File

@@ -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'

View File

@@ -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()

View File

@@ -32,7 +32,7 @@ class MegaView(HomeAssistantView):
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 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)

View File

@@ -10,30 +10,45 @@ 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.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 from .const import TEMP, HUM, PRESS, LUX, PATT_SPLIT, DOMAIN, CONF_HTTP, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_SKIP, \
from .entities import set_events_off CONF_FORCE_D
from .entities import set_events_off, BaseMegaEntity
from .exceptions import CannotConnect from .exceptions import CannotConnect
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): class NoPort(Exception):
pass pass
@@ -57,6 +72,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 +82,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 +98,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)
@@ -91,6 +108,7 @@ class MegaD:
self.last_update = datetime.now() self.last_update = datetime.now()
self._callbacks: typing.DefaultDict[int, typing.List[typing.Callable[[dict], typing.Coroutine]]] = defaultdict(list) self._callbacks: typing.DefaultDict[int, typing.List[typing.Callable[[dict], typing.Coroutine]]] = defaultdict(list)
self._loop = loop self._loop = loop
self._customize = None
self.values = {} self.values = {}
self.last_port = None self.last_port = None
self.updater = DataUpdateCoordinator( self.updater = DataUpdateCoordinator(
@@ -138,6 +156,18 @@ class MegaD:
await self.get_port(x.port, force_http=True, http_cmd=x.http_cmd) await self.get_port(x.port, force_http=True, http_cmd=x.http_cmd)
ports.append(x.port) ports.append(x.port)
@property
def customize(self):
if self._customize is None:
c = self.hass.data.get(DOMAIN, {}).get(CONF_CUSTOM) or {}
c = c.get(self.id) or {}
self._customize = c
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)
@@ -168,8 +198,9 @@ class MegaD:
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)
@@ -259,15 +290,24 @@ 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, only_out=False): @property
def ports(self):
return {e.port for e in self.entities}
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 and not port in self.ports:
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):
await self.get_port(x) await self.get_port(x)
else:
for x in self.ports:
await self.get_port(x)
async def reboot(self, save=True): async def reboot(self, save=True):
await self.save() await self.save()
@@ -376,9 +416,12 @@ class MegaD:
m = m.find(selected=True)['value'] m = m.find(selected=True)['value']
self._scanned[port] = (pty, m) self._scanned[port] = (pty, m)
return pty, m return pty, m
elif pty == '2': 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):
@@ -394,7 +437,7 @@ class MegaD:
ret['binary_sensor'][port].append({}) ret['binary_sensor'][port].append({})
elif pty == "1" and (m in ['0', '1', '3'] or m is None): 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 in ('3', '2'): elif pty in ('3', '2', '4'):
try: try:
http_cmd = 'get' http_cmd = 'get'
values = await self.get_port(port, force_http=True) values = await self.get_port(port, force_http=True)
@@ -413,7 +456,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(

View File

@@ -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.11" "version": "v0.3.17"
} }

View File

@@ -15,7 +15,7 @@ get_port:
description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги
example: "mega" example: "mega"
port: port:
description: Номер порта (если не заполнять, будут запрошены все порты сразу) description: Номер порта или список портов (если не заполнять, будут запрошены все порты сразу)
example: 1 example: 1
run_cmd: run_cmd:
@@ -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

View File

@@ -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%]"
} }
} }
} }

View File

@@ -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"
} }
} }
} }

View File

@@ -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": "Обновлять выходы (регулярно)"
} }
} }
} }

View File

@@ -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": "Оновити виходи"
} }
} }
} }

126
readme.md
View File

@@ -73,56 +73,20 @@ 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
script: "mega" # это api интеграции, к которому будет обращаться контроллер script: "mega" # это api интеграции, к которому будет обращаться контроллер
``` ```
Входы будут доступны как binary_sensor, а так же в виде событий `mega.sensor`. #### Ответ на входящие события от контроллера
События можно обрабатывать так:
```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
```
Для 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, mdid, mega_id) умеет). В шаблоне можно использовать параметры, которые передает контроллер (m, click, pt, mdid, mega_id)
Примеры: Примеры:
@@ -130,7 +94,7 @@ script: "mega" # это api интеграции, к которому будет
mega: mega:
mega1: # id меги, который вы сами придумываете в конфиге в UI mega1: # id меги, который вы сами придумываете в конфиге в UI
4: # номер порта, с которого ожидаются события 4: # номер порта, с которого ожидаются события
response_template: 5:2 # простейший пример без шаблона. Каждый раз когда будет приходить сообщение на этот порт, response_template: "5:2" # простейший пример без шаблона. Каждый раз когда будет приходить сообщение на этот порт,
# будем менять состояние на противоположное # будем менять состояние на противоположное
5: 5:
# пример с использованием шаблона, порт 1 будет выключен если он сейчас включен и включен с последней сохраненной # пример с использованием шаблона, порт 1 будет выключен если он сейчас включен и включен с последней сохраненной
@@ -148,30 +112,31 @@ mega:
{% if m==2 %}1:0{% else %}d{% endif %} {% if m==2 %}1:0{% else %}d{% endif %}
``` ```
## Отладка ответов
Для отладки ответов сервера можно самим имитировать запросы контроллера, если у вас есть доступ к консоли Начиная с версии v0.3.17 ответ можно слать так же и в режиме MQTT. Аналогично, темплейт должен возвращать готовую команду
HA: такую же как требует команда cmd, так же можно использовать d, но d не отправляется по умолчанию, это сделано чтобы не
```shell сломать текущую логику у пользователей предыдущих версий. Чтобы включить для всех входов в режиме mqtt отправку команды
curl -v -X GET 'http://localhost:8123/mega?pt=5&m=1' d необходимо в конфиге прописать следующее:
```
Если доступа нет, нужно в файл конфигурации добавить ip компьюетра, с которого вы хотите делать запросы, например:
```yaml ```yaml
mega: mega:
allow_hosts: mega1:
- 192.168.1.1 force_d: true
``` ```
И тогда можно с локальной машины делать запросы на ваш сервер HA: **Внимание!** Нельзя использовать чекбокс напротив поля act если планируется использовать ответ сервера - у вас и
```shell сработает act и команда от сервера, а вслучае ответа d сработает act два раза.
curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1'
```
В ответ будет приходить либо `d`, либо скрипт, который вы настроили
Так же следует понимать, что это не "ответ" в нормальном понимании - это вызов следом за полученным mqtt-сообщением
http команды такого вида `http://megaurl/?pt=port&cmd=rendered_template`, где `port` - это номер порта сработавшего входа,
а `cmd` - текст команды, который получен из темплейта. Те это имитация ответа. У этого подхода есть минус - задержка в
исполнении будет значительно выше чем при ответе в режиме http, но тем не менее эта задержка скорее всего не будет
сильно заметна.
## События ## binary_sensor и события
`binary_sensor` срабатывает когда цифровой выход принимает значение 'ON'. `binary_sensor` имеет смысл использовать
только с режимом входа P&R Входы будут доступны как binary_sensor, а так же в виде событий `mega.sensor` и `mega.binary`.
Для корректной работы binary_sensor имеет смысл использовать режим P&R, для остальных режимов - лучше пользоваться
событиями.
При каждом срабатывании `binary_sensor` так же сообщает о событии типа `mega.sensor`.
События можно использовать в автоматизациях, например так: События можно использовать в автоматизациях, например так:
```yaml ```yaml
# Пример события с полями как есть прямо из меги # Пример события с полями как есть прямо из меги
@@ -181,21 +146,21 @@ curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1'
event_type: mega.sensor event_type: mega.sensor
event_data: event_data:
pt: 1 pt: 1
cnt: 2 click: 2
action: action:
- service: light.toggle - service: light.toggle
entity_id: light.some_light entity_id: light.some_light
``` ```
События могут содержать следующие поля: События могут содержать следующие поля:
- mega_id: id как в конфиге HA - `mega_id`: id как в конфиге HA
- pt: номер порта - `pt`: номер порта
- cnt: счетчик срабатываний - `cnt`: счетчик срабатываний
- mdid: if как в конфиге контроллера - `mdid`: if как в конфиге контроллера
- click: клик (подробнее в документации меги) - `click`: клик (подробнее в документации меги)
- value: текущее значение (только для mqtt) - `value`: текущее значение (только для mqtt)
- port: номер порта - `port`: номер порта
Начиная с версии 0.3.7 появилось так же событие типа mega.binary: Начиная с версии 0.3.7 появилось так же событие типа `mega.binary`:
```yaml ```yaml
# Пример события с полями как есть прямо из меги # Пример события с полями как есть прямо из меги
- alias: some long click - alias: some long click
@@ -211,12 +176,15 @@ curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1'
``` ```
Возможные варианты поля `type`: Возможные варианты поля `type`:
- `long`: долгое нажатие - `long`: долгое нажатие
- `release`: размыкание (с гарантией что не было долгого нажатия) - `release`: размыкание (с гарантией** что не было долгого нажатия)
- `long_release`: размыкание после долгого нажатия - `long_release`: размыкание после долгого нажатия
- `press`: замыкание - `press`: замыкание
- `single`: одинарный клик (в режиме кликов) - `single`: одинарный клик (в режиме кликов)
- `double`: двойной клик - `double`: двойной клик
**гарантия есть только при использовании http-метода синхронизации, mqtt не гарантирует порядок доставки сообщений, хотя
маловероятно, что порядок будет нарушен, но все же сам протокол не дает таких гарантий.
Чтобы понять, какие события происходят, лучше всего воспользоваться панелью разработчика и подписаться Чтобы понять, какие события происходят, лучше всего воспользоваться панелью разработчика и подписаться
на вкладке события на событие `mega.sensor`, понажимать кнопки. на вкладке события на событие `mega.sensor`, понажимать кнопки.
@@ -264,3 +232,21 @@ logger:
logs: logs:
custom_components.mega: debug 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`, либо скрипт, который вы настроили