Compare commits

..

10 Commits

Author SHA1 Message Date
andvikt
21697d83ae Merge pull request #159 from andvikt/dev
Dev
2023-10-11 11:28:06 +03:00
andvikt
5bb2425918 Merge pull request #158 from andvikt/dev
Dev
2023-10-10 23:03:24 +03:00
andvikt
58a4680311 Merge pull request #157 from andvikt/dev
Dev
2023-10-10 21:05:03 +03:00
andvikt
03462edf83 Merge pull request #156 from andvikt/dev
Dev
2023-10-09 14:59:50 +03:00
andvikt
8847290457 Merge pull request #124 from andvikt/dev
Dev
2022-09-08 13:28:01 +03:00
andvikt
36dfad1697 Merge pull request #113 from andvikt/dev
Dev
2022-07-22 10:17:28 +03:00
andvikt
dee0d6d1a7 Merge pull request #109 from wrt54g/hacs-url
Update HACS URL
2022-07-22 10:14:57 +03:00
Sven Serlier
da7305c07b Update HACS URL 2022-06-02 06:49:56 +02:00
andvikt
d3f3eeedc5 Update index.md 2022-02-11 21:43:58 +03:00
andvikt
16ad1ea4d2 Merge pull request #97 from andvikt/dev
Dev
2021-12-15 16:58:42 +03:00
8 changed files with 183 additions and 270 deletions

View File

@@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 1.1.8b6 current_version = 1.1.8b2
parse = (?P<major>\d+)(\.(?P<minor>\d+))(\.(?P<patch>\d+))(?P<release>[bf]*)(?P<build>\d*) parse = (?P<major>\d+)(\.(?P<minor>\d+))(\.(?P<patch>\d+))(?P<release>[bf]*)(?P<build>\d*)
commit = True commit = True
tag = True tag = True

View File

