Compare commits

..

5 Commits

Author SHA1 Message Date
Викторов Андрей Германович
8e1984e1c1 Bump version: 1.1.0b0 → 1.1.0b1 2021-09-07 10:25:12 +03:00
Викторов Андрей Германович
b7383130c2 Bump version: 1.0.9 → 1.1.0b0 2021-09-07 10:24:54 +03:00
Викторов Андрей Германович
b31922d570 add bumb2version 2021-09-07 10:24:52 +03:00
Викторов Андрей Германович
9ca1326db2 add bumb2version 2021-09-07 10:24:18 +03:00
Викторов Андрей Германович
13f4e138e5 add bumb2version 2021-09-07 10:21:28 +03:00
16 changed files with 205 additions and 413 deletions

View File

@@ -1,6 +1,6 @@
[bumpversion] [bumpversion]
current_version = 1.1.7b0 current_version = 1.1.0b1
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
serialize = serialize =

View File

@@ -1,14 +1,13 @@
"""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_DOMAIN, CONF_NAME, 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, CONF_UNIQUE_ID
) )
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
@@ -17,9 +16,7 @@ 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_ALL, CONF_FORCE_D, CONF_DEF_RESPONSE, CONF_FORCE_I2C_SCAN, CONF_HEX_TO_FLOAT, \ 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, \ 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
@@ -52,16 +49,15 @@ LED_LIGHT = \
) )
} }
ENCODER = \
{
vol.Optional('int', description='int порт'): int,
vol.Required('scl', description='scl порт'): int,
vol.Optional('sync_to', description='entity_id с которым синхронизироваться'): str,
}
CUSTOMIZE_PORT = { CUSTOMIZE_PORT = {
vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool, vol.Optional(CONF_SKIP, description='исключить порт из сканирования', default=False): bool,
vol.Optional(CONF_FILL_NA, default='last'): vol.Any(
'last',
'none'
),
vol.Optional(CONF_RANGE, description='диапазон диммирования'): [
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.Optional(CONF_NAME): vol.Any(str, {
vol.Required(str): str vol.Required(str): str
@@ -85,10 +81,6 @@ CUSTOMIZE_PORT = {
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_FORCE_I2C_SCAN): bool, vol.Optional(CONF_FORCE_I2C_SCAN): bool,
vol.Optional(CONF_HEX_TO_FLOAT): bool, vol.Optional(CONF_HEX_TO_FLOAT): bool,
vol.Optional(CONF_FILTER_VALUES): [vol.Coerce(float)],
vol.Optional(CONF_FILTER_SCALE): vol.Coerce(float),
vol.Optional(CONF_FILTER_LOW): vol.Coerce(float),
vol.Optional(CONF_FILTER_HIGH): vol.Coerce(float),
vol.Optional(CONF_SMOOTH): cv.time_period_seconds, vol.Optional(CONF_SMOOTH): cv.time_period_seconds,
# vol.Optional(CONF_RANGE): vol.ExactSequence([int, int]), TODO: сделать отбрасывание "плохих" значений # vol.Optional(CONF_RANGE): vol.ExactSequence([int, int]), TODO: сделать отбрасывание "плохих" значений
vol.Optional(str): { vol.Optional(str): {
@@ -109,21 +101,11 @@ def extender(x):
else: else:
raise ValueError('must has "e" in port name') raise ValueError('must has "e" in port name')
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_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(str): vol.Any(
CUSTOMIZE_PORT,
CUSTOMIZE_DS2413
)},
vol.Optional(vol.Any(str, int), description='id меги из веб-интерфейса'): { vol.Optional(vol.Any(str, int), description='id меги из веб-интерфейса'): {
vol.Optional(CONF_FORCE_D, description='Принудительно слать d после срабатывания входа', default=False): bool, vol.Optional(CONF_FORCE_D, description='Принудительно слать d после срабатывания входа', default=False): bool,
vol.Optional( vol.Optional(
@@ -136,12 +118,8 @@ CONFIG_SCHEMA = vol.Schema(
CUSTOMIZE_PORT, CUSTOMIZE_PORT,
CUSTOMIZE_DS2413, CUSTOMIZE_DS2413,
), ),
vol.Optional(CONF_FILTER_VALUES): [vol.Coerce(float)], vol.Optional('encoders'): {str: ENCODER}
vol.Optional(CONF_FILTER_SCALE): vol.Coerce(float), }
vol.Optional(CONF_FILTER_LOW): vol.Coerce(float),
vol.Optional(CONF_FILTER_HIGH): vol.Coerce(float),
},
vol.Optional(CONF_1WBUS): [OWBUS]
} }
}, },
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
@@ -240,7 +218,7 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
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
_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)
@@ -251,7 +229,7 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
if task is not None: if task is not None:
task.cancel() task.cancel()
if hub is None: if hub is None:
return True return
await hub.stop() await hub.stop()
return True return True

View File

@@ -67,7 +67,6 @@ class MegaBinarySensor(BinarySensorEntity, MegaPushEntity):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.mega.binary_sensors.append(self.port)
self._is_on = None self._is_on = None
self._attrs = None self._attrs = None
self._click_task = None self._click_task = None

View File

@@ -63,7 +63,7 @@ async def validate_input(hass: core.HomeAssistant, data):
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for mega.""" """Handle a config flow for mega."""
VERSION = 26 VERSION = 24
CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED CONNECTION_CLASS = config_entries.CONN_CLASS_ASSUMED
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):

View File

