Compare commits

..

17 Commits

Author SHA1 Message Date
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
Andrey
f09610355b add version to manifest 2021-01-31 11:46:43 +03:00
Andrey
70e182fec3 Merge remote-tracking branch 'origin/master' 2021-01-31 11:42:11 +03:00
Andrey
ef3152a086 add adc port and template rendering values 2021-01-31 11:41:27 +03:00
andvikt
9b9443864c Update readme.md 2021-01-29 17:57:33 +03:00
andvikt
b7669ac407 Update readme.md 2021-01-29 17:55:45 +03:00
andvikt
2b308a71a1 Update readme.md 2021-01-29 17:55:12 +03:00
Andrey
8f67652c0e Merge remote-tracking branch 'origin/master' 2021-01-29 09:53:49 +03:00
Andrey
f83cdaa583 event monitoring propper restarting 2021-01-29 09:53:10 +03:00
andvikt
8dfa5926ad Merge pull request #4 from Foroxon/master
Ukrainian translation updating
2021-01-29 09:09:40 +03:00
Andrey
3978ce2203 fix 1-wire updating while busy 2021-01-29 09:08:34 +03:00
Vladyslav Heneraliuk
2e025eb0c2 updated Ukrainian translation 2021-01-26 12:16:42 +02:00
Vladyslav Heneraliuk
20d5b8ff40 Merge pull request #1 from andvikt/master
Sync
2021-01-26 12:10:46 +02:00
10 changed files with 113 additions and 96 deletions

View File