@@ -1,57 +1,25 @@
"""The mega integration.""" """The mega integration."""
import asyncio import asyncio
import logging import logging
import typing
from functools import partial from functools import partial
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_NAME, CONF_DOMAIN,
CONF_DOMAIN, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, CONF_DEVICE_CLASS, CONF_PORT
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE_TEMPLATE,
CONF_DEVICE_CLASS,
CONF_PORT,
) )
from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.service import bind_hass from homeassistant.helpers.service import bind_hass
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from .const import ( from .const import DOMAIN, CONF_INVERT, CONF_RELOAD, PLATFORMS, CONF_PORTS, CONF_CUSTOM, CONF_SKIP, CONF_PORT_TO_SCAN, \
DOMAIN, CONF_MQTT_INPUTS, CONF_HTTP, CONF_RESPONSE_TEMPLATE, CONF_ACTION, CONF_GET_VALUE, CONF_ALLOW_HOSTS, \
CONF_INVERT, CONF_CONV_TEMPLATE, CONF_ALL, CONF_FORCE_D, CONF_DEF_RESPONSE, CONF_FORCE_I2C_SCAN, CONF_HEX_TO_FLOAT, \
PLATFORMS, RGB_COMBINATIONS, CONF_WS28XX, CONF_ORDER, CONF_SMOOTH, CONF_LED, CONF_WHITE_SEP, CONF_CHIP, CONF_RANGE, \
CONF_PORTS, CONF_FILTER_VALUES, CONF_FILTER_SCALE, CONF_FILTER_LOW, CONF_FILTER_HIGH, CONF_FILL_NA, CONF_MEGA_ID, CONF_ADDR, \
CONF_CUSTOM, CONF_1WBUS
CONF_SKIP,
CONF_HTTP,
CONF_RESPONSE_TEMPLATE,
CONF_ACTION,
CONF_GET_VALUE,
CONF_ALLOW_HOSTS,
CONF_CONV_TEMPLATE,
CONF_ALL,
CONF_FORCE_D,
CONF_DEF_RESPONSE,
CONF_FORCE_I2C_SCAN,
CONF_HEX_TO_FLOAT,
RGB_COMBINATIONS,
CONF_WS28XX,
CONF_ORDER,
CONF_SMOOTH,
CONF_LED,
CONF_WHITE_SEP,
CONF_CHIP,
CONF_RANGE,
CONF_FILTER_VALUES,
CONF_FILTER_SCALE,
CONF_FILTER_LOW,
CONF_FILTER_HIGH,
CONF_FILL_NA,
CONF_MEGA_ID,
CONF_ADDR,
CONF_1WBUS,
)
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
@@ -60,53 +28,58 @@ _LOGGER = logging.getLogger(__name__)
_port_n = vol.Any(int, str) _port_n = vol.Any(int, str)
LED_LIGHT = { LED_LIGHT = \
str: vol.Any( {
{ str: vol.Any(
vol.Required(CONF_PORTS): vol.Any( {
vol.ExactSequence([_port_n, _port_n, _port_n]), vol.Required(CONF_PORTS): vol.Any(
vol.ExactSequence([_port_n, _port_n, _port_n, _port_n]), vol.ExactSequence([_port_n, _port_n, _port_n]),
msg="ports must be [R, G, B] or [R, G, B, W] of integers 0..255", vol.ExactSequence([_port_n, _port_n, _port_n, _port_n]),
), msg='ports must be [R, G, B] or [R, G, B, W] of integers 0..255'
vol.Optional(CONF_NAME): str, ),
vol.Optional(CONF_WHITE_SEP, default=True): bool, vol.Optional(CONF_NAME): str,
vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds, vol.Optional(CONF_WHITE_SEP, default=True): bool,
}, vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds,
{ },
vol.Required(CONF_PORT): int, {
vol.Required(CONF_WS28XX): True, vol.Required(CONF_PORT): int,
vol.Optional(CONF_CHIP, default=100): int, vol.Required(CONF_WS28XX): True,
vol.Optional(CONF_ORDER, default="rgb"): vol.Any( vol.Optional(CONF_CHIP, default=100): int,
*RGB_COMBINATIONS, msg=f"order must be one of {RGB_COMBINATIONS}" vol.Optional(CONF_ORDER, default='rgb'): vol.Any(*RGB_COMBINATIONS, msg=f'order must be one of {RGB_COMBINATIONS}'),
), vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds,
vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds, vol.Optional(CONF_NAME): str,
vol.Optional(CONF_NAME): str, },
}, )
) }
}
CUSTOMIZE_PORT = { CUSTOMIZE_PORT = {
vol.Optional( vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool,
CONF_SKIP, description="исключить порт из сканирования", default=False vol.Optional(CONF_FILL_NA, default='last'): vol.Any(
): bool, 'last',
vol.Optional(CONF_FILL_NA, default="last"): vol.Any("last", "none"), 'none'
vol.Optional(CONF_RANGE, description="диапазон диммирования"): [ ),
vol.Optional(CONF_RANGE, description='диапазон диммирования'): [
vol.Range(0, 255), vol.Range(0, 255),
vol.Range(0, 255), vol.Range(0, 255),
], ],
vol.Optional(CONF_INVERT, default=False): bool, vol.Optional(CONF_INVERT, default=False): bool,
vol.Optional(CONF_NAME): vol.Any(str, {vol.Required(str): str}), vol.Optional(CONF_NAME): vol.Any(str, {
vol.Optional(CONF_DOMAIN): vol.Any("light", "switch"), vol.Required(str): str
vol.Optional( }),
CONF_UNIT_OF_MEASUREMENT, vol.Optional(CONF_DOMAIN): vol.Any('light', 'switch'),
description="единицы измерений, либо строка либо мепинг", vol.Optional(CONF_UNIT_OF_MEASUREMENT, description='единицы измерений, либо строка либо мепинг'):
): vol.Any(str, {vol.Required(str): str}), vol.Any(str, {
vol.Optional(CONF_DEVICE_CLASS): vol.Any(str, {vol.Required(str): str}), vol.Required(str): str
}),
vol.Optional(CONF_DEVICE_CLASS):
vol.Any(str, {
vol.Required(str): str
}),
vol.Optional( vol.Optional(
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,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
@@ -123,48 +96,43 @@ CUSTOMIZE_PORT = {
vol.Optional(CONF_DEVICE_CLASS): str, vol.Optional(CONF_DEVICE_CLASS): str,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): str, vol.Optional(CONF_UNIT_OF_MEASUREMENT): str,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}, }
} }
CUSTOMIZE_DS2413 = { CUSTOMIZE_DS2413 = {
vol.Optional(str.lower, description="адрес и индекс устройства"): CUSTOMIZE_PORT vol.Optional(str.lower, description='адрес и индекс устройства'): CUSTOMIZE_PORT
} }
def extender(x): def extender(x):
if isinstance(x, str) and "e" in x: if isinstance(x, str) and 'e' in x:
return x return x
else: else:
raise ValueError('must has "e" in port name') raise ValueError('must has "e" in port name')
OWBUS = vol.Schema({
OWBUS = vol.Schema( vol.Required(CONF_PORT): vol.Any(vol.Coerce(int), vol.Coerce(str)),
{ vol.Required(CONF_MEGA_ID): vol.Coerce(str),
vol.Required(CONF_PORT): vol.Any(vol.Coerce(int), vol.Coerce(str)), vol.Required(CONF_ADDR): [str],
vol.Required(CONF_MEGA_ID): vol.Coerce(str), })
vol.Required(CONF_ADDR): [str],
}
)
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: { DOMAIN: {
vol.Optional(CONF_ALLOW_HOSTS): [str], vol.Optional(CONF_ALLOW_HOSTS): [str],
vol.Optional("entities"): { vol.Optional('entities'): {
vol.Optional(str): vol.Any(CUSTOMIZE_PORT, CUSTOMIZE_DS2413) vol.Optional(str): vol.Any(
}, CUSTOMIZE_PORT,
vol.Optional(vol.Any(str, int), description="id меги из веб-интерфейса"): { CUSTOMIZE_DS2413
)},
vol.Optional(vol.Any(str, int), description='id меги из веб-интерфейса'): {
vol.Optional(CONF_FORCE_D, description='Принудительно слать d после срабатывания входа', default=False): bool,
vol.Optional( vol.Optional(
CONF_FORCE_D, CONF_DEF_RESPONSE,
description="Принудительно слать d после срабатывания входа", description='Ответ по умолчанию',
default=False, default=None
): bool,
vol.Optional(
CONF_DEF_RESPONSE, description="Ответ по умолчанию", default=None
): vol.Any(cv.template, None), ): vol.Any(cv.template, None),
vol.Optional(CONF_LED): LED_LIGHT, vol.Optional(CONF_LED): LED_LIGHT,
vol.Optional( vol.Optional(vol.Any(int, extender), description='номер порта'): vol.Any(
vol.Any(int, extender), description="номер порта"
): vol.Any(
CUSTOMIZE_PORT, CUSTOMIZE_PORT,
CUSTOMIZE_DS2413, CUSTOMIZE_DS2413,
), ),
@@ -173,14 +141,14 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_FILTER_LOW): vol.Coerce(float), vol.Optional(CONF_FILTER_LOW): vol.Coerce(float),
vol.Optional(CONF_FILTER_HIGH): vol.Coerce(float), vol.Optional(CONF_FILTER_HIGH): vol.Coerce(float),
}, },
vol.Optional(CONF_1WBUS): [OWBUS], vol.Optional(CONF_1WBUS): [OWBUS]
} }
}, },
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
ALIVE_STATE = "alive" ALIVE_STATE = 'alive'
DEF_ID = "def" DEF_ID = 'def'
_POLL_TASKS = {} _POLL_TASKS = {}
_hubs = {} _hubs = {}
_subs = {} _subs = {}
@@ -194,40 +162,29 @@ async def async_setup(hass: HomeAssistant, config: dict):
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(
DOMAIN, DOMAIN, 'save', partial(_save_service, hass), schema=vol.Schema({
"save", vol.Optional('mega_id'): str
partial(_save_service, hass), })
schema=vol.Schema({vol.Optional("mega_id"): str}),
) )
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN, 'get_port', partial(_get_port, hass), schema=vol.Schema({
"get_port", vol.Optional('mega_id'): str,
partial(_get_port, hass), vol.Optional('port'): vol.Any(int, [int]),
schema=vol.Schema( })
{
vol.Optional("mega_id"): str,
vol.Optional("port"): vol.Any(int, [int]),
}
),
) )
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN, 'run_cmd', partial(_run_cmd, hass), schema=vol.Schema({
"run_cmd", vol.Optional('port'): int,
partial(_run_cmd, hass), vol.Required('cmd'): str,
schema=vol.Schema( vol.Optional('mega_id'): str,
{ })
vol.Optional("port"): int,
vol.Required("cmd"): str,
vol.Optional("mega_id"): str,
}
),
) )
return True return True
async def get_hub(hass, entry): async def get_hub(hass, entry):
id = entry.data.get("id", entry.entry_id) id = entry.data.get('id', entry.entry_id)
data = dict(entry.data) data = dict(entry.data)
data.update(entry.options or {}) data.update(entry.options or {})
data.update(id=id) data.update(id=id)
@@ -237,7 +194,7 @@ async def get_hub(hass, entry):
async def _add_mega(hass: HomeAssistant, entry: ConfigEntry): async def _add_mega(hass: HomeAssistant, entry: ConfigEntry):
id = entry.data.get("id", entry.entry_id) id = entry.data.get('id', entry.entry_id)
hub = await get_hub(hass, entry) hub = await get_hub(hass, entry)
hub.fw = await hub.get_fw() hub.fw = await hub.get_fw()
hass.data[DOMAIN][id] = hub hass.data[DOMAIN][id] = hub
@@ -256,7 +213,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
await hub.start() await hub.start()
for platform in PLATFORMS: for platform in PLATFORMS:
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform) hass.config_entries.async_forward_entry_setup(
entry, platform
)
) )
await hub.updater.async_refresh() await hub.updater.async_refresh()
return True return True
@@ -278,11 +237,11 @@ async def updater(hass: HomeAssistant, entry: ConfigEntry):
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle removal of an entry.""" """Handle removal of an entry."""
id = entry.data.get("id", entry.entry_id) id = entry.data.get('id', entry.entry_id)
hub: MegaD = hass.data[DOMAIN].get(id) hub: MegaD = hass.data[DOMAIN].get(id)
if hub is None: if hub is None:
return True return True
_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) hass.data[DOMAIN][CONF_ALL].pop(id, None)
@@ -296,24 +255,19 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
await hub.stop() await hub.stop()
return True return True
async_unload_entry = async_remove_entry async_unload_entry = async_remove_entry
async def async_migrate_entry(hass, config_entry: ConfigEntry): async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry.""" """Migrate old entry."""
_LOGGER.debug( _LOGGER.debug("Migrating from version %s to version %s", config_entry.version, ConfigFlow.VERSION)
"Migrating from version %s to version %s",
config_entry.version,
ConfigFlow.VERSION,
)
hub = await get_hub(hass, config_entry) hub = await get_hub(hass, config_entry)
new = dict(config_entry.data) new = dict(config_entry.data)
await hub.start() await hub.start()
cfg = await hub.get_config() cfg = await hub.get_config()
await hub.stop() await hub.stop()
new.update(cfg) new.update(cfg)
_LOGGER.debug(f"new config: %s", new) _LOGGER.debug(f'new config: %s', new)
config_entry.data = new config_entry.data = new
config_entry.version = ConfigFlow.VERSION config_entry.version = ConfigFlow.VERSION
@@ -323,7 +277,7 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
async def _save_service(hass: HomeAssistant, call: ServiceCall): async def _save_service(hass: HomeAssistant, call: ServiceCall):
mega_id = call.data.get("mega_id") mega_id = call.data.get('mega_id')
if mega_id: if mega_id:
hub: MegaD = hass.data[DOMAIN][mega_id] hub: MegaD = hass.data[DOMAIN][mega_id]
await hub.save() await hub.save()
@@ -335,8 +289,8 @@ async def _save_service(hass: HomeAssistant, call: ServiceCall):
@bind_hass @bind_hass
async def _get_port(hass: HomeAssistant, call: ServiceCall): async def _get_port(hass: HomeAssistant, call: ServiceCall):
port = call.data.get("port") port = call.data.get('port')
mega_id = call.data.get("mega_id") mega_id = call.data.get('mega_id')
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:
@@ -346,7 +300,6 @@ async def _get_port(hass: HomeAssistant, call: ServiceCall):
elif isinstance(port, list): elif isinstance(port, list):
for x in port: for x in port:
await hub.get_port(x) await hub.get_port(x)
hub.updater.async_set_updated_data(hub.values)
else: else:
for hub in hass.data[DOMAIN][CONF_ALL].values(): for hub in hass.data[DOMAIN][CONF_ALL].values():
if not isinstance(hub, MegaD): if not isinstance(hub, MegaD):
@@ -358,13 +311,12 @@ async def _get_port(hass: HomeAssistant, call: ServiceCall):
elif isinstance(port, list): elif isinstance(port, list):
for x in port: for x in port:
await hub.get_port(x) await hub.get_port(x)
hub.updater.async_set_updated_data(hub.values)
@bind_hass @bind_hass
async def _run_cmd(hass: HomeAssistant, call: ServiceCall): async def _run_cmd(hass: HomeAssistant, call: ServiceCall):
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.request(cmd=cmd) await hub.request(cmd=cmd)