@@ -45,13 +45,6 @@ CONF_SMOOTH = 'smooth'
CONF_WHITE_SEP = 'white_sep' CONF_WHITE_SEP = 'white_sep'
CONF_CHIP = 'chip' CONF_CHIP = 'chip'
CONF_RANGE = 'range' CONF_RANGE = 'range'
CONF_FILL_NA = 'fill_na'
CONF_FILTER_VALUES = 'filter_values'
CONF_FILTER_SCALE = 'filter_scale'
CONF_FILTER_LOW = 'filter_low'
CONF_FILTER_HIGH = 'filter_high'
CONF_1WBUS = '1wbus'
CONF_ADDR = 'addr'
PLATFORMS = [ PLATFORMS = [
"light", "light",
"switch", "switch",

View File

@@ -8,12 +8,11 @@ from functools import partial
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import State from homeassistant.core import State
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from . import hub as h from . import hub as h
from .const import DOMAIN, CONF_CUSTOM, CONF_INVERT, EVENT_BINARY_SENSOR, LONG, \ from .const import DOMAIN, CONF_CUSTOM, CONF_INVERT, EVENT_BINARY_SENSOR, LONG, \
LONG_RELEASE, RELEASE, PRESS, SINGLE_CLICK, DOUBLE_CLICK, EVENT_BINARY, CONF_SMOOTH, CONF_RANGE LONG_RELEASE, RELEASE, PRESS, SINGLE_CLICK, DOUBLE_CLICK, EVENT_BINARY, CONF_SMOOTH
_events_on = False _events_on = False
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -55,6 +54,7 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
smooth=None, smooth=None,
**kwargs, **kwargs,
): ):
super().__init__(mega.updater)
self._smooth = smooth self._smooth = smooth
self.http_cmd = http_cmd self.http_cmd = http_cmd
self._state: State = None self._state: State = None
@@ -85,7 +85,6 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
self._can_smooth_hard = None self._can_smooth_hard = None
if self.http_cmd == 'ds2413': if self.http_cmd == 'ds2413':
self.mega.ds2413_ports |= {self.port} self.mega.ds2413_ports |= {self.port}
super().__init__(coordinator=mega.updater)
@property @property
def is_ws(self): def is_ws(self):
@@ -127,23 +126,21 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
def customize(self): def customize(self):
if self._customize is not None: if self._customize is not None:
return self._customize return self._customize
if self.hass is None or self.entity_id is None: if self.hass is None:
return {} return {}
if self._customize is None: if self._customize is None:
c_entity_id = self.hass.data.get(DOMAIN, {}).get(CONF_CUSTOM).get('entities', {}).get(self.entity_id, {})
c = self.hass.data.get(DOMAIN, {}).get(CONF_CUSTOM) or {} c = self.hass.data.get(DOMAIN, {}).get(CONF_CUSTOM) or {}
c = c.get(self._mega_id) or {} c = c.get(self._mega_id) or {}
c = c.get(self.port) or {} c = c.get(self.port) or {}
if self.addr is not None and self.index is not None and isinstance(c, dict): if self.addr is not None and self.index is not None and isinstance(c, dict):
idx = self.addr.lower() + f'_a' if self.index == 0 else '_b' idx = self.addr.lower() + f'_a' if self.index == 0 else '_b'
c = c.get(idx, {}) c = c.get(idx, {})
c.update(c_entity_id)
self._customize = c self._customize = c
return self._customize return self._customize
@property @property
def device_info(self) -> DeviceInfo: def device_info(self):
if isinstance(self.port, list): if isinstance(self.port, list):
pt_idx = self.id_suffix pt_idx = self.id_suffix
else: else:
@@ -152,20 +149,26 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
pt_idx, _ = _pt.split('e') pt_idx, _ = _pt.split('e')
else: else:
pt_idx = _pt pt_idx = _pt
return DeviceInfo( return {
identifiers={ "identifiers": {
# Serial numbers are unique identifiers within a specific domain # Serial numbers are unique identifiers within a specific domain
(DOMAIN, f'{self._mega_id}', pt_idx) (DOMAIN, f'{self._mega_id}', pt_idx),
}, },
name=self.name, "config_entries": [
manufacturer='ab-log.ru', self.config_entry,
sw_version=self.mega.fw, ],
via_device=(DOMAIN, self._mega_id), "name": f'{self._mega_id} port {pt_idx}' if not isinstance(self.port, list) else f'{self._mega_id} {pt_idx}',
) "manufacturer": 'ab-log.ru',
# "model": self.light.productname,
"sw_version": self.mega.fw,
"via_device": (DOMAIN, self._mega_id),
}
@property @property
def lg(self) -> logging.Logger: def lg(self) -> logging.Logger:
return _LOGGER if self._lg is None:
self._lg = self.mega.lg.getChild(self._name or self.unique_id)
return self._lg
@property @property
def available(self) -> bool: def available(self) -> bool:
@@ -298,43 +301,35 @@ class MegaOutPort(MegaPushEntity):
else: else:
return 255 return 255
@property
def range(self) -> typing.List[int]:
return self.customize.get(CONF_RANGE, [0, 255])
@property @property
def invert(self): def invert(self):
return self.customize.get(CONF_INVERT, False) return self.customize.get(CONF_INVERT, False)
@property @property
def brightness(self): def brightness(self):
ret = None
if not self.dimmer: if not self.dimmer:
return return
val = self.mega.values.get(self.port, {}) val = self.mega.values.get(self.port, {})
if isinstance(val, dict) and len(val) == 0 and self._state is not None: if isinstance(val, dict) and len(val) == 0 and self._state is not None:
ret = safe_int(self._state.attributes.get("brightness"), def_on=self.max_dim, def_off=0, def_val=0) return self._state.attributes.get("brightness")
ret = self._calc_brightness(ret)
elif isinstance(self.port, str) and 'e' in self.port: elif isinstance(self.port, str) and 'e' in self.port:
if isinstance(val, str): if isinstance(val, str):
val = safe_int(val, def_on=self.max_dim, def_off=0, def_val=0) val = safe_int(val)
else: else:
val = 0 val = 0
if val == 0: if val == 0:
ret = self._brightness return self._brightness
elif isinstance(val, (int, float)): elif isinstance(val, (int, float)):
ret = int(val / self.dimmer_scale) return int(val / self.dimmer_scale)
elif val is not None: elif val is not None:
val = val.get("value") val = val.get("value")
if val is None: if val is None:
return return
try: try:
val = int(val) val = int(val)
ret = val return val
except Exception: except Exception:
pass pass
ret = self._cal_reverse_brightness(ret)
return ret
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
@@ -420,28 +415,6 @@ class MegaOutPort(MegaPushEntity):
updater=partial(self.update_from_smooth, update_state=update_state), updater=partial(self.update_from_smooth, update_state=update_state),
)) ))
def _calc_brightness(self, brightness):
if brightness is None:
brightness = 0
pct = brightness / 255
pct = max((0, pct))
pct = min((pct, 1))
l, h = self.range
d = h - l
brightness = round(pct * d + l)
return brightness
def _cal_reverse_brightness(self, brightness):
if brightness is None:
brightness = 0
l, h = self.range
d = h - l
pct = (brightness - l) / d
pct = max((0, pct))
pct = min((pct, 1))
brightness = round(pct * 255)
return brightness
async def async_turn_on(self, brightness=None, transition=None, **kwargs): async def async_turn_on(self, brightness=None, transition=None, **kwargs):
if (time.time() - self._last_called) < 0.1: if (time.time() - self._last_called) < 0.1:
return return
@@ -451,7 +424,6 @@ class MegaOutPort(MegaPushEntity):
if not self.is_on and (brightness is None or brightness == 0): if not self.is_on and (brightness is None or brightness == 0):
brightness = self._restore_brightness brightness = self._restore_brightness
brightness = brightness or self.brightness or 255 brightness = brightness or self.brightness or 255
brightness = self._calc_brightness(brightness)
_prev = safe_int(self.brightness) or 0 _prev = safe_int(self.brightness) or 0
self._brightness = brightness self._brightness = brightness
if self.dimmer and brightness == 0: if self.dimmer and brightness == 0:
@@ -495,7 +467,7 @@ class MegaOutPort(MegaPushEntity):
if (time.time() - self._last_called) < 0.1: if (time.time() - self._last_called) < 0.1:
return return
self._last_called = time.time() self._last_called = time.time()
self._restore_brightness = self._cal_reverse_brightness(safe_int(self._brightness)) self._restore_brightness = safe_int(self._brightness)
if not self.dimmer: if not self.dimmer:
transition = None transition = None
cmd = "0" if not self.invert else "1" cmd = "0" if not self.invert else "1"
@@ -531,12 +503,12 @@ class MegaOutPort(MegaPushEntity):
def safe_int(v, def_on=1, def_off=0, def_val=None): def safe_int(v):
if v == 'ON': if v == 'ON':
return def_on return 1
elif v == 'OFF': elif v == 'OFF':
return def_off return 0
try: try:
return int(v) return int(v)
except (ValueError, TypeError): except (ValueError, TypeError):
return def_val return None