@@ -16,7 +16,8 @@ from homeassistant.helpers import config_validation as cv
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_MQTT_INPUTS, CONF_HTTP, CONF_RESPONSE_TEMPLATE, CONF_ACTION, CONF_GET_VALUE, CONF_ALLOW_HOSTS, \
CONF_CONV_TEMPLATE
from .hub import MegaD
from .config_flow import ConfigFlow
from .http import MegaView
@@ -45,6 +46,7 @@ CONFIG_SCHEMA = vol.Schema(
'сообщение из меги '): cv.template,
vol.Optional(CONF_ACTION): cv.script_action,
vol.Optional(CONF_GET_VALUE, default=True): bool,
vol.Optional(CONF_CONV_TEMPLATE): cv.template
}
}
}
@@ -73,7 +75,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
hass.services.async_register(
DOMAIN, 'get_port', partial(_get_port, hass), schema=vol.Schema({
vol.Optional('mega_id'): str,
vol.Optional('port'): int,
vol.Optional('port'): vol.Any(int, [int]),
})
)
hass.services.async_register(
@@ -161,6 +163,7 @@ async def async_remove_entry(hass, entry) -> None:
return
_LOGGER.debug(f'remove {id}')
_hubs.pop(id, None)
hass.data[DOMAIN].pop(id, None)
task: asyncio.Task = _POLL_TASKS.pop(id, None)
if task is not None:
task.cancel()
@@ -205,9 +208,12 @@ async def _get_port(hass: HomeAssistant, call: ServiceCall):
if mega_id:
hub: MegaD = hass.data[DOMAIN][mega_id]
if port is None:
await hub.get_all_ports()
else:
await hub.get_all_ports(check_skip=True)
elif isinstance(port, int):
await hub.get_port(port)
elif isinstance(port, list):
for x in port:
await hub.get_port(x)
else:
for hub in hass.data[DOMAIN].values():
if not isinstance(hub, MegaD):

View File

@@ -16,7 +16,7 @@ from homeassistant.const import (
CONF_ENTITY_ID,
)
from homeassistant.core import HomeAssistant
from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_CUSTOM, CONF_SKIP
from .const import EVENT_BINARY_SENSOR, DOMAIN, CONF_CUSTOM, CONF_SKIP, CONF_INVERT
from .entities import MegaPushEntity
from .hub import MegaD
@@ -71,6 +71,10 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
def state_attributes(self):
return self._attrs
@property
def invert(self):
return self.customize.get(CONF_INVERT, False)
@property
def is_on(self) -> bool:
val = self.mega.values.get(self.port, {}).get("value") \
@@ -79,9 +83,9 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
return self._state == 'ON'
elif val is not None:
if val in ['ON', 'OFF']:
return val == 'ON'
return val == 'ON' if not self.invert else val == 'OFF'
else:
return val != 1
return val != 1 if not self.invert else val == 1
def _update(self, payload: dict):
self.mega.values[self.port] = payload

View File

@@ -24,6 +24,7 @@ CONF_ACTION = 'action'
CONF_UPDATE_ALL = 'update_all'
CONF_GET_VALUE = 'get_value'
CONF_ALLOW_HOSTS = 'allow_hosts'
CONF_CONV_TEMPLATE = 'conv_template'
PLATFORMS = [
"light",
"switch",

View File

@@ -13,16 +13,18 @@ from .const import DOMAIN, CONF_CUSTOM, CONF_INVERT, EVENT_BINARY_SENSOR, LONG,
_events_on = False
_LOGGER = logging.getLogger(__name__)
async def _set_events_on():
global _events_on
global _events_on, _task_set_ev_on
await asyncio.sleep(10)
_LOGGER.debug('events on')
_events_on = True
def set_events_off():
global _events_on
global _events_on, _task_set_ev_on
_events_on = False
_task_set_ev_on = None
_task_set_ev_on = None

View File

@@ -14,7 +14,7 @@ from homeassistant.const import DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY
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
from .const import TEMP, HUM, PATT_SPLIT, DOMAIN, CONF_HTTP, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_SKIP
from .entities import set_events_off
from .exceptions import CannotConnect
from .tools import make_ints
@@ -91,6 +91,7 @@ class MegaD:
self.last_update = datetime.now()
self._callbacks: typing.DefaultDict[int, typing.List[typing.Callable[[dict], typing.Coroutine]]] = defaultdict(list)
self._loop = loop
self._customize = None
self.values = {}
self.last_port = None
self.updater = DataUpdateCoordinator(
@@ -138,6 +139,14 @@ class MegaD:
await self.get_port(x.port, force_http=True, http_cmd=x.http_cmd)
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 is_online(self):
return (datetime.now() - self.last_update).total_seconds() < (self.poll_interval + 10)
@@ -209,6 +218,8 @@ class MegaD:
def parse_response(self, ret):
if ret is None:
raise NoPort()
if 'busy' in ret:
return None
if ':' in ret:
ret = PATT_SPLIT.split(ret)
ret = {'value': dict([
@@ -229,13 +240,16 @@ class MegaD:
"""
self.lg.debug(f'get port %s', port)
if self.mqtt is None or force_http:
ret = await self.request(pt=port, cmd=http_cmd)
ret = self.parse_response(ret)
self.lg.debug('parsed: %s', ret)
if http_cmd == 'list' and isinstance(ret, dict) and 'value' in ret:
if http_cmd == 'list':
await self.request(pt=port, cmd='conv')
await asyncio.sleep(1)
ret = await self.request(pt=port, cmd=http_cmd)
ret = self.parse_response(ret)
ret = self.parse_response(await self.request(pt=port, cmd=http_cmd))
ntry = 0
while http_cmd == 'list' and ret is None and ntry < 3:
await asyncio.sleep(1)
ret = self.parse_response(await self.request(pt=port, cmd=http_cmd))
ntry += 1
self.lg.debug('parsed: %s', ret)
self.values[port] = ret
return ret
@@ -254,14 +268,20 @@ class MegaD:
except asyncio.TimeoutError:
self.lg.error(f'timeout when getting port {port}')
async def get_all_ports(self, only_out=False):
async def get_all_ports(self, only_out=False, check_skip=False):
if not self.mqtt_inputs:
ret = await self.request(cmd='all')
for port, x in enumerate(ret.split(';')):
if check_skip:
if self.customize.get(port, {}).get(CONF_SKIP, False):
continue
ret = self.parse_response(x)
self.values[port] = ret
else:
for x in range(self.nports + 1):
if check_skip:
if self.customize.get(x, {}).get(CONF_SKIP, False):
continue
await self.get_port(x)
async def reboot(self, save=True):
@@ -371,6 +391,9 @@ class MegaD:
m = m.find(selected=True)['value']
self._scanned[port] = (pty, m)
return pty, m
elif pty in ('2', '4'): # эта часть не очень проработана, тут есть i2c который может работать неправильно
self._scanned[port] = (pty, '0')
return pty, '0'
async def scan_ports(self, nports=37):
for x in range(0, nports+1):
@@ -386,7 +409,7 @@ 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 == '3':
elif pty in ('3', '2', '4'):
try:
http_cmd = 'get'
values = await self.get_port(port, force_http=True)

View File

@@ -14,5 +14,6 @@
"codeowners": [
"@andvikt"
],
"issue_tracker": "https://github.com/andvikt/mega_hacs/issues"
"issue_tracker": "https://github.com/andvikt/mega_hacs/issues",
"version": "v0.3.13"
}

View File

@@ -16,8 +16,9 @@ from homeassistant.const import (
CONF_TYPE, CONF_UNIT_OF_MEASUREMENT,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.template import Template
from .entities import MegaPushEntity
from .const import CONF_KEY, TEMP, HUM, W1, W1BUS
from .const import CONF_KEY, TEMP, HUM, W1, W1BUS, CONF_CONV_TEMPLATE
from .hub import MegaD
import re
@@ -164,6 +165,10 @@ class Mega1WSensor(MegaPushEntity):
ret = str(ret)
except:
ret = None
tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE)
if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass
ret = tmpl.async_render({'value': ret})
return ret
@property

View File

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

View File

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

113
readme.md
View File

@@ -1,4 +1,8 @@
# MegaD HomeAssistant integration
[![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)
@@ -53,6 +57,9 @@ mega:
unit_of_measurement:
hum: "%" # если датчиков несколько, то можно указывать юниты по их ключам
temp: "°C"
# можно так же указать шаблон для конвертации значения, может быть полезно для ацп-входа
# текущее значение порта передается в шаблон в переменной "value"
conv_template: "{{(value|float)/100}}"
14:
name: какой-то датчик
unit_of_measurement: "°C" # если датчик один, то просто строчкой
@@ -72,50 +79,11 @@ srv: "192.168.1.4:8123" # ip:port вашего HA
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`, что означает
запустить то что прописано в поле act в настройках порта).
Поддерживаеются шаблоны HA. Это может быть использовано, например, для запоминания яркости (тк сам контроллер этого не
Поддерживаются шаблоны HA. Это может быть использовано, например, для запоминания яркости (тк сам контроллер этого не
умеет). В шаблоне можно использовать параметры, которые передает контроллер (m, click, pt, mdid, mega_id)
Примеры:
@@ -123,7 +91,7 @@ script: "mega" # это api интеграции, к которому будет
mega:
mega1: # id меги, который вы сами придумываете в конфиге в UI
4: # номер порта, с которого ожидаются события
response_template: 5:2 # простейший пример без шаблона. Каждый раз когда будет приходить сообщение на этот порт,
response_template: "5:2" # простейший пример без шаблона. Каждый раз когда будет приходить сообщение на этот порт,
# будем менять состояние на противоположное
5:
# пример с использованием шаблона, порт 1 будет выключен если он сейчас включен и включен с последней сохраненной
@@ -141,30 +109,13 @@ mega:
{% if m==2 %}1:0{% else %}d{% endif %}
```
## Отладка ответов
Для отладки ответов сервера можно самим имитировать запросы контроллера, если у вас есть доступ к консоли
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`, либо скрипт, который вы настроили
## binary_sensor и события
## События
`binary_sensor` срабатывает когда цифровой выход принимает значение 'ON'. `binary_sensor` имеет смысл использовать
только с режимом входа P&R
Входы будут доступны как binary_sensor, а так же в виде событий `mega.sensor` и `mega.binary`.
Для корректной работы binary_sensor имеет смысл использовать режим P&R, для остальных режимов - лучше пользоваться
событиями.
При каждом срабатывании `binary_sensor` так же сообщает о событии типа `mega.sensor`.
События можно использовать в автоматизациях, например так:
```yaml
# Пример события с полями как есть прямо из меги
@@ -174,21 +125,21 @@ curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1'
event_type: mega.sensor
event_data:
pt: 1
cnt: 2
click: 2
action:
- service: light.toggle
entity_id: light.some_light
```
События могут содержать следующие поля:
- mega_id: id как в конфиге HA
- pt: номер порта
- cnt: счетчик срабатываний
- mdid: if как в конфиге контроллера
- click: клик (подробнее в документации меги)
- value: текущее значение (только для mqtt)
- port: номер порта
- `mega_id`: id как в конфиге HA
- `pt`: номер порта
- `cnt`: счетчик срабатываний
- `mdid`: if как в конфиге контроллера
- `click`: клик (подробнее в документации меги)
- `value`: текущее значение (только для mqtt)
- `port`: номер порта
Начиная с версии 0.3.7 появилось так же событие типа mega.binary:
Начиная с версии 0.3.7 появилось так же событие типа `mega.binary`:
```yaml
# Пример события с полями как есть прямо из меги
- alias: some long click
@@ -257,3 +208,21 @@ logger:
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`, либо скрипт, который вы настроили