View File

@@ -164,7 +164,7 @@ class MegaD:
if allow_hosts is not None and DOMAIN in hass.data: if allow_hosts is not None and DOMAIN in hass.data:
allow_hosts = set(allow_hosts.split(";")) allow_hosts = set(allow_hosts.split(";"))
hass.data[DOMAIN][CONF_HTTP].allowed_hosts |= allow_hosts hass.data[DOMAIN][CONF_HTTP].allowed_hosts |= allow_hosts
hass.data[DOMAIN][CONF_HTTP].protected = protected hass.data[DOMAIN][CONF_HTTP].protected = protected
except Exception: except Exception:
self.lg.exception("while setting allowed hosts") self.lg.exception("while setting allowed hosts")
self.binary_sensors = [] self.binary_sensors = []
@@ -477,7 +477,7 @@ class MegaD:
return return
ret = {} ret = {}
for i, x in enumerate(values.split(";")): for i, x in enumerate(values.split(";")):
ret[f"{port}e{i}" if not self.new_naming else f"{port:02d}e{i:02d}"] = x ret[f"{port}e{i}"] = x
return ret return ret
async def _update_i2c(self, params): async def _update_i2c(self, params):

View File

@@ -263,7 +263,6 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
setattr(self, f"_{item}", value) setattr(self, f"_{item}", value)
if item == "rgb_color": if item == "rgb_color":
_after = map_reorder_rgb(value, RGB, self._color_order) _after = map_reorder_rgb(value, RGB, self._color_order)
self._hs_color = colorsys.rgb_to_hsv(*value)
_after = _after or self.get_rgbw() _after = _after or self.get_rgbw()
self._rgb_color = map_reorder_rgb(tuple(_after[:3]), self._color_order, RGB) self._rgb_color = map_reorder_rgb(tuple(_after[:3]), self._color_order, RGB)
if transition is None: if transition is None:

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": "v1.1.8b6" "version": "v1.1.8b2"
} }

View File

@@ -4,9 +4,7 @@ import voluptuous as vol
import struct import struct
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_SCHEMA, PLATFORM_SCHEMA as SENSOR_SCHEMA, SensorEntity, SensorDeviceClass
SensorEntity,
SensorDeviceClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@@ -14,46 +12,35 @@ from homeassistant.const import (
CONF_PORT, CONF_PORT,
CONF_UNIQUE_ID, CONF_UNIQUE_ID,
CONF_ID, CONF_ID,
CONF_TYPE, CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE_TEMPLATE,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from .entities import MegaPushEntity from .entities import MegaPushEntity
from .const import ( from .const import CONF_KEY, TEMP, HUM, W1, W1BUS, CONF_CONV_TEMPLATE, CONF_HEX_TO_FLOAT, DOMAIN, CONF_CUSTOM, \
CONF_KEY, CONF_SKIP, CONF_FILTER_VALUES, CONF_FILTER_SCALE, CONF_FILTER_LOW, CONF_FILTER_HIGH, CONF_FILL_NA
TEMP,
HUM,
W1,
W1BUS,
CONF_CONV_TEMPLATE,
CONF_HEX_TO_FLOAT,
DOMAIN,
CONF_CUSTOM,
CONF_SKIP,
CONF_FILTER_VALUES,
CONF_FILTER_SCALE,
CONF_FILTER_LOW,
CONF_FILTER_HIGH,
CONF_FILL_NA,
)
from .hub import MegaD from .hub import MegaD
import re import re
from .tools import int_ignore from .tools import int_ignore
lg = logging.getLogger(__name__) lg = logging.getLogger(__name__)
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\.]+)')
PATTERNS = { PATTERNS = {
TEMP: TEMP_PATT, TEMP: TEMP_PATT,
HUM: HUM_PATT, HUM: HUM_PATT,
} }
UNITS = {TEMP: "°C", HUM: "%"} UNITS = {
CLASSES = {TEMP: SensorDeviceClass.TEMPERATURE, HUM: SensorDeviceClass.HUMIDITY} TEMP: '°C',
HUM: '%'
}
CLASSES = {
TEMP: SensorDeviceClass.TEMPERATURE,
HUM: SensorDeviceClass.HUMIDITY
}
# Validation of the user's configuration # Validation of the user's configuration
_ITEM = { _ITEM = {
vol.Required(CONF_PORT): int, vol.Required(CONF_PORT): int,
@@ -63,18 +50,18 @@ _ITEM = {
W1, W1,
W1BUS, W1BUS,
), ),
vol.Optional(CONF_KEY, default=""): str, vol.Optional(CONF_KEY, default=''): str,
} }
PLATFORM_SCHEMA = SENSOR_SCHEMA.extend( PLATFORM_SCHEMA = SENSOR_SCHEMA.extend(
{vol.Optional(str, description="mega id"): [_ITEM]}, {
vol.Optional(str, description="mega id"): [_ITEM]
},
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
async def async_setup_platform(hass, config, add_entities, discovery_info=None): async def async_setup_platform(hass, config, add_entities, discovery_info=None):
lg.warning( lg.warning('mega integration does not support yaml for sensors, please use UI configuration')
"mega integration does not support yaml for sensors, please use UI configuration"
)
return True return True
@@ -85,23 +72,19 @@ def _make_entity(config_entry, mid: str, port: int, conf: dict):
mega_id=mid, mega_id=mid,
port=port, port=port,
patt=PATTERNS.get(key), patt=PATTERNS.get(key),
unit_of_measurement=UNITS.get( unit_of_measurement=UNITS.get(key, UNITS[TEMP]), # TODO: make other units, make options in config flow
key, UNITS[TEMP]
), # TODO: make other units, make options in config flow
device_class=CLASSES.get(key, CLASSES[TEMP]), device_class=CLASSES.get(key, CLASSES[TEMP]),
id_suffix=key, id_suffix=key,
config_entry=config_entry, config_entry=config_entry
) )
async def async_setup_entry( async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_devices):
hass: HomeAssistant, config_entry: ConfigEntry, async_add_devices
):
mid = config_entry.data[CONF_ID] mid = config_entry.data[CONF_ID]
hub: MegaD = hass.data["mega"][mid] hub: MegaD = hass.data['mega'][mid]
devices = [] devices = []
customize = hass.data.get(DOMAIN, {}).get(CONF_CUSTOM, {}).get(mid, {}) customize = hass.data.get(DOMAIN, {}).get(CONF_CUSTOM, {}).get(mid, {})
for tp in ["sensor", "i2c"]: for tp in ['sensor', 'i2c']:
for port, cfg in config_entry.data.get(tp, {}).items(): for port, cfg in config_entry.data.get(tp, {}).items():
port = int_ignore(port) port = int_ignore(port)
c = customize.get(port, {}) c = customize.get(port, {})
@@ -109,19 +92,14 @@ async def async_setup_entry(
hub.skip_ports |= {port} hub.skip_ports |= {port}
continue continue
for data in cfg: for data in cfg:
hub.lg.debug( hub.lg.debug(f'add sensor on port %s with data %s, constructor: %s', port, data, _constructors[tp])
f"add sensor on port %s with data %s, constructor: %s",
port,
data,
_constructors[tp],
)
sensor = _constructors[tp]( sensor = _constructors[tp](
mega=hub, mega=hub,
port=port, port=port,
config_entry=config_entry, config_entry=config_entry,
**data, **data,
) )
if "<" in sensor.name: if '<' in sensor.name:
continue continue
devices.append(sensor) devices.append(sensor)
@@ -129,76 +107,64 @@ async def async_setup_entry(
class FilterBadValues(MegaPushEntity, SensorEntity): class FilterBadValues(MegaPushEntity, SensorEntity):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._prev_value = None self._prev_value = None
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def filter_value(self, value): def filter_value(self, value):
if value is None:
return
try: try:
if ( if value \
value in self.filter_values in self.filter_values \
or (self.filter_low is not None and value < self.filter_low) or (self.filter_low is not None and value < self.filter_low) \
or (self.filter_high is not None and value > self.filter_high) or (self.filter_high is not None and value > self.filter_high) \
or ( or (
self._prev_value is not None self._prev_value is not None
and self.filter_scale is not None and self.filter_scale is not None
and abs(value)
> 2 # при переходе через 0 каждое небольшое изменение будет иметь слишком большой эффект
and ( and (
abs((value - self._prev_value) / self._prev_value) abs(value - self._prev_value) / self._prev_value > self.filter_scale
> self.filter_scale
) )
)
): ):
if self.fill_na == "last": if self.fill_na == 'last':
value = self._prev_value value = self._prev_value
else: else:
value = None value = None
self._prev_value = value self._prev_value = value
return value return value
except Exception as exc: except Exception as exc:
lg.exception(f"while parsing value") lg.exception(f'while parsing value')
return None return None
@property @property
def filter_values(self): def filter_values(self):
return self.customize.get( return self.customize.get(CONF_FILTER_VALUES, self.mega.customize.get(CONF_FILTER_VALUES, []))
CONF_FILTER_VALUES, self.mega.customize.get(CONF_FILTER_VALUES, [])
)
@property @property
def filter_scale(self): def filter_scale(self):
return self.customize.get( return self.customize.get(CONF_FILTER_SCALE, self.mega.customize.get(CONF_FILTER_SCALE, None))
CONF_FILTER_SCALE, self.mega.customize.get(CONF_FILTER_SCALE, None)
)
@property @property
def filter_low(self): def filter_low(self):
return self.customize.get( return self.customize.get(CONF_FILTER_LOW, self.mega.customize.get(CONF_FILTER_LOW, None))
CONF_FILTER_LOW, self.mega.customize.get(CONF_FILTER_LOW, None)
)
@property @property
def filter_high(self): def filter_high(self):
return self.customize.get( return self.customize.get(CONF_FILTER_HIGH, self.mega.customize.get(CONF_FILTER_HIGH, None))
CONF_FILTER_HIGH, self.mega.customize.get(CONF_FILTER_HIGH, None)
)
@property @property
def fill_na(self): def fill_na(self):
return self.customize.get(CONF_FILL_NA, "last") return self.customize.get(CONF_FILL_NA, 'last')
class MegaI2C(FilterBadValues): class MegaI2C(FilterBadValues):
def __init__( def __init__(
self, self,
*args, *args,
device_class: str, device_class: str,
params: dict, params: dict,
unit_of_measurement: str = None, unit_of_measurement: str = None,
**kwargs, **kwargs
): ):
self._device_class = device_class self._device_class = device_class
self._params = tuple(params.items()) self._params = tuple(params.items())
@@ -217,11 +183,9 @@ class MegaI2C(FilterBadValues):
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
attrs = super().extra_state_attributes or {} attrs = super().extra_state_attributes or {}
attrs.update( attrs.update({
{ 'i2c_id': self.id_suffix,
"i2c_id": self.id_suffix, })
}
)
return attrs return attrs
@property @property
@@ -238,24 +202,22 @@ class MegaI2C(FilterBadValues):
ret = self.mega.values.get(self._params) ret = self.mega.values.get(self._params)
if self.customize.get(CONF_HEX_TO_FLOAT): if self.customize.get(CONF_HEX_TO_FLOAT):
try: try:
ret = struct.unpack("!f", bytes.fromhex(ret))[0] ret = struct.unpack('!f', bytes.fromhex(ret))[0]
except: except:
self.lg.warning(f"could not convert {ret} form hex to float") self.lg.warning(f'could not convert {ret} form hex to float')
tmpl: Template = self.customize.get( tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE))
CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE)
)
try: try:
ret = float(ret) ret = float(ret)
if tmpl is not None and self.hass is not None: if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass tmpl.hass = self.hass
ret = tmpl.async_render({"value": ret}) ret = tmpl.async_render({'value': ret})
except: except:
ret = ret ret = ret
ret = self.filter_value(ret) ret = self.filter_value(ret)
if ret is not None: if ret is not None:
return str(ret) return str(ret)
except Exception: except Exception:
lg.exception("while getting value") lg.exception('while getting value')
return None return None
@property @property
@@ -264,8 +226,14 @@ class MegaI2C(FilterBadValues):
class Mega1WSensor(FilterBadValues): class Mega1WSensor(FilterBadValues):
def __init__( def __init__(
self, unit_of_measurement=None, device_class=None, key=None, *args, **kwargs self,
unit_of_measurement=None,
device_class=None,
key=None,
*args,
**kwargs
): ):
""" """
1-wire sensor entity 1-wire sensor entity
@@ -296,7 +264,7 @@ class Mega1WSensor(FilterBadValues):
@property @property
def unique_id(self): def unique_id(self):
if self.key: if self.key:
return super().unique_id + f"_{self.key}" return super().unique_id + f'_{self.key}'
else: else:
return super().unique_id return super().unique_id
@@ -316,27 +284,23 @@ class Mega1WSensor(FilterBadValues):
def native_value(self): def native_value(self):
try: try:
ret = None ret = None
if not hasattr(self, "key"): if not hasattr(self, 'key'):
return None return None
if self.key: if self.key:
try: try:
ret = self.mega.values.get(self.port, {}) ret = self.mega.values.get(self.port, {})
if isinstance(ret, dict): if isinstance(ret, dict):
ret = ret.get("value", {}) ret = ret.get('value', {})
if isinstance(ret, dict): if isinstance(ret, dict):
ret = ret.get(self.key) ret = ret.get(self.key)
except: except:
self.lg.error(self.mega.values.get(self.port, {}).get("value", {})) self.lg.error(self.mega.values.get(self.port, {}).get('value', {}))
return return
else: else:
ret = self.mega.values.get(self.port, {}).get("value") ret = self.mega.values.get(self.port, {}).get('value')
if ( if ret is None and self.fill_na == 'fill_na' and self.prev_value is not None:
ret is None
and self.fill_na == "fill_na"
and self.prev_value is not None
):
ret = self.prev_value ret = self.prev_value
elif ret is None and self.fill_na == "fill_na" and self._state is not None: elif ret is None and self.fill_na == 'fill_na' and self._state is not None:
ret = self._state.state ret = self._state.state
try: try:
ret = float(ret) ret = float(ret)
@@ -346,17 +310,15 @@ class Mega1WSensor(FilterBadValues):
ret = self.prev_value ret = self.prev_value
if self.customize.get(CONF_HEX_TO_FLOAT): if self.customize.get(CONF_HEX_TO_FLOAT):
try: try:
ret = struct.unpack("!f", bytes.fromhex(ret))[0] ret = struct.unpack('!f', bytes.fromhex(ret))[0]
except: except:
self.lg.warning(f"could not convert {ret} form hex to float") self.lg.warning(f'could not convert {ret} form hex to float')
tmpl: Template = self.customize.get( tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE))
CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE)
)
try: try:
ret = float(ret) ret = float(ret)
if tmpl is not None and self.hass is not None: if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass tmpl.hass = self.hass
ret = tmpl.async_render({"value": ret}) ret = tmpl.async_render({'value': ret})
except: except:
pass pass
ret = self.filter_value(ret) ret = self.filter_value(ret)
@@ -364,7 +326,7 @@ class Mega1WSensor(FilterBadValues):
if ret is not None: if ret is not None:
return str(ret) return str(ret)
except Exception: except Exception:
lg.exception("while parsing state") lg.exception('while parsing state')
return None return None
@property @property
@@ -380,6 +342,6 @@ class Mega1WSensor(FilterBadValues):
_constructors = { _constructors = {
"sensor": Mega1WSensor, 'sensor': Mega1WSensor,
"i2c": MegaI2C, 'i2c': MegaI2C,
} }

View File

@@ -12,7 +12,7 @@
Если вам понравилась интеграция, не забудьте поставить звезду на гитхабе - вам не сложно, а мне приятно ) А если Если вам понравилась интеграция, не забудьте поставить звезду на гитхабе - вам не сложно, а мне приятно ) А если
интеграция очень понравилась - еще приятнее, если вы воспользуетесь кнопкой доната ) интеграция очень понравилась - еще приятнее, если вы воспользуетесь кнопкой доната )
Обновление прошивки MegaD можно делать прямо из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git) Обновление прошивки MegaD можно делать из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git)
## Основные особенности {: #mains } ## Основные особенности {: #mains }
- Настройка в [веб-интерфейсе](settings.md) + [yaml](yaml.md) - Настройка в [веб-интерфейсе](settings.md) + [yaml](yaml.md)

View File

@@ -1,7 +1,7 @@
# MegaD HomeAssistant integration # MegaD HomeAssistant integration
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs) [![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/hacs/integration)
[![Donate](https://img.shields.io/badge/donate-Yandex-red.svg)](https://yoomoney.ru/to/410013955329136) [![Donate](https://img.shields.io/badge/donate-Yandex-red.svg)](https://yoomoney.ru/to/410013955329136)
Интеграция с [MegaD-2561, MegaD-328](https://www.ab-log.ru/smart-house/ethernet/megad-2561) Интеграция с [MegaD-2561, MegaD-328](https://www.ab-log.ru/smart-house/ethernet/megad-2561)