View File

@@ -135,7 +135,6 @@ class MegaView(HomeAssistantView):
await hub.request(cmd=act.replace(':3', f':{v}')) await hub.request(cmd=act.replace(':3', f':{v}'))
ret = 'd' if hub.force_d else '' ret = 'd' if hub.force_d else ''
else: else:
# elif port in hub.binary_sensors:
hub.values[port] = data hub.values[port] = data
for cb in self.callbacks[hub.id][port]: for cb in self.callbacks[hub.id][port]:
cb(data) cb(data)
@@ -148,7 +147,7 @@ class MegaView(HomeAssistantView):
_LOGGER.debug('response %s', ret) _LOGGER.debug('response %s', ret)
Response(body='' if hub.fake_response else ret, content_type='text/plain') Response(body='' if hub.fake_response else ret, content_type='text/plain')
if hub.fake_response and 'value' not in data and 'pt' in data and port in hub.binary_sensors: if hub.fake_response and 'value' not in data and 'pt' in data:
if 'd' in ret: if 'd' in ret:
await hub.request(pt=port, cmd=ret) await hub.request(pt=port, cmd=ret)
else: else:

View File

@@ -9,10 +9,10 @@ import re
import json import json
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( TEMP_CELSIUS, PERCENTAGE, LIGHT_LUX 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.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@@ -45,10 +45,10 @@ UNITS = {
LUX: LIGHT_LUX LUX: LIGHT_LUX
} }
CLASSES = { CLASSES = {
TEMP: SensorDeviceClass.TEMPERATURE, TEMP: DEVICE_CLASS_TEMPERATURE,
HUM: SensorDeviceClass.HUMIDITY, HUM: DEVICE_CLASS_HUMIDITY,
PRESS: SensorDeviceClass.PRESSURE, PRESS: DEVICE_CLASS_PRESSURE,
LUX: SensorDeviceClass.ILLUMINANCE LUX: DEVICE_CLASS_ILLUMINANCE
} }
I2C_DEVICE_TYPES = { I2C_DEVICE_TYPES = {
"2": LUX, # BH1750 "2": LUX, # BH1750
@@ -164,7 +164,6 @@ class MegaD:
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 = []
async def start(self): async def start(self):
pass pass
@@ -310,8 +309,8 @@ class MegaD:
return ret return ret
except asyncio.TimeoutError: except asyncio.TimeoutError:
self.lg.warning(f'timeout while requesting {url}') self.lg.warning(f'timeout while requesting {url}')
# raise raise
await asyncio.sleep(1) # await asyncio.sleep(1)
raise asyncio.TimeoutError('after 3 tries') raise asyncio.TimeoutError('after 3 tries')
async def save(self): async def save(self):
@@ -427,7 +426,7 @@ class MegaD:
def subscribe(self, port, callback): def subscribe(self, port, callback):
port = int_ignore(port) port = int_ignore(port)
self.lg.debug( self.lg.debug(
f'subscribe %s', port, f'subscribe %s %s', port, callback
) )
self.http.callbacks[self.id][port].append(callback) self.http.callbacks[self.id][port].append(callback)
@@ -567,9 +566,6 @@ class MegaD:
elif cfg.pty == '4' and cfg.m == '2': elif cfg.pty == '4' and cfg.m == '2':
# scl исключаем из сканирования # scl исключаем из сканирования
continue continue
elif cfg.pty is None and nports < 30:
# вроде как это ADC на 328 меге
ret['sensor'][port].append(dict())
elif cfg.pty in ('3', '2', '4'): elif cfg.pty in ('3', '2', '4'):
http_cmd = 'get' http_cmd = 'get'
if cfg.d == '5' and cfg.pty == '3': if cfg.d == '5' and cfg.pty == '3':

View File

@@ -1,10 +1,12 @@
import typing from dataclasses import dataclass, field
from dataclasses import dataclass, astuple
from urllib.parse import parse_qsl, urlparse from urllib.parse import parse_qsl, urlparse
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.const import ( from homeassistant.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_CO2,
PERCENTAGE, PERCENTAGE,
LIGHT_LUX, LIGHT_LUX,
TEMP_CELSIUS, TEMP_CELSIUS,
@@ -13,15 +15,7 @@ from homeassistant.const import (
) )
from collections import namedtuple from collections import namedtuple
DeviceType = namedtuple('DeviceType', 'device_class,unit_of_measurement,suffix')
# DeviceType = namedtuple('DeviceType', 'device_class,unit_of_measurement,suffix')
@dataclass
class DeviceType:
device_class: typing.Optional[str] = None
unit_of_measurement: typing.Optional[str] = None
suffix: typing.Optional[str] = None
delay: typing.Optional[float] = None
def parse_scan_page(page: str): def parse_scan_page(page: str):
@@ -38,40 +32,39 @@ def parse_scan_page(page: str):
continue continue
classes = i2c_classes.get(dev, []) classes = i2c_classes.get(dev, [])
for i, c in enumerate(classes): for i, c in enumerate(classes):
_params = params.copy()
if c is Skip: if c is Skip:
continue continue
elif c is Request: elif c is Request:
req.append(_params) req.append(params)
continue continue
elif isinstance(c, Request): elif isinstance(c, Request):
if c.delay: if c.delay:
_params['delay'] = c.delay params = params.copy()
req.append(_params) params['delay'] = c.delay
req.append(params)
continue continue
elif isinstance(c, DeviceType): elif isinstance(c, DeviceType):
c, m, suffix, delay = astuple(c) c, m, suffix = c
if delay is not None:
_params['delay'] = delay
else: else:
continue continue
suffix = suffix or c suffix = suffix or c
if 'addr' in _params: if 'addr' in params:
suffix += f"_{_params['addr']}" if suffix else str(_params['addr']) suffix += f"_{params['addr']}" if suffix else str(params['addr'])
if suffix: if suffix:
_dev = f'{dev}_{suffix}' _dev = f'{dev}_{suffix}'
else: else:
_dev = dev _dev = dev
params = params.copy()
if i > 0: if i > 0:
_params['i2c_par'] = i params['i2c_par'] = i
ret.append({ ret.append({
'id_suffix': _dev, 'id_suffix': _dev,
'device_class': c, 'device_class': c,
'params': _params, 'params': params,
'unit_of_measurement': m, 'unit_of_measurement': m,
}) })
req.append(_params) req.append(params)
return req, ret return req, ret
@@ -86,55 +79,55 @@ class Request:
i2c_classes = { i2c_classes = {
'htu21d': [ 'htu21d': [
DeviceType(SensorDeviceClass.HUMIDITY, PERCENTAGE, None), DeviceType(DEVICE_CLASS_HUMIDITY, PERCENTAGE, None),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None),
], ],
'sht31': [ 'sht31': [
DeviceType(SensorDeviceClass.HUMIDITY, PERCENTAGE, None, delay=1.5), DeviceType(DEVICE_CLASS_HUMIDITY, PERCENTAGE, None),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None),
], ],
'max44009': [ 'max44009': [
DeviceType(SensorDeviceClass.ILLUMINANCE, LIGHT_LUX, None) DeviceType(DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX, None)
], ],
'bh1750': [ 'bh1750': [
DeviceType(SensorDeviceClass.ILLUMINANCE, LIGHT_LUX, None) DeviceType(DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX, None)
], ],
'tsl2591': [ 'tsl2591': [
DeviceType(SensorDeviceClass.ILLUMINANCE, LIGHT_LUX, None) DeviceType(DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX, None)
], ],
'bmp180': [ 'bmp180': [
DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None), DeviceType(DEVICE_CLASS_PRESSURE, PRESSURE_BAR, None),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None),
], ],
'bmx280': [ 'bmx280': [
DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None), DeviceType(DEVICE_CLASS_PRESSURE, PRESSURE_BAR, None),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None),
DeviceType(SensorDeviceClass.HUMIDITY, PERCENTAGE, None) DeviceType(DEVICE_CLASS_HUMIDITY, PERCENTAGE, None)
], ],
'dps368': [ 'dps368': [
DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None), DeviceType(DEVICE_CLASS_PRESSURE, PRESSURE_BAR, None),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None),
], ],
'mlx90614': [ 'mlx90614': [
Skip, Skip,
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, 'temp'), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, 'temp'),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, 'object'), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, 'object'),
], ],
'ptsensor': [ 'ptsensor': [
Skip, Skip,
Request(delay=3), # запрос на измерение Request(delay=1), # запрос на измерение
DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None), DeviceType(DEVICE_CLASS_PRESSURE, PRESSURE_BAR, None),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None),
], ],
'mcp9600': [ 'mcp9600': [
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), # термопара DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), # термопара
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), # сенсор встроенный в микросхему DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), # сенсор встроенный в микросхему
], ],
't67xx': [ 't67xx': [
DeviceType(SensorDeviceClass.CO2, CONCENTRATION_PARTS_PER_MILLION, None) DeviceType(DEVICE_CLASS_CO2, CONCENTRATION_PARTS_PER_MILLION, None)
], ],
'tmp117': [ 'tmp117': [
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None),
], ],
'ads1115': [ 'ads1115': [
DeviceType(None, None, 'ch0'), DeviceType(None, None, 'ch0'),

View File

@@ -1,9 +1,6 @@
"""Platform for light integration.""" """Platform for light integration."""
from __future__ import annotations
import asyncio import asyncio
import logging import logging
import typing
from datetime import timedelta, datetime from datetime import timedelta, datetime
from functools import partial from functools import partial
@@ -16,8 +13,8 @@ from homeassistant.components.light import (
SUPPORT_BRIGHTNESS, SUPPORT_BRIGHTNESS,
LightEntity, LightEntity,
SUPPORT_TRANSITION, SUPPORT_TRANSITION,
SUPPORT_COLOR, ColorMode, LightEntityFeature, SUPPORT_COLOR,
# SUPPORT_WHITE_VALUE SUPPORT_WHITE_VALUE
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@@ -118,7 +115,6 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
self._is_on = None self._is_on = None
self._brightness = None self._brightness = None
self._hs_color = None self._hs_color = None
self._rgb_color: tuple[int, int, int] | None = None
self._white_value = None self._white_value = None
self._task: asyncio.Task = None self._task: asyncio.Task = None
self._restore = None self._restore = None
@@ -131,7 +127,7 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
def max_values(self) -> list: def max_values(self) -> list:
if self._max_values is None: if self._max_values is None:
if self.is_ws: if self.is_ws:
self._max_values = [255] * 4 self._max_values = [255] * 3
else: else:
self._max_values = [ self._max_values = [
255 if isinstance(x, int) else 4095 for x in self.port 255 if isinstance(x, int) else 4095 for x in self.port
@@ -146,34 +142,10 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
def is_ws(self): def is_ws(self):
return self.customize.get(CONF_WS28XX) return self.customize.get(CONF_WS28XX)
@property
def supported_color_modes(self) -> set[ColorMode] | set[str] | None:
return {
ColorMode.BRIGHTNESS,
ColorMode.RGB if len(self.port) != 4 else ColorMode.RGBW
}
@property
def color_mode(self) -> ColorMode | str | None:
if len(self.port) == 4:
return ColorMode.RGBW
else:
return ColorMode.RGB
@property @property
def white_value(self): def white_value(self):
# if self.supported_features & SUPPORT_WHITE_VALUE: if self.supported_features & SUPPORT_WHITE_VALUE:
return float(self.get_attribute('white_value', 0)) return float(self.get_attribute('white_value', 0))
@property
def rgb_color(self) -> tuple[int, int, int] | None:
return self._rgb_color
@property
def rgbw_color(self) -> tuple[int, int, int, int] | None:
if self._white_value is not None and self._rgb_color is not None:
return (*self._rgb_color, self._white_value)
@property @property
def brightness(self): def brightness(self):
@@ -189,7 +161,12 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
@property @property
def supported_features(self): def supported_features(self):
return LightEntityFeature.TRANSITION return (
SUPPORT_BRIGHTNESS |
SUPPORT_TRANSITION |
SUPPORT_COLOR |
(SUPPORT_WHITE_VALUE if len(self.port) == 4 else 0)
)
def get_rgbw(self): def get_rgbw(self):
if not self.is_on: if not self.is_on:
@@ -247,7 +224,6 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
for item, value in kwargs.items(): for item, value in kwargs.items():
setattr(self, f'_{item}', value) setattr(self, f'_{item}', value)
_after = self.get_rgbw() _after = self.get_rgbw()
self._rgb_color = tuple(_after[:3])
if transition is None: if transition is None:
transition = self.smooth.total_seconds() transition = self.smooth.total_seconds()
ratio = self.calc_speed_ratio(_before, _after) ratio = self.calc_speed_ratio(_before, _after)

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.7b0" "version": "v1.1.0b1"
} }

View File

@@ -4,7 +4,9 @@ import voluptuous as vol
import struct import struct
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_SCHEMA, SensorEntity, SensorDeviceClass PLATFORM_SCHEMA as SENSOR_SCHEMA,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@@ -18,8 +20,7 @@ from homeassistant.const import (
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 CONF_KEY, TEMP, HUM, W1, W1BUS, CONF_CONV_TEMPLATE, CONF_HEX_TO_FLOAT, DOMAIN, CONF_CUSTOM, \ from .const import CONF_KEY, TEMP, HUM, W1, W1BUS, CONF_CONV_TEMPLATE, CONF_HEX_TO_FLOAT, DOMAIN, CONF_CUSTOM, CONF_SKIP
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
@@ -38,8 +39,8 @@ UNITS = {
HUM: '%' HUM: '%'
} }
CLASSES = { CLASSES = {
TEMP: SensorDeviceClass.TEMPERATURE, TEMP: DEVICE_CLASS_TEMPERATURE,
HUM: SensorDeviceClass.HUMIDITY HUM: DEVICE_CLASS_HUMIDITY
} }
# Validation of the user's configuration # Validation of the user's configuration
_ITEM = { _ITEM = {
@@ -92,7 +93,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
hub.skip_ports |= {port} hub.skip_ports |= {port}
continue continue
for data in cfg: for data in cfg:
hub.lg.debug(f'add sensor on port %s with data %s, constructor: %s', port, data, _constructors[tp]) hub.lg.debug(f'add sensor on port %s with data %s', port, data)
sensor = _constructors[tp]( sensor = _constructors[tp](
mega=hub, mega=hub,
port=port, port=port,
@@ -106,57 +107,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
async_add_devices(devices) async_add_devices(devices)
class FilterBadValues(MegaPushEntity, SensorEntity): class MegaI2C(MegaPushEntity):
def __init__(self, *args, **kwargs):
self._prev_value = None
super().__init__(*args, **kwargs)
def filter_value(self, value):
try:
if value \
in self.filter_values \
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._prev_value is not None
and self.filter_scale is not None
and (
abs(value - self._prev_value) / self._prev_value > self.filter_scale
)
):
if self.fill_na == 'last':
value = self._prev_value
else:
value = None
self._prev_value = value
return value
except Exception as exc:
lg.exception(f'while parsing value')
return None
@property
def filter_values(self):
return self.customize.get(CONF_FILTER_VALUES, self.mega.customize.get(CONF_FILTER_VALUES, []))
@property
def filter_scale(self):
return self.customize.get(CONF_FILTER_SCALE, self.mega.customize.get(CONF_FILTER_SCALE, None))
@property
def filter_low(self):
return self.customize.get(CONF_FILTER_LOW, self.mega.customize.get(CONF_FILTER_LOW, None))
@property
def filter_high(self):
return self.customize.get(CONF_FILTER_HIGH, self.mega.customize.get(CONF_FILTER_HIGH, None))
@property
def fill_na(self):
return self.customize.get(CONF_FILL_NA, 'last')
class MegaI2C(FilterBadValues):
def __init__( def __init__(
self, self,
@@ -173,64 +124,46 @@ class MegaI2C(FilterBadValues):
@property @property
def customize(self): def customize(self):
ret = super().customize return super().customize.get(self.id_suffix, {}) or {}
_old = ret.get(self.id_suffix)
if _old is not None:
ret = ret.copy()
ret.update(_old)
return ret
@property
def extra_state_attributes(self):
attrs = super().extra_state_attributes or {}
attrs.update({
'i2c_id': self.id_suffix,
})
return attrs
@property @property
def device_class(self): def device_class(self):
return self._device_class return self._device_class
@property @property
def native_unit_of_measurement(self): def unit_of_measurement(self):
return self._unit_of_measurement return self._unit_of_measurement
@property @property
def native_value(self): def state(self):
try: # self.lg.debug(f'get % all states: %', self._params, self.mega.values)
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:
ret = struct.unpack('!f', bytes.fromhex(ret))[0]
except:
self.lg.warning(f'could not convert {ret} form hex to float')
tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE))
try: try:
ret = float(ret) ret = struct.unpack('!f', bytes.fromhex('41973333'))[0]
if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass
ret = tmpl.async_render({'value': ret})
except: except:
ret = ret self.lg.warning(f'could not convert {ret} form hex to float')
ret = self.filter_value(ret) tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE))
if ret is not None: try:
return str(ret) ret = float(ret)
except Exception: if tmpl is not None and self.hass is not None:
lg.exception('while getting value') tmpl.hass = self.hass
return None ret = tmpl.async_render({'value': ret})
except:
ret = ret
return str(ret)
@property @property
def device_class(self): def device_class(self):
return self._device_class return self._device_class
class Mega1WSensor(FilterBadValues): class Mega1WSensor(MegaPushEntity):
def __init__( def __init__(
self, self,
unit_of_measurement=None, unit_of_measurement,
device_class=None, device_class,
key=None, key=None,
*args, *args,
**kwargs **kwargs
@@ -242,15 +175,14 @@ class Mega1WSensor(FilterBadValues):
:param patt: pattern to extract value, must have at least one group that will contain parsed value :param patt: pattern to extract value, must have at least one group that will contain parsed value
""" """
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.key = key
self._value = None self._value = None
self.key = key
self._device_class = device_class self._device_class = device_class
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
self.mega.sensors.append(self) self.mega.sensors.append(self)
self.prev_value = None
@property @property
def native_unit_of_measurement(self): def unit_of_measurement(self):
_u = self.customize.get(CONF_UNIT_OF_MEASUREMENT, None) _u = self.customize.get(CONF_UNIT_OF_MEASUREMENT, None)
if _u is None: if _u is None:
return self._unit_of_measurement return self._unit_of_measurement
@@ -266,7 +198,7 @@ class Mega1WSensor(FilterBadValues):
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(Mega1WSensor, self).unique_id
@property @property
def device_class(self): def device_class(self):
@@ -281,63 +213,48 @@ class Mega1WSensor(FilterBadValues):
return self._device_class return self._device_class
@property @property
def native_value(self): def state(self):
try: ret = None
ret = None if self.key:
if not hasattr(self, 'key'): try:
return None ret = self.mega.values.get(self.port, {})
if self.key: if isinstance(ret, dict):
try: ret = ret.get('value', {})
ret = self.mega.values.get(self.port, {})
if isinstance(ret, dict): if isinstance(ret, dict):
ret = ret.get('value', {}) ret = ret.get(self.key)
if isinstance(ret, dict):
ret = ret.get(self.key)
except:
self.lg.error(self.mega.values.get(self.port, {}).get('value', {}))
return
else:
ret = self.mega.values.get(self.port, {}).get('value')
if ret is None and self.fill_na == 'fill_na' and self.prev_value is not None:
ret = self.prev_value
elif ret is None and self.fill_na == 'fill_na' and self._state is not None:
ret = self._state.state
try:
ret = float(ret)
ret = str(ret)
except: except:
self.lg.debug(f'could not convert to float "{ret}"') self.lg.error(self.mega.values.get(self.port, {}).get('value', {}))
ret = self.prev_value return
if self.customize.get(CONF_HEX_TO_FLOAT): else:
try: ret = self.mega.values.get(self.port, {}).get('value')
ret = struct.unpack('!f', bytes.fromhex(ret))[0] if ret is None and self._state is not None:
except: ret = self._state.state
self.lg.warning(f'could not convert {ret} form hex to float') try:
tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE)) ret = float(ret)
ret = str(ret)
except:
ret = None
if self.customize.get(CONF_HEX_TO_FLOAT):
try: try:
ret = float(ret) ret = struct.unpack('!f', bytes.fromhex(ret))[0]
if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass
ret = tmpl.async_render({'value': ret})
except: except:
pass self.lg.warning(f'could not convert {ret} form hex to float')
ret = self.filter_value(ret) tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE))
self.prev_value = ret try:
if ret is not None: ret = float(ret)
return str(ret) if tmpl is not None and self.hass is not None:
except Exception: tmpl.hass = self.hass
lg.exception('while parsing state') ret = tmpl.async_render({'value': ret})
return None except:
pass
return str(ret)
@property @property
def name(self): def name(self):
n = super().name n = super().name
c = self.customize.get(CONF_NAME, {}) c = self.customize.get(CONF_NAME, {})
if isinstance(c, dict): if isinstance(c, dict):
try: c = c.get(self.key)
c = c.get(self.key)
except:
pass
return c or n return c or n

View File

@@ -50,10 +50,6 @@ class PriorityLock(asyncio.Lock):
finally: finally:
self.release() self.release()
@property
def _loop(self):
return asyncio.get_event_loop()
async def acquire(self, priority=0) -> bool: async def acquire(self, priority=0) -> bool:
"""Acquire a lock. """Acquire a lock.

View File

@@ -57,9 +57,7 @@
Информация об обновлениях приходит с некоторым интервалом, чтобы вручную проверить наличие обновлений Информация об обновлениях приходит с некоторым интервалом, чтобы вручную проверить наличие обновлений
нажмите три точки возле интеграции в меню HACS и нажмите `обновить информацию` нажмите три точки возле интеграции в меню HACS и нажмите `обновить информацию`
## Беты {: #beta } Чтобы включить возможность использования бета-версий, зайдите в HACS, найдите интеграцию MegaD, нажмите три точки,
Иногда я буду выпускать бета-версии, в стабильности которых пока не уверен и не готов раскатывать на всех, поэтому в целях тестирвоания
нужно принудительно включать поддержку бет, для этого зайдите в HACS, найдите интеграцию MegaD, нажмите три точки,
там кнопка "переустановить" или reinstall, дальше нужно нажать галку "показывать бета-версии" там кнопка "переустановить" или reinstall, дальше нужно нажать галку "показывать бета-версии"
## Зависимости {: #deps } ## Зависимости {: #deps }

View File

@@ -1,25 +1,6 @@
С помощью yaml-конфигурации можно кастомизировать ваши устройства. С помощью yaml-конфигурации можно кастомизировать ваши устройства.
## Основное ## Основное
!!! note "Альтернативная адресация"
Начиная с v1.1.0 большинство параметров объектов можно записывать в более простой и понятной форме:
```yaml
mega: # название интеграции
entities:
sensor.some_sensor: #entity_id как в интерфейсе HA
filter_low: 20
filter_high: 40
```
Рекомендуется пользоваться именно этим способом, тк он более логичный и простой.
Некоторые параметры по своей логике (влияют на entity_id) не могут быть записаны таким образом, среди них:
- domain
- skip
- name
Остальные параметры можно записывать используя новый entities
Конфиг записывается стандартным образом в файл `configuration.yaml`, начинаем с Конфиг записывается стандартным образом в файл `configuration.yaml`, начинаем с
указания названия интеграции: указания названия интеграции:
```yaml hl_lines="1" ```yaml hl_lines="1"
@@ -81,11 +62,8 @@ mega:
!!! note "" !!! note ""
- **smooth** (float, 0): программное плавное диммирование. Это поле отвечает за кол-во секунд, за которое яркость - **smooth** (float, 0): программное плавное диммирование. Это поле отвечает за кол-во секунд, за которое яркость
диммера набирает от 0 до 100% диммера набирает от 0 до 100%
- **range** (list[int, int], [0, 255]), *начиная с версии 1.1.0*: границы диммирования в абсолютных единицах 0..255. При диммировании 1% - **limits** (list[int, int], [0, 255]), *начиная с версии 1.1.0*: границы диммирования в абсолютных единицах контроллера. При диммировании 1%
будет равен левой границе, 100% - правой. будет равен левой границе, 100% - правой.
```yaml
range: [20, 200]
```
[Подробнее про плавное диммирование](smooth.md) [Подробнее про плавное диммирование](smooth.md)
### MegaD-16R-XT, MegaD-16PWM ### MegaD-16R-XT, MegaD-16PWM
Порты расширителей MegaD-16R-XT, MegaD-16PWM конфигурируются аналогично обычным реле и диммерам, но адресация порта Порты расширителей MegaD-16R-XT, MegaD-16PWM конфигурируются аналогично обычным реле и диммерам, но адресация порта
@@ -181,14 +159,6 @@ some_led2:
- **value_template** (str): шаблон для конвертации, например `{{(value|float)/100}}` - **value_template** (str): шаблон для конвертации, например `{{(value|float)/100}}`
- **device_class** (str): класс устройства, [список доступных](https://developers.home-assistant.io/docs/core/entity/sensor#available-device-classes) - **device_class** (str): класс устройства, [список доступных](https://developers.home-assistant.io/docs/core/entity/sensor#available-device-classes)
- **hex_to_float** (bool, false): если ваш датчик возвращает float запакованный в HEX, интеграция его распакует (перед применением темплейта) - **hex_to_float** (bool, false): если ваш датчик возвращает float запакованный в HEX, интеграция его распакует (перед применением темплейта)
- **filter_high** (float, none): верхняя граница значений, выше нее значения будут считаться ошибочными и отбрасываться. [Доступно так же глобальное значение](#filter_high)
- **filter_low** (float, none): нижняя граница значений, ниже нее значения будут считаться ошибочными и отбрасываться. [Доступно так же глобальное значение](#filter_low)
- **filter_values** ([float], none): список значений, которые считаются ошибочными. [Доступно так же глобальное значение](#filter_values)
- **filter_scale** (float, none): значение отклонения от текущего значения, которое будет считаться выбросом и отфильтруется, например если
установить 1, то это означает, что при росте показателя сенсора на 100% и больше или падении на 100% и больше, такое значение не будет отображаться.
[Доступно так же глобальное значение](#filter_scale)
- **fill_na** (str, last): чем заполнять пропуски, по-умолчанию last, что означает последнее значение, можно так же поставить none-тогда будут пропуски (разрывы на графике).
При этом есть так же особенности адресации, так для сенсора на одном порте с одним значением: При этом есть так же особенности адресации, так для сенсора на одном порте с одним значением:
```yaml ```yaml
36: 36:
@@ -213,10 +183,10 @@ some_led2:
# конфиг # конфиг
``` ```
#### i2c {: #i2c} #### i2c {: #i2c}
Для сенсоров i2c нужно так же указать id сенсора, который можно посмотреть в атрибутах объекта на [странице разработчика](https://my.home-assistant.io/redirect/developer_states/). Для сенсоров i2c адресация конфига такая:
```yaml ```yaml
36: 36:
htu21d_humidity: # i2c_id htu21d_humidity: # entity_id сенсора без приставки sensor., видно в интерфейсе HA
# конфиг # конфиг
``` ```
[Подробнее про i2c](i2c.md) [Подробнее про i2c](i2c.md)
@@ -234,6 +204,33 @@ mega:
{% if m in [0, 1] %}d{% endif %} {% if m in [0, 1] %}d{% endif %}
``` ```
### encoders
Настройки енкодеров. Енкодер состоит из нескольких портов, поэтому энкодеры настраиваются в отдельном блоке. Настроенный
енкодер создает в вашем интерфейсе несколько объектов:
- **light.<encoder_id>**: это rgb-свет, отвечает за светодиод енкодера
- **sensor.<encoder_id>**: это текущее значение енкодера [0..255]
Пример:
```yaml
mega:
megaid1:
encoders:
some_encoder_id: # придумывайте любой
int: 6
scl: 30
sync_to: light.some_light
```
!!! note ""
- **int** (integer): номер порта-прерывания. Если не указать, сервер не будет реагировать на изменения.
- **scl** (integer): номер scl-порта, используется для управления подсветкой и синхронизации состояния
- **sync_to** (str): entity_id, с которым синхронизирвоать состояние. Синхронизация работает только в
одну сторону: а именно, когда вы меняете яркость объекта в интерфейсе, соответсвующая настройка "пробрасывается" на
контроллер. Обычно этого достаточно, тк управление самим светом лучше реализовать штатными средствами контроллера,
но в случае, если вы хотите так же управлять яркостью света с помощью енкодера+HA, то нужно использовать автоматизацию,
для вашего удобства есть шаблон, который можно установить по кнопке ниже:
## Параметры интеграции ## Параметры интеграции
### allow_hosts {: #allow_hosts } ### allow_hosts {: #allow_hosts }
Отвечает за список хостов, с которых интеграция "слушает" сообщения. По умолчанию, в этот список Отвечает за список хостов, с которых интеграция "слушает" сообщения. По умолчанию, в этот список
@@ -243,27 +240,5 @@ mega:
```yaml ```yaml
mega: mega:
allow_hosts: allow_hosts:
- 192.168.1.20 - 192.168.1.20
``` ```
### filter_high {: #filter_high}
Верхняя граница значений датчиков по-умолчанию, выше нее значения будут считаться ошибочными и отбрасываться
### filter_low {: #filter_low}
Нижняя граница значений датчиков по-умолчанию, ниже нее значения будут считаться ошибочными и отбрасываться
### filter_values {: #filter_values }
Список значений, которые считаются ошибочными, настройка по-умолчанию для всех датчиков. Удобно, если у вас много
однотипных датчиков
```yaml
mega:
filter_values: [-82, - 150]
```
### filter_scale {: #filter_scale }
Значение отклонения от текущего значения, которое будет считаться выбросом и отфильтруется, например если
установить 1, то это означает, что при росте показателя сенсора на 100% и больше или падении на 100% и больше, такое значение не будет отображаться.
```yaml
mega:
filter_scale: 1 # 100%
```

View File

@@ -22,7 +22,7 @@ nav:
- Конфигурация: - Конфигурация:
- В интерфейсе: settings.md - В интерфейсе: settings.md
- Настройка обратной связи: http.md - Настройка обратной связи: http.md
- Кастомизация: yaml.md - YAML-конфиг: yaml.md
- i2c: i2c.md - i2c: i2c.md
- Плавные переходы: smooth.md - Плавные переходы: smooth.md
- Автоматизация: - Автоматизация: