Compare commits

...

145 Commits

Author SHA1 Message Date
Викторов Андрей Германович
58288de1dd Bump version: 1.1.8b4 → 1.1.8b5 2023-10-15 13:37:31 +03:00
Викторов Андрей Германович
ec5aeab559 fix update extender 2023-10-15 13:37:24 +03:00
Викторов Андрей Германович
d0659b84dc Bump version: 1.1.8b3 → 1.1.8b4 2023-10-14 18:58:17 +03:00
Викторов Андрей Германович
3cd38a57e0 fix updater service 2023-10-14 18:57:47 +03:00
Викторов Андрей Германович
261c628909 Bump version: 1.1.8b2 → 1.1.8b3 2023-10-11 14:01:30 +03:00
Викторов Андрей Германович
2af9cfbeaf fix rgb brightness memory 2023-10-11 14:01:23 +03:00
Викторов Андрей Германович
60fad1f20f Bump version: 1.1.8b1 → 1.1.8b2 2023-10-11 11:26:57 +03:00
Викторов Андрей Германович
5c18e395da fix color order 2023-10-11 11:26:15 +03:00
Викторов Андрей Германович
082c647110 Bump version: 1.1.8b0 → 1.1.8b1 2023-10-10 23:01:09 +03:00
Викторов Андрей Германович
21bc277b78 fix rgb order 2023-10-10 22:58:25 +03:00
Викторов Андрей Германович
00e62ee83f Bump version: 1.1.7 → 1.1.8b0 2023-10-10 21:01:59 +03:00
Викторов Андрей Германович
b62406210c fix rgb color 2023-10-10 21:01:21 +03:00
Викторов Андрей Германович
c45653b0ba Bump version: 1.1.7b0 → 1.1.7 2023-10-09 14:58:26 +03:00
Викторов Андрей Германович
683727b3ec Bump version: 1.1.6 → 1.1.7b0 2023-10-09 14:57:50 +03:00
Викторов Андрей Германович
2300d2750a . 2023-10-09 14:57:46 +03:00
Викторов Андрей Германович
ac8603f219 fix encoding issue 2023-10-09 14:55:15 +03:00
Викторов Андрей Германович
fbc1f31023 Bump version: 1.1.6b1 → 1.1.6 2023-08-21 08:55:54 +03:00
Викторов Андрей Германович
cd5ab3b689 Bump version: 1.1.6b0 → 1.1.6b1 2023-08-14 20:44:24 +03:00
Викторов Андрей Германович
0138f5e323 . 2023-08-14 20:43:26 +03:00
Викторов Андрей Германович
0ab4f6623c Bump version: 1.1.5 → 1.1.6b0 2023-08-14 20:38:08 +03:00
Викторов Андрей Германович
5664e1e929 . 2023-08-14 20:38:06 +03:00
Викторов Андрей Германович
bcf108fc9c . 2023-08-14 20:37:35 +03:00
Викторов Андрей Германович
ca3f90374b Bump version: 1.1.501 → 1.1.502b0 2023-08-14 20:37:08 +03:00
Викторов Андрей Германович
d15cce8061 Bump version: 1.1.5 → 1.1.501 2023-08-14 20:36:40 +03:00
Викторов Андрей Германович
4e83d81004 fix #152 2023-08-14 20:35:59 +03:00
Викторов Андрей Германович
9ceb544c1a Bump version: 1.1.5b0 → 1.1.5 2023-08-03 16:13:30 +03:00
Викторов Андрей Германович
c8b3cb60de Bump version: 1.1.4 → 1.1.5b0 2023-08-03 16:13:14 +03:00
Викторов Андрей Германович
bd550442d4 fix #151 2023-08-03 16:12:35 +03:00
andvikt
8903628b36 fix #120 2023-06-13 18:09:01 +03:00
andvikt
e1a6637f28 fix #145 2023-06-13 17:47:19 +03:00
andvikt
a346cb3fd7 fix #138 2023-06-13 17:39:49 +03:00
andvikt
d6ef137e75 fix #127 2023-06-13 17:15:42 +03:00
andvikt
d7180c0477 Bump version: 1.1.4b0 → 1.1.4 2022-09-08 12:42:10 +03:00
andvikt
1ceb7e4766 Bump version: 1.1.3 → 1.1.4b0 2022-09-08 12:41:56 +03:00
andvikt
ef46ac2b2b fix 2022.9 2022-09-08 12:41:42 +03:00
andvikt
e44aef35fb Bump version: 1.1.3b0 → 1.1.3 2022-07-22 10:09:04 +03:00
andvikt
a860ba9822 Bump version: 1.1.2b0 → 1.1.3b0 2022-07-22 10:08:43 +03:00
andvikt
2463b270e5 fix asyncio.lock 2022-07-22 10:08:37 +03:00
andvikt
ce2969c1e7 Bump version: 1.1.1 → 1.1.2b0 2021-12-19 17:52:57 +03:00
andvikt
34056273f3 try to fix sht31 2021-12-19 17:52:47 +03:00
Викторов Андрей Германович
19313d3b35 Bump version: 1.1.1b1 → 1.1.1 2021-12-15 16:10:01 +03:00
Викторов Андрей Германович
cb14f9aa2a remove port in hub.binary_sesnors check when processing inbound message from controller 2021-12-15 16:08:06 +03:00
andvikt
b908068315 new config adressation 2021-12-01 18:49:37 +03:00
andvikt
c11de5c2b9 Bump version: 1.1.1b0 → 1.1.1b1 2021-11-07 12:38:18 +03:00
andvikt
679b53bbd3 new config adressation 2021-11-07 12:38:02 +03:00
Викторов Андрей Германович
2c2b3aab74 Bump version: 1.1.0 → 1.1.1b0 2021-11-03 10:24:29 +03:00
Викторов Андрей Германович
a77794dff7 fix sht31 updating issue
fix filters positive float
2021-11-03 10:24:21 +03:00
andvikt
ebb513b83d new config adressation 2021-10-29 21:14:50 +03:00
andvikt
f5519a595d Bump version: 1.1.0b0 → 1.1.0 2021-10-29 21:05:14 +03:00
andvikt
ee26759003 Bump version: 1.0.10b20 → 1.1.0b0 2021-10-29 21:04:30 +03:00
andvikt
8869ad9cee new config adressation 2021-10-29 20:14:18 +03:00
andvikt
27f1e05a3a new config adressation 2021-10-29 20:13:31 +03:00
andvikt
7fdc2fc9ff new config adressation 2021-10-29 20:12:59 +03:00
andvikt
28bf4393f0 Bump version: 1.0.10b19 → 1.0.10b20 2021-10-29 19:53:01 +03:00
andvikt
5ecb246eff fix brightness 2021-10-29 19:52:10 +03:00
Викторов Андрей Германович
3ee464a896 Bump version: 1.0.10b18 → 1.0.10b19 2021-10-29 15:26:28 +03:00
Викторов Андрей Германович
6368fd7cfc add fill_na 2021-10-29 15:26:21 +03:00
Викторов Андрей Германович
647baa9bb1 Bump version: 1.0.10b17 → 1.0.10b18 2021-10-29 14:56:34 +03:00
Викторов Андрей Германович
614525b315 fix customization i2c 2021-10-29 14:56:30 +03:00
Викторов Андрей Германович
a80b9b4293 Bump version: 1.0.10b16 → 1.0.10b17 2021-10-29 13:22:20 +03:00
Викторов Андрей Германович
f6ae5a9f04 add fill_na
add range
2021-10-29 13:22:14 +03:00
Викторов Андрей Германович
9eed9995fb Bump version: 1.0.10b15 → 1.0.10b16 2021-10-29 12:53:23 +03:00
Викторов Андрей Германович
40ff7cca2e add brightness range 2021-10-29 12:53:17 +03:00
Викторов Андрей Германович
86f58bbfd9 Bump version: 1.0.10b14 → 1.0.10b15 2021-10-29 12:18:29 +03:00
Викторов Андрей Германович
633d5e67bd Bump version: 1.0.10b13 → 1.0.10b14 2021-10-29 12:15:23 +03:00
Викторов Андрей Германович
79e3f33345 add sensor filters 2021-10-29 12:15:02 +03:00
Викторов Андрей Германович
74235a39ad Bump version: 1.0.10b12 → 1.0.10b13 2021-10-29 10:03:22 +03:00
Викторов Андрей Германович
61bcbbd103 add more debug log info 2021-10-29 10:03:18 +03:00
andvikt
6411e6e5f9 Bump version: 1.0.10b11 → 1.0.10b12 2021-10-29 09:13:52 +03:00
andvikt
2020a840a8 timeouts 2021-10-29 09:13:48 +03:00
andvikt
f6c2cd025e Bump version: 1.0.10b10 → 1.0.10b11 2021-10-28 20:14:33 +03:00
andvikt
ab599cd59d . 2021-10-28 20:14:30 +03:00
andvikt
a349cbd4a4 . 2021-10-28 20:13:49 +03:00
andvikt
bd1ba0379f Bump version: 1.0.10b9 → 1.0.10b10 2021-10-28 20:03:15 +03:00
andvikt
70016dd69d . 2021-10-28 20:03:09 +03:00
andvikt
5617e7afa5 Bump version: 1.0.10b9 → 1.0.11b0 2021-10-28 20:01:49 +03:00
andvikt
bcdec2d793 . 2021-10-28 20:01:18 +03:00
Викторов Андрей Германович
c4aae6b3ec Bump version: 1.0.10b8 → 1.0.10b9 2021-10-28 16:41:39 +03:00
Викторов Андрей Германович
58d243dcc3 новы способ адресации кастомизаций 2021-10-28 16:41:36 +03:00
Викторов Андрей Германович
3488d4593a Bump version: 1.0.10b7 → 1.0.10b8 2021-10-28 15:57:59 +03:00
Викторов Андрей Германович
76b8988b83 only bin sensors can update state in http mode 2021-10-28 15:57:55 +03:00
Викторов Андрей Германович
4145eebbdd Bump version: 1.0.10b6 → 1.0.10b7 2021-10-28 15:02:18 +03:00
Викторов Андрей Германович
d4dddc61fd add sensor filters 2021-10-28 15:02:11 +03:00
Викторов Андрей Германович
bc34c1a0b3 add sensor filters 2021-09-21 17:49:43 +03:00
Викторов Андрей Германович
2662b9c3bd Bump version: 1.0.10b5 → 1.0.10b6 2021-09-21 17:33:30 +03:00
Викторов Андрей Германович
8399bbd1de fix focs about i2c id 2021-09-21 17:33:22 +03:00
Викторов Андрей Германович
975b2e8a38 Bump version: 1.0.10b4 → 1.0.10b5 2021-09-21 14:51:36 +03:00
Викторов Андрей Германович
919f1ceb12 add sensor filters 2021-09-21 14:51:33 +03:00
Викторов Андрей Германович
2c58f8e67e add sensor filters 2021-09-21 14:41:32 +03:00
Викторов Андрей Германович
1c65c341b7 add sensor filters 2021-09-21 14:38:31 +03:00
Викторов Андрей Германович
2ef0ebf702 add sensor filters 2021-09-21 14:34:44 +03:00
Викторов Андрей Германович
b8ceec0386 Bump version: 1.0.10b3 → 1.0.10b4 2021-09-21 14:26:04 +03:00
Викторов Андрей Германович
496713cfec add sensor filters 2021-09-21 14:26:00 +03:00
andvikt
1512238763 Bump version: 1.0.10b2 → 1.0.10b3 2021-09-18 22:22:20 +03:00
andvikt
ce1987d0c0 Merge pull request #59 from andvikt/feature/adc328
Feature/adc328
2021-09-18 22:21:30 +03:00
andvikt
b4521a93bb Merge branch 'master' into feature/adc328 2021-09-18 22:21:10 +03:00
andvikt
9322f9cae1 Bump version: 1.0.10b1 → 1.0.10b2 2021-09-18 22:11:56 +03:00
andvikt
52ace90600 add adc support for 328 mega 2021-09-18 22:11:52 +03:00
andvikt
8738a936fe Bump version: 1.0.10b0 → 1.0.10b1 2021-09-18 22:10:50 +03:00
andvikt
2490e09748 add adc support for 328 mega 2021-09-18 22:10:47 +03:00
andvikt
e134a874fa add adc support for 328 mega 2021-09-18 22:08:52 +03:00
andvikt
6fc5391490 Update index.md 2021-09-15 14:09:21 +03:00
andvikt
d37fa11ab3 Merge pull request #58 from andvikt/fix/57
Fix/57
2021-09-15 14:02:52 +03:00
andvikt
1db5670cfd fix #57 2021-09-15 13:59:30 +03:00
andvikt
74b6e200a5 add bumb2version 2021-09-15 13:58:22 +03:00
andvikt
3737bf91a3 Bump version: 1.0.9 → 1.0.10b0 2021-09-15 13:56:37 +03:00
andvikt
747b8e6c36 add bumb2version 2021-09-15 13:56:33 +03:00
andvikt
fec5cef05c add bumb2version 2021-09-15 13:54:34 +03:00
Викторов Андрей Германович
1ec581b08b fix #54 2021-08-26 11:49:26 +03:00
Викторов Андрей Германович
c62a5a2d92 fix formating 2021-08-26 11:16:57 +03:00
Викторов Андрей Германович
b6349eece8 fix formating 2021-08-26 08:40:35 +03:00
Andrey
4300676c23 Merge remote-tracking branch 'origin/master' 2021-08-25 18:39:30 +03:00
Andrey
a78a87662b fix #54 2021-08-25 18:39:15 +03:00
andvikt
530daea77c Update index.md 2021-08-15 16:23:08 +03:00
andvikt
0a72dd0f24 Update mkdocs.yml 2021-08-03 13:26:10 +03:00
andvikt
4ccc0618ce Update index.md 2021-08-03 12:17:25 +03:00
Andrey
eadfc5dce0 add enhance type issue 2021-08-03 12:12:43 +03:00
Andrey
eb7926057f Merge remote-tracking branch 'origin/master' 2021-08-03 12:10:56 +03:00
Andrey
c635cc9b36 add enhance type issue 2021-08-03 12:10:45 +03:00
andvikt
bb73c35f2e Update bug-report.md 2021-08-03 12:06:31 +03:00
andvikt
11c8eba6cb Update main.yml 2021-08-03 11:56:28 +03:00
Andrey
c1029d146c add mkdocs.yml 2021-08-03 11:51:56 +03:00
andvikt
693e2e9901 Update main.yml 2021-08-03 11:50:22 +03:00
andvikt
5795c943f5 Update index.md 2021-08-03 11:48:56 +03:00
andvikt
641531f8dc add mkdocs action 2021-08-03 11:34:07 +03:00
Andrey
7b953d06d5 add new docs 2021-08-03 11:27:19 +03:00
Andrey
912440c079 add new docs 2021-08-03 11:23:53 +03:00
Andrey
54f1ebeda3 fix some issues with brightness after restart 2021-07-29 21:30:41 +03:00
Andrey
87492be38f fix dimmer_scale for PCA9685 2021-07-25 08:36:12 +03:00
Andrey
0fab2384b0 fix patt 2021-04-21 10:08:28 +03:00
Andrey
70a2e5bbb0 fix patt 2021-04-20 17:00:31 +03:00
Andrey
83c843d722 fix validation error 2021-04-20 09:23:36 +03:00
Andrey
af684a6c00 fix validation error 2021-04-20 09:19:37 +03:00
Andrey
d94efbe1a7 add ADS1115
add DEVICE_CLASS_CO2
2021-04-20 09:11:00 +03:00
Andrey
eaa46a99ae fix fw pattern
add dps368 support
fix docs url
2021-04-20 07:37:39 +03:00
Andrey
e65598fe63 fix int mega id 2021-04-20 07:35:59 +03:00
Andrey
e12ab45c04 fix int mega id 2021-04-20 07:35:06 +03:00
Andrey
7fb72be646 add skip sensors 2021-04-19 22:03:30 +03:00
Andrey
f9f97a91b6 add timeouts 2021-04-19 21:46:12 +03:00
Andrey
ad1210d5cc fix some timeouts 2021-03-29 21:43:25 +03:00
Andrey Viktorov
687e80f729 fix empty config while first setup 2021-03-29 19:54:48 +03:00
Andrey Viktorov
7d777c9e82 fix sensors 2021-03-24 18:14:15 +03:00
Andrey Viktorov
c9f0e85f6a edit readme 2021-03-24 17:04:37 +03:00
andvikt
e75f8b91ef Update sensor.py 2021-03-24 12:40:39 +03:00
andvikt
bf15d4f3f9 Update hub.py 2021-03-24 10:57:42 +03:00
29 changed files with 1632 additions and 590 deletions

21
.bumpversion.cfg Normal file
View File

@@ -0,0 +1,21 @@
[bumpversion]
current_version = 1.1.8b5
parse = (?P<major>\d+)(\.(?P<minor>\d+))(\.(?P<patch>\d+))(?P<release>[bf]*)(?P<build>\d*)
commit = True
tag = True
serialize =
{major}.{minor}.{patch}{release}{build}
{major}.{minor}.{patch}
[bumpversion:part:release]
optional_value = f
values =
b
f
[bumpversion:part:build]
first_value = 0
[bumpversion:file:custom_components/mega/manifest.json]
search = "version": "v{current_version}"
replace = "version": "v{new_version}"

View File

@@ -1,6 +1,6 @@
--- ---
name: Bug report name: Проблема
about: Create a report to help us improve about: Обнаружен баг/проблема
title: '' title: ''
labels: '' labels: ''
assignees: '' assignees: ''
@@ -24,4 +24,4 @@ megad firmware version:
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**LOG** **LOG**
Просьба прикладывать детальный лог, подробная инструкция как включать отладку по [ссылке](https://github.com/andvikt/mega_hacs/wiki/Отладка) Просьба прикладывать детальный лог, подробная инструкция как включать отладку по [ссылке](https://andvikt.github.io/mega_hacs/debug/)

9
.github/ISSUE_TEMPLATE/enhance.md vendored Normal file
View File

@@ -0,0 +1,9 @@
---
name: Улучшение
about: Идея на улучшение интеграции
title: ''
labels: 'enhancement'
assignees: ''
---
**Описание**

17
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Publish docs via GitHub Pages
on:
push:
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
# Or use mhausenblas/mkdocs-deploy-gh-pages@nomaterial to build without the mkdocs-material theme
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

18
.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
*sync-conflict*
*.db
*.log*
*.yaml
**/__pycache__
test_*
temp_*
tr.py
experiment_*
example*
.HA_VERSION
.idea
.storage
site
.DS_Store
.venv
pyproject.toml
poetry.lock

View File

@@ -6,17 +6,52 @@ 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_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, CONF_DEVICE_CLASS, CONF_PORT CONF_DOMAIN,
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 DOMAIN, CONF_INVERT, CONF_RELOAD, PLATFORMS, CONF_PORTS, CONF_CUSTOM, CONF_SKIP, CONF_PORT_TO_SCAN, \ from .const import (
CONF_MQTT_INPUTS, CONF_HTTP, CONF_RESPONSE_TEMPLATE, CONF_ACTION, CONF_GET_VALUE, CONF_ALLOW_HOSTS, \ DOMAIN,
CONF_CONV_TEMPLATE, CONF_ALL, CONF_FORCE_D, CONF_DEF_RESPONSE, CONF_FORCE_I2C_SCAN, CONF_HEX_TO_FLOAT, \ CONF_INVERT,
RGB_COMBINATIONS, CONF_WS28XX, CONF_ORDER, CONF_SMOOTH, CONF_LED, CONF_WHITE_SEP, CONF_CHIP, CONF_RANGE PLATFORMS,
CONF_PORTS,
CONF_CUSTOM,
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
@@ -25,55 +60,62 @@ _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.Required(CONF_PORTS): vol.Any( vol.ExactSequence([_port_n, _port_n, _port_n]),
vol.ExactSequence([_port_n, _port_n, _port_n]), vol.ExactSequence([_port_n, _port_n, _port_n, _port_n]),
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",
msg='ports must be [R, G, B] or [R, G, B, W] of integers 0..255' ),
), vol.Optional(CONF_NAME): str,
vol.Optional(CONF_NAME): str, vol.Optional(CONF_WHITE_SEP, default=True): bool,
vol.Optional(CONF_WHITE_SEP, default=True): bool, vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds,
vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds, },
}, {
{ vol.Required(CONF_PORT): int,
vol.Required(CONF_PORT): int, vol.Required(CONF_WS28XX): True,
vol.Required(CONF_WS28XX): True, vol.Optional(CONF_CHIP, default=100): int,
vol.Optional(CONF_CHIP, default=100): int, vol.Optional(CONF_ORDER, default="rgb"): vol.Any(
vol.Optional(CONF_ORDER, default='rgb'): vol.Any(*RGB_COMBINATIONS, msg=f'order must be one of {RGB_COMBINATIONS}'), *RGB_COMBINATIONS, msg=f"order must be one of {RGB_COMBINATIONS}"
vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds, ),
vol.Optional(CONF_NAME): str, vol.Optional(CONF_SMOOTH, default=1): cv.time_period_seconds,
}, vol.Optional(CONF_NAME): 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 vol.Optional(CONF_DOMAIN): vol.Any("light", "switch"),
}), vol.Optional(
vol.Optional(CONF_DOMAIN): vol.Any('light', 'switch'), CONF_UNIT_OF_MEASUREMENT,
vol.Optional(CONF_UNIT_OF_MEASUREMENT, description='единицы измерений, либо строка либо мепинг'): description="единицы измерений, либо строка либо мепинг",
vol.Any(str, { ): 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(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,
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): {
@@ -81,44 +123,64 @@ 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(
{
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.Required(str, description='id меги из веб-интерфейса'): { vol.Optional("entities"): {
vol.Optional(CONF_FORCE_D, description='Принудительно слать d после срабатывания входа', default=False): bool, vol.Optional(str): vol.Any(CUSTOMIZE_PORT, CUSTOMIZE_DS2413)
},
vol.Optional(vol.Any(str, int), description="id меги из веб-интерфейса"): {
vol.Optional( vol.Optional(
CONF_DEF_RESPONSE, CONF_FORCE_D,
description='Ответ по умолчанию', description="Принудительно слать d после срабатывания входа",
default=None default=False,
): 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.Any(int, extender), description='номер порта'): vol.Any( vol.Optional(
vol.Any(int, extender), description="номер порта"
): vol.Any(
CUSTOMIZE_PORT, CUSTOMIZE_PORT,
CUSTOMIZE_DS2413, CUSTOMIZE_DS2413,
), ),
} 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_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 = {}
@@ -132,29 +194,40 @@ 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, 'save', partial(_save_service, hass), schema=vol.Schema({ DOMAIN,
vol.Optional('mega_id'): str "save",
}) partial(_save_service, hass),
schema=vol.Schema({vol.Optional("mega_id"): str}),
) )
hass.services.async_register( hass.services.async_register(
DOMAIN, 'get_port', partial(_get_port, hass), schema=vol.Schema({ DOMAIN,
vol.Optional('mega_id'): str, "get_port",
vol.Optional('port'): vol.Any(int, [int]), partial(_get_port, hass),
}) schema=vol.Schema(
{
vol.Optional("mega_id"): str,
vol.Optional("port"): vol.Any(int, [int]),
}
),
) )
hass.services.async_register( hass.services.async_register(
DOMAIN, 'run_cmd', partial(_run_cmd, hass), schema=vol.Schema({ DOMAIN,
vol.Optional('port'): int, "run_cmd",
vol.Required('cmd'): str, partial(_run_cmd, hass),
vol.Optional('mega_id'): str, schema=vol.Schema(
}) {
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)
@@ -164,7 +237,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
@@ -183,9 +256,7 @@ 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( hass.config_entries.async_forward_entry_setup(entry, platform)
entry, platform
)
) )
await hub.updater.async_refresh() await hub.updater.async_refresh()
return True return True
@@ -207,11 +278,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 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)
@@ -221,23 +292,28 @@ 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 return True
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("Migrating from version %s to version %s", config_entry.version, ConfigFlow.VERSION) _LOGGER.debug(
"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
@@ -247,7 +323,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()
@@ -259,8 +335,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:
@@ -270,6 +346,7 @@ 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):
@@ -281,12 +358,13 @@ 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

@@ -67,6 +67,7 @@ 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 = 24 VERSION = 26
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,6 +45,13 @@ 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",
@@ -64,7 +71,7 @@ LUX = 'lux'
SINGLE_CLICK = 'single' SINGLE_CLICK = 'single'
DOUBLE_CLICK = 'double' DOUBLE_CLICK = 'double'
PATT_FW = re.compile(r'fw:\s(.+)\)') PATT_FW = re.compile(r'fw:\s(.+?)\)')
REMOVE_CONFIG = [ REMOVE_CONFIG = [
'extenders', 'extenders',

View File

@@ -8,11 +8,12 @@ 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 LONG_RELEASE, RELEASE, PRESS, SINGLE_CLICK, DOUBLE_CLICK, EVENT_BINARY, CONF_SMOOTH, CONF_RANGE
_events_on = False _events_on = False
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -54,7 +55,6 @@ 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,6 +85,7 @@ 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):
@@ -126,21 +127,23 @@ 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: if self.hass is None or self.entity_id 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): def device_info(self) -> DeviceInfo:
if isinstance(self.port, list): if isinstance(self.port, list):
pt_idx = self.id_suffix pt_idx = self.id_suffix
else: else:
@@ -149,26 +152,20 @@ class BaseMegaEntity(CoordinatorEntity, RestoreEntity):
pt_idx, _ = _pt.split('e') pt_idx, _ = _pt.split('e')
else: else:
pt_idx = _pt pt_idx = _pt
return { return DeviceInfo(
"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)
}, },
"config_entries": [ name=self.name,
self.config_entry, manufacturer='ab-log.ru',
], sw_version=self.mega.fw,
"name": f'{self._mega_id} port {pt_idx}' if not isinstance(self.port, list) else f'{self._mega_id} {pt_idx}', via_device=(DOMAIN, self._mega_id),
"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:
if self._lg is None: return _LOGGER
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:
@@ -296,40 +293,48 @@ class MegaOutPort(MegaPushEntity):
def max_dim(self): def max_dim(self):
if self.dimmer_scale == 1: if self.dimmer_scale == 1:
return 255 return 255
elif self.dimmer == 16: elif self.dimmer_scale == 16:
return 4095 return 4095
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:
return self._state.attributes.get("brightness") ret = safe_int(self._state.attributes.get("brightness"), def_on=self.max_dim, def_off=0, def_val=0)
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) val = safe_int(val, def_on=self.max_dim, def_off=0, def_val=0)
else: else:
val = 0 val = 0
if val == 0: if val == 0:
return self._brightness ret = self._brightness
elif isinstance(val, (int, float)): elif isinstance(val, (int, float)):
return int(val / self.dimmer_scale) ret = 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)
return val ret = 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:
@@ -415,15 +420,38 @@ 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
self._last_called = time.time() self._last_called = time.time()
if not self.dimmer: if not self.dimmer:
transition = None transition = None
if not self.is_on: 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:
@@ -467,7 +495,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 = safe_int(self._brightness) self._restore_brightness = self._cal_reverse_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"
@@ -503,12 +531,12 @@ class MegaOutPort(MegaPushEntity):
def safe_int(v): def safe_int(v, def_on=1, def_off=0, def_val=None):
if v == 'ON': if v == 'ON':
return 1 return def_on
elif v == 'OFF': elif v == 'OFF':
return 0 return def_off
try: try:
return int(v) return int(v)
except (ValueError, TypeError): except (ValueError, TypeError):
return None return def_val

View File

@@ -117,7 +117,7 @@ class MegaView(HomeAssistantView):
_data = data.copy() _data = data.copy()
if e.startswith('ext'): if e.startswith('ext'):
idx = e[3:] idx = e[3:]
pt = f'{pt_orig}e{idx}' pt = f'{pt_orig}e{idx}' if not hub.new_naming else f'{int(pt_orig):02d}e{int(idx):02d}'
_data['pt_orig'] = pt_orig _data['pt_orig'] = pt_orig
_data['value'] = 'ON' if v == '1' else 'OFF' _data['value'] = 'ON' if v == '1' else 'OFF'
_data['m'] = 1 if _data[e] == '0' else 0 # имитация поведения обычного входа, чтобы события обрабатывались аналогично _data['m'] = 1 if _data[e] == '0' else 0 # имитация поведения обычного входа, чтобы события обрабатывались аналогично
@@ -135,6 +135,7 @@ 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)
@@ -147,7 +148,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: if hub.fake_response and 'value' not in data and 'pt' in data and port in hub.binary_sensors:
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,51 +9,50 @@ 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 ( from homeassistant.const import TEMP_CELSIUS, PERCENTAGE, LIGHT_LUX
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
from .config_parser import parse_config, DS2413, MCP230, MCP230_OUT, MCP230_IN, PCA9685 from .config_parser import parse_config, DS2413, MCP230, MCP230_OUT, MCP230_IN, PCA9685
from .const import ( from .const import (
TEMP, HUM, PRESS, TEMP,
LUX, PATT_SPLIT, DOMAIN, HUM,
CONF_HTTP, EVENT_BINARY_SENSOR, CONF_CUSTOM, CONF_FORCE_D, CONF_DEF_RESPONSE, PATT_FW, CONF_FORCE_I2C_SCAN, PRESS,
REMOVE_CONFIG LUX,
PATT_SPLIT,
DOMAIN,
CONF_HTTP,
EVENT_BINARY_SENSOR,
CONF_CUSTOM,
CONF_FORCE_D,
CONF_DEF_RESPONSE,
PATT_FW,
CONF_FORCE_I2C_SCAN,
REMOVE_CONFIG,
) )
from .entities import set_events_off, BaseMegaEntity, MegaOutPort, safe_int from .entities import set_events_off, BaseMegaEntity, MegaOutPort, safe_int
from .exceptions import CannotConnect, NoPort from .exceptions import CannotConnect, NoPort
from .i2c import parse_scan_page from .i2c import parse_scan_page
from .tools import make_ints, int_ignore, PriorityLock from .tools import make_ints, int_ignore, PriorityLock
TEMP_PATT = re.compile(r'temp:([01234567890\.]+)') TEMP_PATT = re.compile(r"temp:([01234567890\.]+)")
HUM_PATT = re.compile(r'hum:([01234567890\.]+)') HUM_PATT = re.compile(r"hum:([01234567890\.]+)")
PRESS_PATT = re.compile(r'press:([01234567890\.]+)') PRESS_PATT = re.compile(r"press:([01234567890\.]+)")
LUX_PATT = re.compile(r'lux:([01234567890\.]+)') LUX_PATT = re.compile(r"lux:([01234567890\.]+)")
PATTERNS = { PATTERNS = {TEMP: TEMP_PATT, HUM: HUM_PATT, PRESS: PRESS_PATT, LUX: LUX_PATT}
TEMP: TEMP_PATT, UNITS = {TEMP: TEMP_CELSIUS, HUM: PERCENTAGE, PRESS: "mmHg", LUX: LIGHT_LUX}
HUM: HUM_PATT,
PRESS: PRESS_PATT,
LUX: LUX_PATT
}
UNITS = {
TEMP: TEMP_CELSIUS,
HUM: PERCENTAGE,
PRESS: 'mmHg',
LUX: LIGHT_LUX
}
CLASSES = { CLASSES = {
TEMP: DEVICE_CLASS_TEMPERATURE, TEMP: SensorDeviceClass.TEMPERATURE,
HUM: DEVICE_CLASS_HUMIDITY, HUM: SensorDeviceClass.HUMIDITY,
PRESS: DEVICE_CLASS_PRESSURE, PRESS: SensorDeviceClass.PRESSURE,
LUX: DEVICE_CLASS_ILLUMINANCE LUX: SensorDeviceClass.ILLUMINANCE,
} }
I2C_DEVICE_TYPES = { I2C_DEVICE_TYPES = {
"2": LUX, # BH1750 "2": LUX, # BH1750
"3": LUX, # TSL2591 "3": LUX, # TSL2591
"7": LUX, # MAX44009 "7": LUX, # MAX44009
"70": LUX, # OPT3001 "70": LUX, # OPT3001
} }
@@ -62,44 +61,45 @@ class MegaD:
"""MegaD Hub""" """MegaD Hub"""
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
loop: asyncio.AbstractEventLoop, loop: asyncio.AbstractEventLoop,
host: str, host: str,
password: str, password: str,
lg: logging.Logger, lg: logging.Logger,
id: str, id: str,
config: ConfigEntry = None, config: ConfigEntry = None,
mqtt_id: str = None, mqtt_id: str = None,
scan_interval=60, scan_interval=60,
port_to_scan=0, port_to_scan=0,
nports=38, nports=38,
update_all: bool=True, update_all: bool = True,
poll_outs: bool=False, poll_outs: bool = False,
fake_response: bool=True, fake_response: bool = True,
force_d: bool=None, force_d: bool = None,
allow_hosts: str=None, allow_hosts: str = None,
protected=True, protected=True,
restore_on_restart=False, restore_on_restart=False,
extenders=None, extenders=None,
ext_in=None, ext_in=None,
ext_acts=None, ext_acts=None,
i2c_sensors=None, i2c_sensors=None,
new_naming=False, new_naming=False,
update_time=False, update_time=False,
smooth: list=None, smooth: list = None,
**kwargs, **kwargs,
): ):
"""Initialize.""" """Initialize."""
self.skip_ports = set()
if config is not None: if config is not None:
lg.debug(f'load config: %s', config.data) lg.debug(f"load config: %s", config.data)
self.config = config self.config = config
self.http = hass.data.get(DOMAIN, {}).get(CONF_HTTP) self.http = hass.data.get(DOMAIN, {}).get(CONF_HTTP)
if not self.http is None: if not self.http is None:
self.http.allowed_hosts |= {host} self.http.allowed_hosts |= {host}
self.http.hubs[host] = self self.http.hubs[host] = self
if len(self.http.hubs) == 1: if len(self.http.hubs) == 1:
self.http.hubs['__def'] = self self.http.hubs["__def"] = self
if mqtt_id: if mqtt_id:
self.http.hubs[mqtt_id] = self self.http.hubs[mqtt_id] = self
self.smooth = smooth or [] self.smooth = smooth or []
@@ -133,7 +133,9 @@ class MegaD:
self.sensors = [] self.sensors = []
self.port_to_scan = port_to_scan self.port_to_scan = port_to_scan
self.last_update = datetime.now() self.last_update = datetime.now()
self._callbacks: typing.DefaultDict[int, typing.List[typing.Callable[[dict], typing.Coroutine]]] = defaultdict(list) self._callbacks: typing.DefaultDict[
int, typing.List[typing.Callable[[dict], typing.Coroutine]]
] = defaultdict(list)
self._loop = loop self._loop = loop
self._customize = None self._customize = None
self.values = {} self.values = {}
@@ -143,10 +145,12 @@ class MegaD:
self.lg, self.lg,
name="megad", name="megad",
update_method=self.poll, update_method=self.poll,
update_interval=timedelta(seconds=self.poll_interval) if self.poll_interval else None, update_interval=timedelta(seconds=self.poll_interval)
if self.poll_interval
else None,
) )
self.updaters = [] self.updaters = []
self.fw = '' self.fw = ""
self.notifiers = defaultdict(asyncio.Condition) self.notifiers = defaultdict(asyncio.Condition)
if not mqtt_id: if not mqtt_id:
_id = host.split(".")[-1] _id = host.split(".")[-1]
@@ -157,13 +161,13 @@ class MegaD:
if force_d is not None: if force_d is not None:
self.customize[CONF_FORCE_D] = force_d self.customize[CONF_FORCE_D] = force_d
try: try:
if allow_hosts is not None: 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 = []
async def start(self): async def start(self):
pass pass
@@ -182,11 +186,14 @@ class MegaD:
self.lg.debug(self.sensors) self.lg.debug(self.sensors)
ports = [] ports = []
for x in self.sensors: for x in self.sensors:
if only_list and x.http_cmd != 'list': if only_list and x.http_cmd != "list":
continue continue
if x.port in ports: if x.port in ports:
continue continue
await self.get_port(x.port, force_http=True, http_cmd=x.http_cmd) try:
await self.get_port(x.port, force_http=True, http_cmd=x.http_cmd)
except asyncio.TimeoutError:
continue
ports.append(x.port) ports.append(x.port)
@property @property
@@ -207,22 +214,24 @@ class MegaD:
@property @property
def is_online(self): def is_online(self):
return (datetime.now() - self.last_update).total_seconds() < (self.poll_interval + 10) return (datetime.now() - self.last_update).total_seconds() < (
self.poll_interval + 10
)
def _warn_offline(self): def _warn_offline(self):
if self.online: if self.online:
self.lg.warning('mega is offline') self.lg.warning("mega is offline")
self.hass.states.async_set( self.hass.states.async_set(
f'mega.{self.id}', f"mega.{self.id}",
'offline', "offline",
) )
self.online = False self.online = False
def _notify_online(self): def _notify_online(self):
if not self.online: if not self.online:
self.hass.states.async_set( self.hass.states.async_set(
f'mega.{self.id}', f"mega.{self.id}",
'online', "online",
) )
self.online = True self.online = True
@@ -232,19 +241,19 @@ class MegaD:
:return: :return:
""" """
for x in self.ds2413_ports: for x in self.ds2413_ports:
self.lg.debug(f'poll ds2413 for %s', x) self.lg.debug(f"poll ds2413 for %s", x)
await self.get_port( try:
port=x, await self.get_port(
force_http=True, port=x, force_http=True, http_cmd="list", conv=False
http_cmd='list', )
conv=False except asyncio.TimeoutError:
) continue
async def poll(self): async def poll(self):
""" """
Polling ports Polling ports
""" """
self.lg.debug('poll') self.lg.debug("poll")
if self._update_time: if self._update_time:
await self.update_time() await self.update_time()
for x in self.i2c_sensors: for x in self.i2c_sensors:
@@ -253,10 +262,11 @@ class MegaD:
ret = await self._update_i2c(x) ret = await self._update_i2c(x)
if isinstance(ret, dict): if isinstance(ret, dict):
self.values.update(ret) self.values.update(ret)
for x in self.extenders: for x in self.extenders:
ret = await self._update_extender(x) ret = await self._update_extender(x)
if not isinstance(ret, dict): if not isinstance(ret, dict):
self.lg.warning(f'wrong updater result: {ret} from extender {x}') self.lg.warning(f"wrong updater result: {ret} from extender {x}")
continue continue
self.values.update(ret) self.values.update(ret)
@@ -267,14 +277,14 @@ class MegaD:
async def get_mqtt_id(self): async def get_mqtt_id(self):
async with aiohttp.request( async with aiohttp.request(
'get', f'http://{self.host}/{self.sec}/?cf=2' "get", f"http://{self.host}/{self.sec}/?cf=2"
) as req: ) as req:
data = await req.text() data = await req.text(encoding="iso-8859-5")
data = BeautifulSoup(data, features="lxml") data = BeautifulSoup(data, features="lxml")
_id = data.find(attrs={'name': 'mdid'}) _id = data.find(attrs={"name": "mdid"})
if _id: if _id:
_id = _id['value'] _id = _id["value"]
return _id or 'megad/' + self.host.split('.')[-1] return _id or "megad/" + self.host.split(".")[-1]
async def get_fw(self): async def get_fw(self):
data = await self.request() data = await self.request()
@@ -284,74 +294,90 @@ class MegaD:
return await self.request(pt=port, cmd=cmd) return await self.request(pt=port, cmd=cmd)
async def request(self, priority=0, **kwargs): async def request(self, priority=0, **kwargs):
cmd = '&'.join([f'{k}={v}' for k, v in kwargs.items() if v is not None]) cmd = "&".join([f"{k}={v}" for k, v in kwargs.items() if v is not None])
url = f"http://{self.host}/{self.sec}" url = f"http://{self.host}/{self.sec}"
if cmd: if cmd:
url = f"{url}/?{cmd}" url = f"{url}/?{cmd}"
self.lg.debug('request: %s', url) self.lg.debug("request: %s", url)
async with self._http_lck(priority): async with self._http_lck(priority):
async with aiohttp.request("get", url=url) as req: for _ntry in range(3):
if req.status != 200: try:
self.lg.warning('%s returned %s (%s)', url, req.status, await req.text()) async with aiohttp.request(
return None "get", url=url, timeout=aiohttp.ClientTimeout(total=5)
else: ) as req:
ret = await req.text() if req.status != 200:
self.lg.debug('response %s', ret) self.lg.warning(
return ret "%s returned %s (%s)",
url,
req.status,
await req.text(encoding="iso-8859-5"),
)
return None
else:
ret = await req.text(encoding="iso-8859-5")
self.lg.debug("response %s", ret)
return ret
except asyncio.TimeoutError:
self.lg.warning(f"timeout while requesting {url}")
# raise
await asyncio.sleep(1)
raise asyncio.TimeoutError("after 3 tries")
async def save(self): async def save(self):
await self.send_command(cmd='s') await self.send_command(cmd="s")
def parse_response(self, ret, cmd='get'): def parse_response(self, ret, cmd="get"):
if ret is None: if ret is None:
raise NoPort() raise NoPort()
if 'busy' in ret: if "busy" in ret:
return None return None
if ':' in ret: if ":" in ret:
if ';' in ret: if ";" in ret:
ret = ret.split(';') ret = ret.split(";")
elif '/' in ret and not cmd == 'list': elif "/" in ret and not cmd == "list":
ret = ret.split('/') ret = ret.split("/")
else: else:
ret = [ret] ret = [ret]
ret = {'value': dict([ ret = {"value": dict([x.split(":") for x in ret if x.count(":") == 1])}
x.split(':') for x in ret if x.count(':') == 1 elif "ON" in ret:
])} ret = {"value": "ON"}
elif 'ON' in ret: elif "OFF" in ret:
ret = {'value': 'ON'} ret = {"value": "OFF"}
elif 'OFF' in ret:
ret = {'value': 'OFF'}
else: else:
ret = {'value': ret} ret = {"value": ret}
return ret return ret
async def get_port(self, port, force_http=False, http_cmd='get', conv=True): async def get_port(self, port, force_http=False, http_cmd="get", conv=True):
""" """
Запрос состояния порта. Состояние всегда возвращается в виде объекта, всегда сохраняется в центральное Запрос состояния порта. Состояние всегда возвращается в виде объекта, всегда сохраняется в центральное
хранилище values хранилище values
""" """
self.lg.debug(f'get port %s', port) self.lg.debug(f"get port %s", port)
if http_cmd == 'list' and conv: if http_cmd == "list" and conv:
await self.request(pt=port, cmd='conv') await self.request(pt=port, cmd="conv")
await asyncio.sleep(1) await asyncio.sleep(1)
ret = self.parse_response(await self.request(pt=port, cmd=http_cmd), cmd=http_cmd) ret = self.parse_response(
await self.request(pt=port, cmd=http_cmd), cmd=http_cmd
)
ntry = 0 ntry = 0
while http_cmd == 'list' and ret is None and ntry < 3: while http_cmd == "list" and ret is None and ntry < 3:
await asyncio.sleep(1) await asyncio.sleep(1)
ret = self.parse_response(await self.request(pt=port, cmd=http_cmd)) ret = self.parse_response(await self.request(pt=port, cmd=http_cmd))
ntry += 1 ntry += 1
self.lg.debug('parsed: %s', ret) self.lg.debug("parsed: %s", ret)
self.values[port] = ret self.values[port] = ret
return ret return ret
@property @property
def ports(self): def ports(self):
return {e.port for e in self.entities} return {e.port for e in self.entities}
async def get_all_ports(self, only_out=False, check_skip=False): async def get_all_ports(self, only_out=False, check_skip=False):
ret = await self.request(cmd='all') try:
for port, x in enumerate(ret.split(';')): ret = await self.request(cmd="all")
except asyncio.TimeoutError:
return
for port, x in enumerate(ret.split(";")):
if port in self.ds2413_ports: if port in self.ds2413_ports:
continue continue
if check_skip and not port in self.ports: if check_skip and not port in self.ports:
@@ -369,22 +395,20 @@ class MegaD:
def _process_msg(self, msg): def _process_msg(self, msg):
try: try:
d = msg.topic.split('/') d = msg.topic.split("/")
port = d[-1] port = d[-1]
except ValueError: except ValueError:
self.lg.warning('can not process %s', msg) self.lg.warning("can not process %s", msg)
return return
if port == 'cmd': if port == "cmd":
return return
try: try:
port = int_ignore(port) port = int_ignore(port)
except: except:
self.lg.warning('can not process %s', msg) self.lg.warning("can not process %s", msg)
return return
self.lg.debug( self.lg.debug("process incomming message: %s", msg)
'process incomming message: %s', msg
)
value = None value = None
try: try:
value = json.loads(msg.payload) value = json.loads(msg.payload)
@@ -395,13 +419,13 @@ class MegaD:
cb(value) cb(value)
if isinstance(value, dict): if isinstance(value, dict):
value = value.copy() value = value.copy()
value['mega_id'] = self.id value["mega_id"] = self.id
self.hass.bus.async_fire( self.hass.bus.async_fire(
EVENT_BINARY_SENSOR, EVENT_BINARY_SENSOR,
value, value,
) )
except Exception as exc: except Exception as exc:
self.lg.warning(f'could not parse json ({msg.payload}): {exc}') self.lg.warning(f"could not parse json ({msg.payload}): {exc}")
return return
finally: finally:
asyncio.run_coroutine_threadsafe(self._notify(port, value), self.loop) asyncio.run_coroutine_threadsafe(self._notify(port, value), self.loop)
@@ -409,14 +433,15 @@ 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 %s', port, callback f"subscribe %s",
port,
) )
self.http.callbacks[self.id][port].append(callback) self.http.callbacks[self.id][port].append(callback)
async def authenticate(self) -> bool: async def authenticate(self) -> bool:
"""Test if we can authenticate with the host.""" """Test if we can authenticate with the host."""
async with aiohttp.request("get", url=f"http://{self.host}/{self.sec}") as req: async with aiohttp.request("get", url=f"http://{self.host}/{self.sec}") as req:
if "Unauthorized" in await req.text(): if "Unauthorized" in await req.text(encoding="iso-8859-5"):
return False return False
else: else:
if req.status != 200: if req.status != 200:
@@ -424,21 +449,21 @@ class MegaD:
return True return True
async def get_port_page(self, port): async def get_port_page(self, port):
url = f'http://{self.host}/{self.sec}/?pt={port}' url = f"http://{self.host}/{self.sec}/?pt={port}"
self.lg.debug(f'get page for port {port} {url}') self.lg.debug(f"get page for port {port} {url}")
async with aiohttp.request('get', url) as req: async with aiohttp.request("get", url) as req:
return await req.text() return await req.text(encoding="iso-8859-5")
async def scan_port(self, port): async def scan_port(self, port):
data = await self.request(pt=port) data = await self.request(pt=port)
return parse_config(data) return parse_config(data)
async def scan_ports(self, nports=37): async def scan_ports(self, nports=37):
for x in range(0, nports+1): for x in range(0, nports + 1):
ret = await self.scan_port(x) ret = await self.scan_port(x)
if ret: if ret:
yield x, ret yield x, ret
self.nports = nports+1 self.nports = nports + 1
async def _update_extender(self, port): async def _update_extender(self, port):
""" """
@@ -446,10 +471,13 @@ class MegaD:
:param port: :param port:
:return: :return:
""" """
values = await self.request(pt=port, cmd='get') try:
values = await self.request(pt=port, cmd="get")
except asyncio.TimeoutError:
return
ret = {} ret = {}
for i, x in enumerate(values.split(';')): for i, x in enumerate(values.split(";")):
ret[f'{port}e{i}'] = x ret[f"{port}e{i}" if not self.new_naming else f"{port:02d}e{i:02d}"] = x
return ret return ret
async def _update_i2c(self, params): async def _update_i2c(self, params):
@@ -458,119 +486,161 @@ class MegaD:
:param params: параметры url :param params: параметры url
:return: :return:
""" """
pt = params.get("pt")
if pt in self.skip_ports:
return
if pt is not None:
pass
_params = tuple(params.items()) _params = tuple(params.items())
delay = None delay = None
if 'delay' in params: if "delay" in params:
delay = params.pop('delay') delay = params.pop("delay")
ret = { try:
_params: await self.request(**params) ret = {_params: await self.request(**params)}
} except asyncio.TimeoutError:
self.lg.debug('i2c response: %s', ret) return
self.lg.debug("i2c response: %s", ret)
if delay: if delay:
self.lg.debug('delay %s', delay) self.lg.debug("delay %s", delay)
await asyncio.sleep(delay) await asyncio.sleep(delay)
return ret return ret
async def get_config(self, nports=37): async def get_config(self, nports=37):
ret = defaultdict(lambda: defaultdict(list)) ret = defaultdict(lambda: defaultdict(list))
ret['mqtt_id'] = await self.get_mqtt_id() ret["mqtt_id"] = await self.get_mqtt_id()
ret['extenders'] = extenders = [] ret["extenders"] = extenders = []
ret['ext_in'] = ext_int = {} ret["ext_in"] = ext_int = {}
ret['ext_acts'] = ext_acts = {} ret["ext_acts"] = ext_acts = {}
ret['i2c_sensors'] = i2c_sensors = [] ret["i2c_sensors"] = i2c_sensors = []
ret['smooth'] = smooth = [] ret["smooth"] = smooth = []
async for port, cfg in self.scan_ports(nports): async for port, cfg in self.scan_ports(nports):
_cust = self.customize.get(port) _cust = self.customize.get(port)
if not isinstance(_cust, dict): if not isinstance(_cust, dict):
_cust = {} _cust = {}
if cfg.pty == "0": if cfg.pty == "0":
ret['binary_sensor'][port].append({}) ret["binary_sensor"][port].append({})
elif cfg.pty == "1" and (cfg.m in ['0', '1', '3'] or cfg.m is None): elif cfg.pty == "1" and (cfg.m in ["0", "1", "3"] or cfg.m is None):
if cfg.misc is not None: if cfg.misc is not None:
smooth.append(port) smooth.append(port)
ret['light'][port].append({'dimmer': cfg.m == '1', 'smooth': safe_int(cfg.misc)}) ret["light"][port].append(
{"dimmer": cfg.m == "1", "smooth": safe_int(cfg.misc)}
)
elif cfg == DS2413: elif cfg == DS2413:
# ds2413 # ds2413
_data = await self.get_port(port=port, force_http=True, http_cmd='list', conv=False) _data = await self.get_port(
data = _data.get('value', {}) port=port, force_http=True, http_cmd="list", conv=False
)
data = _data.get("value", {})
if not isinstance(data, dict): if not isinstance(data, dict):
self.lg.warning(f'can not add ds2413 on port {port}, it has wrong data: {_data}') self.lg.warning(
f"can not add ds2413 on port {port}, it has wrong data: {_data}"
)
continue continue
for addr, state in data.items(): for addr, state in data.items():
ret['light'][port].extend([ ret["light"][port].extend(
{"index": 0, "addr": addr, "id_suffix": f'{addr}_a', "http_cmd": 'ds2413'}, [
{"index": 1, "addr": addr, "id_suffix": f'{addr}_b', "http_cmd": 'ds2413'}, {
]) "index": 0,
"addr": addr,
"id_suffix": f"{addr}_a",
"http_cmd": "ds2413",
},
{
"index": 1,
"addr": addr,
"id_suffix": f"{addr}_b",
"http_cmd": "ds2413",
},
]
)
elif cfg == MCP230: elif cfg == MCP230:
extenders.append(port) extenders.append(port)
if cfg.inta: if cfg.inta:
ext_int[int_ignore(cfg.inta)] = port ext_int[int_ignore(cfg.inta)] = port
values = await self.request(pt=port, cmd='get') values = await self.request(pt=port, cmd="get")
values = values.split(';') values = values.split(";")
for n in range(len(values)): for n in range(len(values)):
ext_page = await self.request(pt=port, ext=n) ext_page = await self.request(pt=port, ext=n)
ext_cfg = parse_config(ext_page) ext_cfg = parse_config(ext_page)
pt = f'{port}e{n}' if not self.new_naming else f'{port:02}e{n:02}' pt = f"{port}e{n}" if not self.new_naming else f"{port:02d}e{n:02d}"
if ext_cfg.ety == '1': if ext_cfg.ety == "1":
ret['light'][pt].append({}) ret["light"][pt].append({})
elif ext_cfg.ety == '0': elif ext_cfg.ety == "0":
if ext_cfg.eact: if ext_cfg.eact:
ext_acts[pt] = ext_cfg.eact ext_acts[pt] = ext_cfg.eact
ret['binary_sensor'][pt].append({}) ret["binary_sensor"][pt].append({})
elif cfg == PCA9685: elif cfg == PCA9685:
extenders.append(port) extenders.append(port)
values = await self.request(pt=port, cmd='get') values = await self.request(pt=port, cmd="get")
values = values.split(';') values = values.split(";")
for n in range(len(values)): for n in range(len(values)):
pt = f'{port}e{n}' pt = f"{port}e{n}"
name = pt if not self.new_naming else f'{port:02}e{n:02}' name = pt if not self.new_naming else f"{port:02}e{n:02}"
ret['light'][pt].append({'dimmer': True, 'dimmer_scale': 16, 'name': f'{self.id}_{name}'}) ret["light"][pt].append(
if cfg.pty == '4': # and (cfg.gr == '0' or _cust.get(CONF_FORCE_I2C_SCAN)) {
"dimmer": True,
"dimmer_scale": 16,
"name": f"{self.id}_{name}",
}
)
if cfg.pty == "4": # and (cfg.gr == '0' or _cust.get(CONF_FORCE_I2C_SCAN))
# i2c в режиме ANY # i2c в режиме ANY
scan = cfg.src.find('a', text='I2C Scan') scan = cfg.src.find("a", text="I2C Scan")
self.lg.debug(f'find scan link: %s', scan) self.lg.debug(f"find scan link: %s", scan)
if scan is not None: if scan is not None:
page = await self.request(pt=port, cmd='scan') page = await self.request(pt=port, cmd="scan")
req, parsed = parse_scan_page(page) req, parsed = parse_scan_page(page)
self.lg.debug(f'scan results: %s', (req, parsed)) self.lg.debug(f"scan results: %s", (req, parsed))
ret['i2c'][port].extend(parsed) ret["i2c"][port].extend(parsed)
i2c_sensors.extend(req) i2c_sensors.extend(req)
elif cfg.pty == '4' and cfg.m == '2': elif cfg.pty == "4" and cfg.m == "2":
# scl исключаем из сканирования # scl исключаем из сканирования
continue continue
elif cfg.pty in ('3', '2', '4'): elif cfg.pty is None and nports < 30:
http_cmd = 'get' # вроде как это ADC на 328 меге
if cfg.d == '5' and cfg.pty == '3': ret["sensor"][port].append(dict())
elif cfg.pty in ("3", "2", "4"):
http_cmd = "get"
if cfg.d == "5" and cfg.pty == "3":
# 1-wire bus # 1-wire bus
values = await self.get_port(port, force_http=True, http_cmd='list') values = await self.get_port(port, force_http=True, http_cmd="list")
http_cmd = 'list' http_cmd = "list"
else: else:
values = await self.get_port(port, force_http=True) values = await self.get_port(port, force_http=True)
if values is None or (isinstance(values, dict) and str(values.get('value')) in ('', 'None')): if values is None or (
values = await self.get_port(port, force_http=True, http_cmd='list') isinstance(values, dict)
http_cmd = 'list' and str(values.get("value")) in ("", "None")
self.lg.debug(f'values: %s', values) ):
values = await self.get_port(
port, force_http=True, http_cmd="list"
)
http_cmd = "list"
self.lg.debug(f"values: %s", values)
if values is None: if values is None:
self.lg.warning(f'port {port} is of type sensor but response is None, skipping it') self.lg.warning(
f"port {port} is of type sensor but response is None, skipping it"
)
continue continue
if isinstance(values, dict) and 'value' in values: if isinstance(values, dict) and "value" in values:
values = values['value'] values = values["value"]
if isinstance(values, str) and TEMP_PATT.search(values): if isinstance(values, str) and TEMP_PATT.search(values):
values = {TEMP: values} values = {TEMP: values}
elif not isinstance(values, dict): elif not isinstance(values, dict):
if cfg.pty == '4' and cfg.d in I2C_DEVICE_TYPES: if cfg.pty == "4" and cfg.d in I2C_DEVICE_TYPES:
values = {I2C_DEVICE_TYPES.get(cfg.m): values} values = {I2C_DEVICE_TYPES.get(cfg.m): values}
else: else:
values = {None: values} values = {None: values}
for key in values: for key in values:
self.lg.debug(f'add sensor {key}') self.lg.debug(f"add sensor {key}")
ret['sensor'][port].append(dict( ret["sensor"][port].append(
key=key, dict(
unit_of_measurement=UNITS.get(key, UNITS[TEMP]), key=key,
device_class=CLASSES.get(key, CLASSES[TEMP]), unit_of_measurement=UNITS.get(key, UNITS[TEMP]),
id_suffix=key, device_class=CLASSES.get(key, CLASSES[TEMP]),
http_cmd=http_cmd, id_suffix=key,
)) http_cmd=http_cmd,
)
)
return ret return ret
async def restore_states(self): async def restore_states(self):
@@ -582,10 +652,7 @@ class MegaD:
await x.async_turn_off() await x.async_turn_off()
async def update_time(self): async def update_time(self):
await self.request( await self.request(cf=7, stime=datetime.now().strftime("%H:%M:%S"))
cf=7,
stime=datetime.now().strftime('%H:%M:%S')
)
async def reload(self, reload_entry=True): async def reload(self, reload_entry=True):
new = await self.get_config(nports=self.nports) new = await self.get_config(nports=self.nports)
@@ -593,14 +660,14 @@ class MegaD:
for x in REMOVE_CONFIG: for x in REMOVE_CONFIG:
cfg.pop(x, None) cfg.pop(x, None)
cfg.update(new) cfg.update(new)
self.lg.debug(f'new config: %s', cfg) self.lg.debug(f"new config: %s", cfg)
self.config.data = cfg self.config.data = cfg
if reload_entry: if reload_entry:
await self.hass.config_entries.async_reload(self.config.entry_id) await self.hass.config_entries.async_reload(self.config.entry_id)
return cfg return cfg
def _wrap_port_smooth(self, from_, to_, time): def _wrap_port_smooth(self, from_, to_, time):
self.lg.debug('dim from %s to %s for %s seconds', from_, to_, time) self.lg.debug("dim from %s to %s for %s seconds", from_, to_, time)
if time <= 0: if time <= 0:
return return
beg = datetime.now() beg = datetime.now()
@@ -613,15 +680,15 @@ class MegaD:
yield val yield val
async def smooth_dim( async def smooth_dim(
self, self,
*config: typing.Tuple[typing.Any, int, int], *config: typing.Tuple[typing.Any, int, int],
time: float, time: float,
jitter: int = 50, jitter: int = 50,
ws=False, ws=False,
updater=None, updater=None,
can_smooth_hardware=False, can_smooth_hardware=False,
max_values=None, max_values=None,
chip=None, chip=None,
): ):
""" """
Плавное диммирование силами сервера, сразу нескольких портов (одной командой) Плавное диммирование силами сервера, сразу нескольких портов (одной командой)
@@ -665,7 +732,9 @@ class MegaD:
continue continue
if not ws: if not ws:
cmd = dict( cmd = dict(
cmd=';'.join([f'{pt}:{_next_val[i]}' for i, (pt, _, _) in enumerate(config)]) cmd=";".join(
[f"{pt}:{_next_val[i]}" for i, (pt, _, _) in enumerate(config)]
)
) )
await self.request(**cmd) await self.request(**cmd)
else: else:
@@ -673,7 +742,9 @@ class MegaD:
cmd = dict( cmd = dict(
pt=config[0][0], pt=config[0][0],
chip=chip, chip=chip,
ws=''.join([hex(x).split('x')[1].ljust(2, '0').upper() for x in _next_val]) ws="".join(
[hex(x).split("x")[1].rjust(2, "0").upper() for x in _next_val]
),
) )
await self.request(**cmd) await self.request(**cmd)

View File

@@ -1,11 +1,10 @@
from dataclasses import dataclass, field import typing
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,
PERCENTAGE, PERCENTAGE,
LIGHT_LUX, LIGHT_LUX,
TEMP_CELSIUS, TEMP_CELSIUS,
@@ -14,7 +13,15 @@ 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):
@@ -31,39 +38,40 @@ 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 = params.copy() _params['delay'] = c.delay
params['delay'] = c.delay req.append(_params)
req.append(params)
continue continue
elif isinstance(c, DeviceType): elif isinstance(c, DeviceType):
c, m, suffix = c c, m, suffix, delay = astuple(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
@@ -78,50 +86,66 @@ class Request:
i2c_classes = { i2c_classes = {
'htu21d': [ 'htu21d': [
DeviceType(DEVICE_CLASS_HUMIDITY, PERCENTAGE, None), DeviceType(SensorDeviceClass.HUMIDITY, PERCENTAGE, None),
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None),
], ],
'sht31': [ 'sht31': [
DeviceType(DEVICE_CLASS_HUMIDITY, PERCENTAGE, None), DeviceType(SensorDeviceClass.HUMIDITY, PERCENTAGE, None, delay=1.5),
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None),
], ],
'max44009': [ 'max44009': [
DeviceType(DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX, None) DeviceType(SensorDeviceClass.ILLUMINANCE, LIGHT_LUX, None)
], ],
'bh1750': [ 'bh1750': [
DeviceType(DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX, None) DeviceType(SensorDeviceClass.ILLUMINANCE, LIGHT_LUX, None)
], ],
'tsl2591': [ 'tsl2591': [
DeviceType(DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX, None) DeviceType(SensorDeviceClass.ILLUMINANCE, LIGHT_LUX, None)
], ],
'bmp180': [ 'bmp180': [
DeviceType(DEVICE_CLASS_PRESSURE, PRESSURE_BAR, None), DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None),
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None),
], ],
'bmx280': [ 'bmx280': [
DeviceType(DEVICE_CLASS_PRESSURE, PRESSURE_BAR, None), DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None),
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None),
DeviceType(DEVICE_CLASS_HUMIDITY, PERCENTAGE, None) DeviceType(SensorDeviceClass.HUMIDITY, PERCENTAGE, None)
],
'dps368': [
DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None),
DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None),
], ],
'mlx90614': [ 'mlx90614': [
Skip, Skip,
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, 'temp'), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, 'temp'),
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, 'object'), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, 'object'),
], ],
'ptsensor': [ 'ptsensor': [
Skip, Skip,
Request(delay=1), # запрос на измерение Request(delay=3), # запрос на измерение
DeviceType(DEVICE_CLASS_PRESSURE, PRESSURE_BAR, None), DeviceType(SensorDeviceClass.PRESSURE, PRESSURE_BAR, None),
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None),
], ],
'mcp9600': [ 'mcp9600': [
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), # термопара DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), # термопара
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), # сенсор встроенный в микросхему DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None), # сенсор встроенный в микросхему
], ],
't67xx': [ 't67xx': [
DeviceType(None, CONCENTRATION_PARTS_PER_MILLION, None) # для co2 нет класса в HA DeviceType(SensorDeviceClass.CO2, CONCENTRATION_PARTS_PER_MILLION, None)
], ],
'tmp117': [ 'tmp117': [
DeviceType(DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, None), DeviceType(SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None),
] ],
'ads1115': [
DeviceType(None, None, 'ch0'),
DeviceType(None, None, 'ch1'),
DeviceType(None, None, 'ch2'),
DeviceType(None, None, 'ch3'),
],
'ads1015': [
DeviceType(None, None, 'ch0'),
DeviceType(None, None, 'ch1'),
DeviceType(None, None, 'ch2'),
DeviceType(None, None, 'ch3'),
],
} }

View File

@@ -1,6 +1,9 @@
"""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
@@ -14,7 +17,9 @@ from homeassistant.components.light import (
LightEntity, LightEntity,
SUPPORT_TRANSITION, SUPPORT_TRANSITION,
SUPPORT_COLOR, SUPPORT_COLOR,
SUPPORT_WHITE_VALUE ColorMode,
LightEntityFeature,
# SUPPORT_WHITE_VALUE
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@@ -33,7 +38,15 @@ from .const import (
CONF_SWITCH, CONF_SWITCH,
DOMAIN, DOMAIN,
CONF_CUSTOM, CONF_CUSTOM,
CONF_SKIP, CONF_LED, CONF_WS28XX, CONF_PORTS, CONF_WHITE_SEP, CONF_SMOOTH, CONF_ORDER, CONF_CHIP, RGB, CONF_SKIP,
CONF_LED,
CONF_WS28XX,
CONF_PORTS,
CONF_WHITE_SEP,
CONF_SMOOTH,
CONF_ORDER,
CONF_CHIP,
RGB,
) )
from .tools import int_ignore, map_reorder_rgb from .tools import int_ignore, map_reorder_rgb
@@ -61,13 +74,17 @@ PLATFORM_SCHEMA = LIGHT_SCHEMA.extend(
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('mega integration does not support yaml for lights, please use UI configuration') lg.warning(
"mega integration does not support yaml for lights, please use UI configuration"
)
return True return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_devices): async def async_setup_entry(
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, {})
skip = [] skip = []
@@ -75,23 +92,29 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
for entity_id, conf in customize[CONF_LED].items(): for entity_id, conf in customize[CONF_LED].items():
ports = conf.get(CONF_PORTS) or [conf.get(CONF_PORT)] ports = conf.get(CONF_PORTS) or [conf.get(CONF_PORT)]
skip.extend(ports) skip.extend(ports)
devices.append(MegaRGBW( devices.append(
mega=hub, MegaRGBW(
port=ports, mega=hub,
name=entity_id, port=ports,
customize=conf, name=entity_id,
id_suffix=entity_id, customize=conf,
config_entry=config_entry id_suffix=entity_id,
)) config_entry=config_entry,
for port, cfg in config_entry.data.get('light', {}).items(): )
)
for port, cfg in config_entry.data.get("light", {}).items():
port = int_ignore(port) port = int_ignore(port)
c = customize.get(port, {}) c = customize.get(port, {})
if c.get(CONF_SKIP, False) or port in skip or c.get(CONF_DOMAIN, 'light') != 'light': if (
c.get(CONF_SKIP, False)
or port in skip
or c.get(CONF_DOMAIN, "light") != "light"
):
continue continue
for data in cfg: for data in cfg:
hub.lg.debug(f'add light on port %s with data %s', port, data) hub.lg.debug(f"add light on port %s with data %s", port, data)
light = MegaLight(mega=hub, port=port, config_entry=config_entry, **data) light = MegaLight(mega=hub, port=port, config_entry=config_entry, **data)
if '<' in light.name: if "<" in light.name:
continue continue
devices.append(light) devices.append(light)
@@ -99,27 +122,25 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
class MegaLight(MegaOutPort, LightEntity): class MegaLight(MegaOutPort, LightEntity):
@property @property
def supported_features(self): def supported_features(self):
return ( return (SUPPORT_BRIGHTNESS if self.dimmer else 0) | (
(SUPPORT_BRIGHTNESS if self.dimmer else 0) | SUPPORT_TRANSITION if self.dimmer else 0
(SUPPORT_TRANSITION if self.dimmer else 0)
) )
class MegaRGBW(LightEntity, BaseMegaEntity): class MegaRGBW(LightEntity, BaseMegaEntity):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
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
self.smooth: timedelta = self.customize[CONF_SMOOTH] self.smooth: timedelta = self.customize[CONF_SMOOTH]
self._color_order = self.customize.get(CONF_ORDER, 'rgb') self._color_order = self.customize.get(CONF_ORDER, "rgb")
self._last_called: float = 0 self._last_called: float = 0
self._max_values = None self._max_values = None
@@ -127,7 +148,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] * 3 self._max_values = [255] * 4
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
@@ -142,37 +163,55 @@ 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):
return float(self.get_attribute('brightness', 0)) return float(self.get_attribute("brightness", 0))
@property @property
def hs_color(self): def hs_color(self):
return self.get_attribute('hs_color', [0, 0]) return self.get_attribute("hs_color", [0, 0])
@property @property
def is_on(self): def is_on(self):
return self.get_attribute('is_on', False) return self.get_attribute("is_on", False)
@property @property
def supported_features(self): def supported_features(self):
return ( return LightEntityFeature.TRANSITION
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:
return [0 for x in range(len(self.port))] if not self.is_ws else [0] * 3 return [0 for x in range(len(self.port))] if not self.is_ws else [0] * 3
rgb = colorsys.hsv_to_rgb( rgb = colorsys.hsv_to_rgb(
self.hs_color[0]/360, self.hs_color[1]/100, self.brightness / 255 self.hs_color[0] / 360, self.hs_color[1] / 100, self.brightness / 255
) )
rgb = [x for x in rgb] rgb = [x for x in rgb]
if self.white_value is not None: if self.white_value is not None:
@@ -180,9 +219,7 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
if not self.customize.get(CONF_WHITE_SEP): if not self.customize.get(CONF_WHITE_SEP):
white = white * (self.brightness / 255) white = white * (self.brightness / 255)
rgb.append(white / 255) rgb.append(white / 255)
rgb = [ rgb = [round(x * self.max_values[i]) for i, x in enumerate(rgb)]
round(x * self.max_values[i]) for i, x in enumerate(rgb)
]
if self.is_ws: if self.is_ws:
# восстанавливаем мэпинг # восстанавливаем мэпинг
rgb = map_reorder_rgb(rgb, RGB, self._color_order) rgb = map_reorder_rgb(rgb, RGB, self._color_order)
@@ -192,7 +229,7 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
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.lg.debug(f'turn on %s with kwargs %s', self.entity_id, kwargs) self.lg.debug(f"turn on %s with kwargs %s", self.entity_id, kwargs)
if self._restore is not None: if self._restore is not None:
self._restore.update(kwargs) self._restore.update(kwargs)
kwargs = self._restore kwargs = self._restore
@@ -208,9 +245,9 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
return return
self._last_called = time.time() self._last_called = time.time()
self._restore = { self._restore = {
'hs_color': self.hs_color, "hs_color": self.hs_color,
'brightness': self.brightness, "brightness": self.brightness,
'white_value': self.white_value, "white_value": self.white_value,
} }
_before = self.get_rgbw() _before = self.get_rgbw()
self._is_on = False self._is_on = False
@@ -219,17 +256,22 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
self._task = asyncio.create_task(self.set_color(_before, **kwargs)) self._task = asyncio.create_task(self.set_color(_before, **kwargs))
async def set_color(self, _before, **kwargs): async def set_color(self, _before, **kwargs):
transition = kwargs.get('transition') transition = kwargs.get("transition")
update_state = transition is not None and transition > 3 update_state = transition is not None and transition > 3
_after = None
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() if item == "rgb_color":
_after = map_reorder_rgb(value, RGB, self._color_order)
self._hs_color = colorsys.rgb_to_hsv(*value)
_after = _after or self.get_rgbw()
self._rgb_color = map_reorder_rgb(tuple(_after[:3]), self._color_order, RGB)
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)
transition = transition * ratio transition = transition * ratio
self.async_write_ha_state() self.async_write_ha_state()
ports = self.port if not self.is_ws else self.port*3 ports = self.port if not self.is_ws else self.port * 3
config = [(port, _before[i], _after[i]) for i, port in enumerate(ports)] config = [(port, _before[i], _after[i]) for i, port in enumerate(ports)]
try: try:
await self.mega.smooth_dim( await self.mega.smooth_dim(
@@ -245,7 +287,7 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
except asyncio.CancelledError: except asyncio.CancelledError:
return return
except: except:
self.lg.exception('while dimming') self.lg.exception("while dimming")
async def async_will_remove_from_hass(self) -> None: async def async_will_remove_from_hass(self) -> None:
await super().async_will_remove_from_hass() await super().async_will_remove_from_hass()
@@ -260,10 +302,10 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
w = None w = None
rgb = rgbw rgb = rgbw
if self.is_ws: if self.is_ws:
rgb = map_reorder_rgb( rgb = map_reorder_rgb(rgb, self._color_order, RGB)
rgb, self._color_order, RGB h, s, v = colorsys.rgb_to_hsv(
) *[x / self.max_values[i] for i, x in enumerate(rgb)]
h, s, v = colorsys.rgb_to_hsv(*[x/self.max_values[i] for i, x in enumerate(rgb)]) )
h *= 360 h *= 360
s *= 100 s *= 100
v *= 255 v *= 255
@@ -272,7 +314,7 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
self._brightness = v self._brightness = v
if w is not None: if w is not None:
if not self.customize.get(CONF_WHITE_SEP): if not self.customize.get(CONF_WHITE_SEP):
w = w/(self._brightness / 255) w = w / (self._brightness / 255)
else: else:
w = w w = w
w = w / (self.max_values[-1] / 255) w = w / (self.max_values[-1] / 255)
@@ -297,7 +339,7 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
return return
data = data.get(x, None) data = data.get(x, None)
if isinstance(data, dict): if isinstance(data, dict):
data = data.get('value') data = data.get("value")
data = safe_int(data) data = safe_int(data)
if data is None: if data is None:
return return
@@ -314,4 +356,4 @@ class MegaRGBW(LightEntity, BaseMegaEntity):
ret = r ret = r
else: else:
ret = max([r, ret]) ret = max([r, ret])
return ret return ret

View File

@@ -2,7 +2,7 @@
"domain": "mega", "domain": "mega",
"name": "mega", "name": "mega",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/mega_hacs", "documentation": "https://github.com/andvikt/mega_hacs",
"requirements": [ "requirements": [
"beautifulsoup4", "beautifulsoup4",
"lxml" "lxml"
@@ -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.0.1" "version": "v1.1.8b5"
} }

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
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 (
@@ -20,7 +18,8 @@ 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 from .const import CONF_KEY, 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
@@ -39,8 +38,8 @@ UNITS = {
HUM: '%' HUM: '%'
} }
CLASSES = { CLASSES = {
TEMP: DEVICE_CLASS_TEMPERATURE, TEMP: SensorDeviceClass.TEMPERATURE,
HUM: DEVICE_CLASS_HUMIDITY HUM: SensorDeviceClass.HUMIDITY
} }
# Validation of the user's configuration # Validation of the user's configuration
_ITEM = { _ITEM = {
@@ -84,11 +83,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
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, {})
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, {})
if c.get(CONF_SKIP):
hub.skip_ports |= {port}
continue
for data in cfg: for data in cfg:
hub.lg.debug(f'add sensor on port %s with data %s', port, data) hub.lg.debug(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,
@@ -102,7 +106,57 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
async_add_devices(devices) async_add_devices(devices)
class MegaI2C(MegaPushEntity): class FilterBadValues(MegaPushEntity, SensorEntity):
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,
@@ -119,46 +173,64 @@ class MegaI2C(MegaPushEntity):
@property @property
def customize(self): def customize(self):
return super().customize.get(self.id_suffix, {}) or {} ret = super().customize
_old = ret.get(self.id_suffix)
@property if _old is not None:
def device_class(self): ret = ret.copy()
return self._device_class ret.update(_old)
@property
def unit_of_measurement(self):
return self._unit_of_measurement
@property
def state(self):
# self.lg.debug(f'get % all states: %', self._params, self.mega.values)
ret = self.mega.values.get(self._params)
if self.customize.get(CONF_HEX_TO_FLOAT):
try:
ret = struct.unpack('!f', bytes.fromhex('41973333'))[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:
ret = float(ret)
if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass
ret = tmpl.async_render({'value': ret})
except:
ret = ret
return ret return ret
@property
def extra_state_attributes(self):
attrs = super().extra_state_attributes or {}
attrs.update({
'i2c_id': self.id_suffix,
})
return attrs
@property
def device_class(self):
return self._device_class
@property
def native_unit_of_measurement(self):
return self._unit_of_measurement
@property
def native_value(self):
try:
ret = self.mega.values.get(self._params)
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:
ret = float(ret)
if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass
ret = tmpl.async_render({'value': ret})
except:
ret = ret
ret = self.filter_value(ret)
if ret is not None:
return str(ret)
except Exception:
lg.exception('while getting value')
return None
@property @property
def device_class(self): def device_class(self):
return self._device_class return self._device_class
class Mega1WSensor(MegaPushEntity): class Mega1WSensor(FilterBadValues):
def __init__( def __init__(
self, self,
unit_of_measurement, unit_of_measurement=None,
device_class, device_class=None,
key=None, key=None,
*args, *args,
**kwargs **kwargs
@@ -170,14 +242,15 @@ class Mega1WSensor(MegaPushEntity):
: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._value = None
self.key = key self.key = key
self._value = None
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 unit_of_measurement(self): def native_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
@@ -193,7 +266,7 @@ class Mega1WSensor(MegaPushEntity):
if self.key: if self.key:
return super().unique_id + f'_{self.key}' return super().unique_id + f'_{self.key}'
else: else:
return super(Mega1WSensor, self).unique_id return super().unique_id
@property @property
def device_class(self): def device_class(self):
@@ -208,51 +281,67 @@ class Mega1WSensor(MegaPushEntity):
return self._device_class return self._device_class
@property @property
def state(self): def native_value(self):
ret = None
if self.key:
try:
ret = self.mega.values.get(self.port, {})
if isinstance(ret, dict):
ret = ret.get('value', {})
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._state is not None:
ret = self._state.state
try: try:
ret = float(ret)
ret = str(ret)
except:
ret = None ret = None
if self.customize.get(CONF_HEX_TO_FLOAT): if not hasattr(self, 'key'):
return None
if self.key:
try:
ret = self.mega.values.get(self.port, {})
if isinstance(ret, dict):
ret = ret.get('value', {})
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: try:
ret = struct.unpack('!f', bytes.fromhex(ret))[0] ret = float(ret)
ret = str(ret)
except: except:
self.lg.warning(f'could not convert {ret} form hex to float') self.lg.debug(f'could not convert to float "{ret}"')
tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE)) ret = self.prev_value
try: if self.customize.get(CONF_HEX_TO_FLOAT):
ret = float(ret) try:
if tmpl is not None and self.hass is not None: ret = struct.unpack('!f', bytes.fromhex(ret))[0]
tmpl.hass = self.hass except:
ret = tmpl.async_render({'value': ret}) self.lg.warning(f'could not convert {ret} form hex to float')
except: tmpl: Template = self.customize.get(CONF_CONV_TEMPLATE, self.customize.get(CONF_VALUE_TEMPLATE))
return ret try:
ret = float(ret)
if tmpl is not None and self.hass is not None:
tmpl.hass = self.hass
ret = tmpl.async_render({'value': ret})
except:
pass
ret = self.filter_value(ret)
self.prev_value = ret
if ret is not None:
return str(ret)
except Exception:
lg.exception('while parsing state')
return None
@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):
c = c.get(self.key) try:
c = c.get(self.key)
except:
pass
return c or n return c or n
_constructors = { _constructors = {
'sensor': Mega1WSensor, 'sensor': Mega1WSensor,
'i2c': MegaI2C, 'i2c': MegaI2C,
} }

View File

@@ -50,6 +50,10 @@ 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.

58
docs/blueprints.md Normal file
View File

@@ -0,0 +1,58 @@
**blueprints** - это удобные шаблоны автоматизаций, которые помогают строить автоматизацию из
интерфейса и ими легко делиться. Все ваши шаблоны доступны из специального меню.
[![Open your Home Assistant instance and show your blueprints.](https://my.home-assistant.io/badges/blueprints.svg)](https://my.home-assistant.io/redirect/blueprints/)
[Официальная документация по blueprints](https://www.home-assistant.io/docs/blueprint/)
## Общее
Здесь я делюсь шаблонами, в которых используются события из моей интеграции.
Если вы хотите сделать что-то подобное своими руками, то можно использовать мои шаблоны как отправную точку.
Во всех шаблонах в качестве триггера используется событие **mega.binary** и доступен выбор типа,
[подробное описание типов здесь](events.md#binary).
## Включить что-то
Этот шаблон лучше всего подходит для включения сценариев/сцен или любых других объектов по нажатию какой-то кнопки или
обнаружению движения.
!!! note "Движение"
Датчики движения - это такие же *binary_sensor* как и обычные выключатели. В зависимости от настроек контроллера
будут приходить события либо типа **single** (если настроен режим click), либо **press**
Опционально так же доступна настройка автоматического выключения по таймеру, если указан 0 (по умолчанию), таймер не
будет использован.
Опционально доступен так же *блокирующий объект* и *период блокировки*. Например, если в одной комнате с датчиком
движения есть выключатель, тогда его можно указать как *блокирующий объект* и в течении *периода блокировки*
после нажатия выключателя события с датчика движения будут игнорироваться.
[![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgist.github.com%2Fandvikt%2Fb78459f4f43862d04c7fbba20d6893c7)
[Исходный код](https://gist.github.com/andvikt/b78459f4f43862d04c7fbba20d6893c7)
## Переключить состояние
Классическое управление светом с кнопки без фиксации: нажали кнопку - свет выключился, если он сейчас включен, и наоборот.
Если вам нужно управлять несколькими светильниками, то необходимо будет
[создать группу света](https://www.home-assistant.io/integrations/light.group/)
[![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgist.github.com%2Fandvikt%2Fefb48535b1b9d998fe3dbe9a3efcea2c)
[Исходный код](https://gist.github.com/andvikt/efb48535b1b9d998fe3dbe9a3efcea2c)
## Выключатель с фиксацией
Если выбран тип "нестрогий", то при каждом переключении состояния выключателя состоянии целевого объекта так же будет
меняться. Этот режим рекомендуется, тк в случае переключения состояния с сервера, в случае со строгим типом будет
"рассинхрон" - вам придется сначала выключатель привести в соответствие с текущим состоянием света.
Если выбран тип "строгий", то будет строгое соответсвие состояний, те выкл==выкл и наоборот.
[![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgist.github.com%2Fandvikt%2F9addf966db75d0964143177963f40bb9)
[Исходный код](https://gist.github.com/andvikt/9addf966db75d0964143177963f40bb9)
## Универсальный шаблон
Универсальный шаблон, с помощью которого можно выбрать любое событие меги, привязать
к нему набор произвольных действий
[![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgist.github.com%2Fandvikt%2Fbe1f683d308050b8972f9efa8aec465f)
[Исходный код](https://gist.github.com/andvikt/be1f683d308050b8972f9efa8aec465f)

15
docs/debug.md Normal file
View File

@@ -0,0 +1,15 @@
[Сообщить о проблеме](https://github.com/andvikt/mega_hacs/issues/new?assignees=&labels=&template=bug-report.md&title=){ .md-button .md-button--primary }
В первую очередь проверьте лог на наличие ошибок, доступ к логу возможен по кнопке ниже.
[![Open your Home Assistant instance and show your Home Assistant logs.](https://my.home-assistant.io/badges/logs.svg)](https://my.home-assistant.io/redirect/logs/)
Так же будет очень полезно прикладывать детальный лог, который можно включить в конфиге так:
```yaml
logger:
default: info
logs:
custom_components.mega: debug
```
Для просмотра логов рекомендуется использовать [logviewer](https://github.com/hassio-addons/addon-log-viewer)

66
docs/events.md Normal file
View File

@@ -0,0 +1,66 @@
Для быстрого старта рекомендую попробовать [мои шаблоны автоматизаций](blueprints.md)
## mega.binary {: #binary}
События можно использовать только в автоматизациях как триггер типа *event*
```yaml
- alias: some long click
trigger:
- platform: event
event_type: mega.binary
event_data:
entity_id: binary_sensor.some_id
type: long
action:
- service: light.toggle
entity_id: light.some_light
```
!!! note "Возможные варианты поля type"
- **press**: замыкание
- **release**: размыкание (с гарантией, что не было долгого нажатия)
*Эти типы доступны только в режиме click (настраивается на контроллере):*
- **long**: долгое нажатие
- **long_release**: размыкание после долгого нажатия
- **single**: одинарный клик (в режиме кликов)
- **double**: двойной клик
## mega.sensor
Этот вид событий более "технический", им имеет смысл пользоваться только если функциональности *mega.binary* не
достаточно.
```yaml
# событие при перезагрузке меги
- alias: mega restart
trigger:
- platform: event
event_type: mega.sensor
event_data:
st: 1
action:
# какой-то экшн
# Пример события с полями как есть прямо из меги
- 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
```
!!! note "События могут содержать следующие поля в event_data"
- **mega_id**: id как в конфиге HA
- **pt**: номер порта
- **cnt**: счетчик срабатываний
- **mdid**: id как в конфиге контроллера
- **click**: клик (подробнее в документации меги)
- **port**: номер порта
## Отладка
Чтобы понять, какие события приходят, лучше всего воспользоваться панелью разработчика (кнопка ниже) и подписаться
на вкладке события на событие `mega.binary` или `mega.sensor`, понажимать физические кнопки на меге.
[![Open your Home Assistant instance and show your event developer tools.](https://my.home-assistant.io/badges/developer_events.svg)](https://my.home-assistant.io/redirect/developer_events/)

64
docs/http.md Normal file
View File

@@ -0,0 +1,64 @@
Контроллер оповещает сервер о своих событиях, например, нажали кнопку выключателя или сработал датчик движения,
для этого в интеграции реализован http-сервер, для его работы необходимо прописать
в настройках меги следующие параметры:
```yaml
srv: "192.168.1.4:8123" # ip:port вашего HA
script: "mega" # это api интеграции, к которому будет обращаться контроллер
```
!!! note "Внимание!"
Не используйте **srv loop** на контроллере - это может приводить к ложным срабатываниям входов. Вместо srv loop
интеграция будет сама обновлять все состояния портов с заданным интервалом
За события будут отвечать объекты типа *binary_sensor* - их статус будет меняться на **on** при замыкании
контакта, на **off** при размыкании, а так же для более сложного контроля (двойные, долгие нажатия) предусмотрены
события с типом *mega.binary*, [об этом подробнее в разделе события](events.md)
Так же вы можете [воспользоваться моими шаблонами автоматизаций](blueprints.md) для быстрого понимания, как всем этим
пользоваться.
## Ответ на входящие события от контроллера
Контроллер ожидает ответ от сервера, который может быть сценарием (по умолчанию интеграция отвечает `d`, что означает
запустить то что прописано в поле act в настройках порта).
*Внимание!* По умолчанию в настройках интеграции стоит опция `имитация ответа` - это означает, что сервер вместо ответа
делает запрос к меге с необходимой командой - это вынужденная мера, тк встроенный в HA сервер разбивает пакет на части,
а контроллер не работает с такими пакетами. В целом, `имитация ответа` полностью закрывает эту проблему, единственный
недостаток - это небольшая задержка в ответе.
Для максимальной скорости реакции, можно воспользоваться
[аддоном](https://github.com/andvikt/mega_addon/tree/master/mega-proxy), подробности в документации аддона.
[Поддерживаются шаблоны HA.](yaml.md#binary) Это может быть использовано, например, для запоминания яркости (тк сам контроллер этого не
умеет).
## Отладка шаблонов {: #temp-debug }
Отладку шаблонов рекомендуется проводить в специальном меню HA, которое находится в `Панель разработчика` - `Шаблоны`
Вот пример, с которого можно начать:
```yaml
{## Переменные, которые передает контроллер, указываются только в тесте ##}
{% set m = 1%}
{% set pt = 2%}
{% set mdid = 'mega'%}
{## Шаблон ответа ##}
{% if m in [0, 1] %}d{% endif %}
```
## Отладка ответов http-сервера {: #http-response }
Для отладки ответов сервера можно самим имитировать запросы контроллера, если у вас есть доступ к консоли HA:
```shell
curl -v -X GET 'http://localhost:8123/mega?pt=5&m=1&mdid=mymega1'
```
Где mymega1 - id устройства mega, который нужно узнать по url `http://192.168.1.14/sec/?cf=2`
При этом необходимо так же в настройках интеграции прописать хост, с которого вы будете обращаться,
[подробнее](yaml.md#allow_hosts)
И тогда можно с локальной машины делать запросы на ваш сервер HA:
```shell
curl -v -X GET 'http://192.168.88.1.4:8123/mega?pt=5&m=1&mdid=mymega1'
```
В ответ будет приходить либо d, либо скрипт, который вы настроили

29
docs/i2c.md Normal file
View File

@@ -0,0 +1,29 @@
I2C-датчики будут добавлены автоматически в HA с названием, соответствующим порту, типу и адресу
(если необходим), название и entity_id вы всегда можете поменять из интерфейса HA, [а также дополнительно их
кастомизировать с помощью yaml](yaml.md#sensors).
Как и все остальные датчики, i2c подчиняется единому интервалу обновления, который указывается в меню интеграции.
## Список i2c-датчиков, поддерживаемых интеграцией: {: #list}
!!! note ""
Под поддерживаемыми подразумеваются те датчики, у которых учтены все возможные
дополнительные значения, а так же корректно определены типы
Неподдерживаемые датчики все равно будут работать, но будет отображаться только основное значение (i2c_par=0),
а тип будет определен как общий, универсальный для всех датчиков.
- HTU21D/Si7021
- SHT31
- MAX44009
- BH1750
- TSL2591
- BMP180
- BME280
- T6703/T67xx
- MLX90614
- PTsensor
- MCP9600
- DPS368
- ADS1115/ADS1015
Так же заводите issue если какой-то датчик отсутсвует в этом списке, но поддерживается контроллером.

71
docs/index.md Normal file
View File

@@ -0,0 +1,71 @@
# 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)
<a class="github-button" href="https://github.com/andvikt/mega_hacs" data-icon="octicon-star" data-show-count="true" aria-label="Star andvikt/mega_hacs on GitHub">Star</a>
[Сообщить о проблеме](https://github.com/andvikt/mega_hacs/issues/new?assignees=&labels=&template=bug-report.md&title=){ .md-button .md-button--primary }
[Предложение об улучшении](https://github.com/andvikt/mega_hacs/issues/new?assignees=&labels=enhancement&template=enhance.md&title=){ .md-button .md-button--primary }
Интеграция с [MegaD-2561, MegaD-328](https://www.ab-log.ru/smart-house/ethernet/megad-2561)
Если вам понравилась интеграция, не забудьте поставить звезду на гитхабе - вам не сложно, а мне приятно ) А если
интеграция очень понравилась - еще приятнее, если вы воспользуетесь кнопкой доната )
Обновление прошивки MegaD можно делать прямо из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git)
## Основные особенности {: #mains }
- Настройка в [веб-интерфейсе](settings.md) + [yaml](yaml.md)
- Все порты автоматически добавляются как устройства (для обычных релейных выходов создается
`light`, для шим - `light` с поддержкой яркости, для цифровых входов `binary_sensor`, для датчиков
`sensor`)
- Поддержка rgb+w лент как с использованием диммеров, так и адресных лент на чипах ws28xx и подобных,
[подробнее про rgbw](yaml.md#rgb)
- Плавное диммирование для любых диммируемых объектов (в том числе с аппаратной поддержкой и без),
[подробнее про smooth](smooth.md)
- Возможность работы с несколькими megad
- Обратная связь по [http](http.md)
- Автоматическое восстановление состояний выходов после перезагрузки контроллера
- Автоматическое добавление/изменение объектов после перезагрузки контроллера
- [События](events.md) на двойные/долгие нажатия
- Команды выполняются друг за другом без конкурентного доступа к ресурсам megad, это дает гарантии надежного исполнения
большого кол-ва команд (например в сценах). Каждая следующая команда отправляется только после получения ответа о
выполнении предыдущей.
- поддержка [ds2413](https://www.ab-log.ru/smart-house/ethernet/megad-2w) в том числе несколько шиной (начиная с версии 0.4.1)
- поддержка расширителей MegaD-16I-XT, MegaD-16R-XT, MegaD-16PWM (начиная с версии 0.5.1)
- поддержка всех возможных датчиков в режиме I2C-ANY, полный список поддерживаемых датчиков
[по ссылке](i2c.md) (начиная с версии 0.6.1)
## Установка {: #install}
Если вы уже раньше устанавливали HACS, то просто поищите в списке интеграций HACS MegaD, если нет, то сначла необходимо
установить HACS - это витрина сторонних интеграций. [Инструкция по установке](https://hacs.xyz/docs/installation/installation)
Далее внутри интерфейса HACS ищем MegaD: `HACS - Integrations - Explore`, в поиске ищем MegaD.
На этом установка не закончена, вам потребуется прописать настройки каждого контроллера, [подробнее](settings.md)
!!! note "Альтернативный способ установки"
Откройте терминал (стандартный аддон Terminal & SSH, если у вас есть supervisor, если нет то терминал вашей системы)
```shell
# из папки с конфигом
wget -q -O - https://raw.githubusercontent.com/andvikt/mega_hacs/master/install.sh | bash -
```
Не забываем перезагрузить HA
## Обновления
Обновления выполняются так же в меню HACS.
Информация об обновлениях приходит с некоторым интервалом, чтобы вручную проверить наличие обновлений
нажмите три точки возле интеграции в меню HACS и нажмите `обновить информацию`
## Беты {: #beta }
Иногда я буду выпускать бета-версии, в стабильности которых пока не уверен и не готов раскатывать на всех, поэтому в целях тестирвоания
нужно принудительно включать поддержку бет, для этого зайдите в HACS, найдите интеграцию MegaD, нажмите три точки,
там кнопка "переустановить" или reinstall, дальше нужно нажать галку "показывать бета-версии"
## Зависимости {: #deps }
Для максимальной скорости реакции на команды сервера, рекомендуется выключить `имитацию http-ответа` в
настройках интеграции и настроить proxy_pass к HA, самый простой способ сделать это - воспользоваться
[специальным аддоном](https://github.com/andvikt/mega_addon/tree/master/mega-proxy)
Обновить ваш контроллер до последней версии, обновление прошивки MegaD можно делать
из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git)

32
docs/services.md Normal file
View File

@@ -0,0 +1,32 @@
Все сервисы доступны в меню разработчика с описанием и примерами использования
```yaml
mega.save:
description: Сохраняет текущее состояние портов (?cmd=s)
fields:
mega_id:
description: ID меги, можно оставить пустым, тогда будут сохранены все зарегистрированные меги
example: "mega"
mega.get_port:
description: Запросить текущий статус порта (или всех)
fields:
mega_id:
description: ID меги, можно оставить пустым, тогда будут порты всех зарегистрированных мег
example: "mega"
port:
description: Номер порта (если не заполнять, будут запрошены все порты сразу)
example: 1
mega.run_cmd:
description: Выполнить любую произвольную команду
fields:
mega_id:
description: ID меги
example: "mega"
port:
description: Номер порта (это не порт, которым мы управляем, а порт с которого шлем команду)
example: 1
cmd:
description: Любая поддерживаемая мегой команда
example: "1:0"
```

16
docs/settings.md Normal file
View File

@@ -0,0 +1,16 @@
[После успешной установки интеграции в HACS](index.md#install), необходимо настроить
каждый контроллер, проще всего сделать это по этой кнопке:
[![Добавить интеграцию](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=mega)
Все имеющиеся у вас порты будут настроены автоматически. Вы можете менять названия, иконки и entity_id так же из интерфейса.
В самой меге необходимо прописать настройки:
```yaml
srv: "192.168.1.4:8123" # ip:port вашего HA
script: "mega" # это api интеграции, к которому будет обращаться контроллер
```
Так же необходимо настроить Mega-ID в настройках контроллера, для каждой меги id должен быть разным.
При любых изменениях настроек контроллера (типы входов, id и тд) необходимо в настройках интеграции нажать `Обновить
объекты`

50
docs/smooth.md Normal file
View File

@@ -0,0 +1,50 @@
Начиная с версии `1.0.0` интеграция поддерживает плавные переходы. Функция реализована
как на аппаратном уровне, так и на программном.
Для аппаратной поддержки в настройках контроллера диммируемого порта необходимо включить опцию smooth.
В чем разница между аппаратной и программной реализацией? Контроллер на аппаратном уровне умеет медленно
менять значение pwm-порта, рекомендуется для всех портов с поддержкой этого режима использовать именно его,
тк будет обеспечена максимальная плавность для любого числа устройств одновременно.
Плавность программного диммирования ограничена ресурсами вашего сервера и скоростью ответа контроллера,
если вы будете довольно быстро (за пару секунд) диммировать сразу группу
света из нескольких светильков, то в программной реализации возможно увидеть скачки.
Тем не менее, pwm-расширитель не умеет аппаратно сглаживать диммирование, поэтому для него есть смысл воспользоваться
программной реализацией
Для запуска плавного перехода можно воспользоваться штатными сервисами, например:
```yaml
action:
service: light.turn_on
entity_id: light.some_light
data:
# свет будет плавно включаться в течении 30 секунд
brightness_pct: 50
transition: 10 # кол-во секунд на переход
```
Так же любые диммируемые каналы могут участвовать в сценах, а эти сцены в свою очередь будут поддерживать опцию transition:
```yaml
action:
service: scene.turn_on
target:
entity_id: scene.romantic
data:
transition: 2.5
```
Плавность реализована в любых диммируемых объектах: свет, rgb-ленты.
Кроме того, возможно установить плавность по-умолчанию (имеет смысл использовать на pwm-расширителе), для этого в yaml-конфиге
следует добавить опцию smooth:
```yaml
mega:
mega1:
10e1:
smooth: 1 # если указать, то порт будет диммироваться плавно (от 0 до 100% за <smooth> секунд)
# опцию smooth можно использовать и на обычном pwm-порте, но в этом мало необходимости, лучше использовать
# встроенный в контроллер механизм smooth
```
Для светодиодных лент smooth по умолчанию установлен в 1 секунду,
подробнее [тут](yaml.md#rgb)

269
docs/yaml.md Normal file
View File

@@ -0,0 +1,269 @@
С помощью 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`, начинаем с
указания названия интеграции:
```yaml hl_lines="1"
mega:
megaid1:
10:
domain: switch
invert: true
megaid2:
14:
hex_to_float: true
```
Далее каждый новый контроллер добавляется с помощью указания его id, который вы
придумали при установке интеграции
```yaml hl_lines="2 6"
mega:
megaid1:
10:
domain: switch
invert: true
megaid2:
14:
hex_to_float: true
```
Далее конфигурируются порты:
```yaml hl_lines="3 4 5 7 8"
mega:
megaid1:
10:
domain: switch
invert: true
megaid2:
14:
hex_to_float: true
```
## Параметры устройств
В зависимости от типа порта доступны разные параметры. Все параметры опциональные, в скобках приведены типы и дефолтные
значения.
### Стандартный набор параметров
Все устройства вне зависимости от типа
!!! note ""
- **skip** (bool, false): пропустить или нет. Если true - устройство будет исключено из списка
- **name** (str): имя, используется в интерфейсе
### Реле
!!! note ""
- **domain** (str, light): тип устройства. Можно выбрать light или switch
- **invert** (bool, false): инвертировать или нет.
### ds2413
Те же параметры, что у реле, но записываются иначе:
```yaml
10:
c6c439000000_a: #c6c439000000 - это адрес ds2413, a-первый канал
# параметры
c6c439000000_b: #b-второй канал
```
### Диммеры
!!! note ""
- **smooth** (float, 0): программное плавное диммирование. Это поле отвечает за кол-во секунд, за которое яркость
диммера набирает от 0 до 100%
- **range** (list[int, int], [0, 255]), *начиная с версии 1.1.0*: границы диммирования в абсолютных единицах 0..255. При диммировании 1%
будет равен левой границе, 100% - правой.
```yaml
range: [20, 200]
```
[Подробнее про плавное диммирование](smooth.md)
### MegaD-16R-XT, MegaD-16PWM
Порты расширителей MegaD-16R-XT, MegaD-16PWM конфигурируются аналогично обычным реле и диммерам, но адресация порта
выглядит так:
```yaml
33e1: # 33-основной порт, на котором сидит расширитель, e1-дополнительный порт расширителя
# стандартный конфиг порта
33e2:
```
### RGB+W {: #rgb}
Для настройки rgb(w) лент существует специальный раздел `led` в настройках каждого контроллера:
```yaml hl_lines="3"
mega:
megaid1:
led:
ledid1: # id, который вы придумываете сами
# конфиг
```
Далее интеграция имеет поддержку двух типов лент
#### На диммерах
Интеграция может превратить любые 3 (или 4) диммера (актуально для мосфетов на pwm-расширителе или моноблоке)
в rgb(w) контроллер с интерфейсом выбора цвета. Конфиг для такого типа ленты будет выглядеть так:
!!! note ""
- **ports** (list\[str\]): список номеров портов, из которых составлять ленту, все порты должны быть типа PWM.
Порядок цветов строго \[R, G, B, W\]. W указывается опционально
- **white_sep** (bool, true): яркостью белого можно управлять в двух режимах - синхронно с яркостью RGB, либо
отдельно, по умолчанию стоит отдельно (true)
- **smooth** (float, 1): скорость диммирования, от 0 до 100% за <smooth> секунд.
Пример:
```yaml
some_led1:
ports: [10, 12, 15, 16]
white_sep: true
smooth: 2
```
#### Адресные ленты на WS281X
Подробно про поддержку контроллера таких лент рассказано в [инструкции](https://www.ab-log.ru/smart-house/ethernet/megad-2561#ws).
Интеграция не привносит ничего нового - только помогает "пробросить" такие ленты в интерфейс HA.
Конфиг таких лент выглядит так:
!!! note ""
- **ws28xx** (bool, false): обязательное поле, необходимо установить true.
- **port** (int): номер порта, на котором настроена лента.
- **order** (str, "rgb"): последовательность каналов, допускаются любые комбинации букв r,g,b: rbg, bgr и тд
- **chip** (int, 100): кол-во чипов в ленте, по умолчанию 100, если их меньше емеет смысл указать правильное кол-во
для увеличения скорости плавного диммирования
- **smooth** (float, 1): скорость диммирования, от 0 до 100% за <smooth> секунд.
Пример:
```yaml
some_led2:
ws28xx: true
port: 36
order: bgr
smooth: 2
```
### Бинарные сенсоры {: #binary}
Или по-другому цифровые входы. Как правило используются для выключателей, кнопок, датчиков движения и тд.
!!! note ""
- **response_template** (str): шаблон ответа на команды сервера. По-умолчанию d.
Про формат ответа подробно описано [тут](https://www.ab-log.ru/smart-house/ethernet/megad-2561#conf-in-act).
В шаблоне можно использовать параметры, которые передает контроллер (m, click, pt, mdid, mega_id).
Про отладку шаблонов подробнее [тут](http.md#temp-debug)
!!! note "на меге"
Для корректной работы binary_sensor имеет смысл использовать режим P&R в настройках порта меги
Бинарные сенсоры так же отвечают за события типа *mega.binary*, [об этом подробнее в разделе события](events.md)
Примеры шаблонов ответа:
```yaml
4:
response_template: "5:2" # простейший пример без шаблона. Каждый раз когда будет приходить сообщение на этот порт,
# будем менять состояние на противоположное
5:
# пример с использованием шаблона, порт 1 будет выключен если он сейчас включен и включен с последней сохраненной
# яркостью если он сейчас выключен
response_template: >-
{% if is_state('light.some_port_1', 'on') %}
1:0
{% else %}
1:{{state_attr('light.some_port_1', 'brightness')}}
{% endif %}
6:
# в шаблон так же передаются все параметры, которые передает контроллер (pt, cnt, m, click)
# эти параметры можно использовать в условиях или непосредственно в шаблоне в виде {{pt}}
response_template: >-
{% if m==2 %}1:0{% else %}d{% endif %}
```
### Датчики {: #sensors }
Любой датчик будь то i2c или аналоговый или 1-wire
!!! note ""
- **unit_of_measurement** (str): единицы измерения, [список доступных](https://developers.home-assistant.io/docs/core/entity/sensor#available-device-classes)
- **value_template** (str): шаблон для конвертации, например `{{(value|float)/100}}`
- **device_class** (str): класс устройства, [список доступных](https://developers.home-assistant.io/docs/core/entity/sensor#available-device-classes)
- **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
36:
# конфиг
```
#### DHT11/22
На этих сенсорах два значения, одно для температуры, второе для влажности, поэтому для них применяется
особый вид адресации:
```yaml
35:
name:
hum: "влажность"
temp: "температура"
# и так далее для любого параметра сенсоров
```
Логика так себе ) Но так повелось в первых версиях.
#### 1W-BUS
Для датчиков установленных в шину 1-wire адресация кастомизации такая:
```yaml
35:
addr: # адрес датчика, по-умолчанию entity_id будет состоять из адреса и типа
# конфиг
```
#### i2c {: #i2c}
Для сенсоров i2c нужно так же указать id сенсора, который можно посмотреть в атрибутах объекта на [странице разработчика](https://my.home-assistant.io/redirect/developer_states/).
```yaml
36:
htu21d_humidity: # i2c_id
# конфиг
```
[Подробнее про i2c](i2c.md)
## Параметры контроллера
Некоторые параметры применяются для всего контроллера (одна мега)
### def_response
Шаблон ответа сервера по умолчанию. Если этот параметр указан, то настройка "d по умолчанию"
в UI игнорируется.
Пример:
```yaml
mega:
megaid1:
def_response: >-
{% if m in [0, 1] %}d{% endif %}
```
## Параметры интеграции
### allow_hosts {: #allow_hosts }
Отвечает за список хостов, с которых интеграция "слушает" сообщения. По умолчанию, в этот список
входят все настроенные меги, а так же все запросы с локального хоста.
Иногда, в целях отладки, требуется расширить этот список, что можно сделать следующим оьразом:
```yaml
mega:
allow_hosts:
- 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%
```

34
mkdocs.yml Normal file
View File

@@ -0,0 +1,34 @@
site_name: MegaD HomeAssistant integration
repo_url: https://github.com/andvikt/mega_hacs
site_url: http://127.0.0.1:8000
extra:
disqus: mega-hacs
analytics:
provider: google
property: G-E3LX9D6959
theme:
name: material
markdown_extensions:
- toc:
permalink: "#"
- attr_list:
- pymdownx.highlight
- pymdownx.superfences
- admonition
plugins:
- search
nav:
- Главное: index.md
- Конфигурация:
- В интерфейсе: settings.md
- Настройка обратной связи: http.md
- Кастомизация: yaml.md
- i2c: i2c.md
- Плавные переходы: smooth.md
- Автоматизация:
- Шаблоны (Blueprints): blueprints.md
- События: events.md
- Сервисы: services.md
- Неполадки: debug.md
extra_javascript:
- "https://buttons.github.io/buttons.js"

View File

@@ -9,76 +9,4 @@
Если вам понравилась интеграция, не забудьте поставить звезду на гитхабе - вам не сложно, а мне приятно ) А если Если вам понравилась интеграция, не забудьте поставить звезду на гитхабе - вам не сложно, а мне приятно ) А если
интеграция очень понравилась - еще приятнее, если вы воспользуетесь кнопкой доната ) интеграция очень понравилась - еще приятнее, если вы воспользуетесь кнопкой доната )
Обновление прошивки MegaD можно делать прямо из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git) [Подробная документация](https://andvikt.github.io/mega_hacs/)
Подробная документация по [ссылке](https://github.com/andvikt/mega_hacs/wiki)
Предложения по доработкам просьба писать в [discussions](https://github.com/andvikt/mega_hacs/discussions), о проблемах
создавать [issue](https://github.com/andvikt/mega_hacs/issues/new/choose)
## Основные особенности:
- Настройка в веб-интерфейсе + [yaml](https://github.com/andvikt/mega_hacs/wiki/Кастомизация)
- Все порты автоматически добавляются как устройства (для обычных релейных выходов создается
`light`, для шим - `light` с поддержкой яркости, для цифровых входов `binary_sensor`, для датчиков
`sensor`)
- Поддержка rgb+w лент как с использованием диммеров, так и адресных лент на чипах ws28xx и подобных,
[подробнее про rgbw](https://github.com/andvikt/mega_hacs/wiki/rgbw)
- Плавное диммирование для любых диммируемых объектов (в том числе с аппаратной поддержкой и без),
[подробнее про smooth](https://github.com/andvikt/mega_hacs/wiki/smooth)
- Возможность работы с несколькими megad
- Обратная связь по [http](https://github.com/andvikt/mega_hacs/wiki/http)
будет выключена в версиях >= 1.0.0, тк в нем нет необходимости)
- Автоматическое восстановление состояний выходов после перезагрузки контроллера
- Автоматическое добавление/изменение объектов после перезагрузки контроллера
- [События](https://github.com/andvikt/mega_hacs/wiki/События) на двойные/долгие нажатия
- Команды выполняются друг за другом без конкурентного доступа к ресурсам megad, это дает гарантии надежного исполнения
большого кол-ва команд (например в сценах). Каждая следующая команда отправляется только после получения ответа о
выполнении предыдущей.
- поддержка [ds2413](https://www.ab-log.ru/smart-house/ethernet/megad-2w) в том числе несколько шиной (начиная с версии 0.4.1)
- поддержка расширителей MegaD-16I-XT, MegaD-16R-XT, MegaD-16PWM (начиная с версии 0.5.1)
- поддержка всех возможных датчиков в режиме I2C-ANY, полный список поддерживаемых датчиков
[по ссылке](https://github.com/andvikt/mega_hacs/wiki/i2c) (начиная с версии 0.6.1)
## Установка
Рекомендованный способ с поддержкой обновлений - [HACS](https://hacs.xyz/docs/installation/installation):
HACS - Integrations - Explore, в поиске ищем MegaD.
Чтобы включить возможность использования бета-версий, зайдите в HACS, найдите интеграцию MegaD, нажмите три точки,
там кнопка "переустановить" или reinstall, дальше нужно нажать галку "показывать бета-версии"
Обновления выполняются так же в меню HACS.
Информация об обновлениях приходит с некоторым интервалом, чтобы вручную проверить наличие обновлений
нажмите три точки возле интеграции в меню HACS и нажмите `обновить информацию`
Альтернативный способ установки:
```shell
# из папки с конфигом
wget -q -O - https://raw.githubusercontent.com/andvikt/mega_hacs/master/install.sh | bash -
```
Не забываем перезагрузить HA
## Настройка
`Настройки` -> `Интеграции` -> `Добавить интеграцию` в поиске ищем mega
Все имеющиеся у вас порты будут настроены автоматически. Вы можете менять названия, иконки и entity_id так же из интерфейса.
В самой меге необходимо прописать настройки:
```yaml
srv: "192.168.1.4:8123" # ip:port вашего HA
script: "mega" # это api интеграции, к которому будет обращаться контроллер
```
Так же необходимо настроить Mega-ID в настройках контроллера, для каждой меги id должен быть разным.
При любых изменениях настроек контроллера (типы входов, id и тд) необходимо в настройках интеграции нажать `Обновить
объекты`
## Зависимости
Для максимальной скорости реакции на команды сервера, рекомендуется выключить `имитацию http-ответа` в
настройках интеграции и настроить proxy_pass к HA, самый простой способ сделать это - воспользоваться
[специальным аддоном](https://github.com/andvikt/mega_addon/tree/master/mega-proxy)
Обновить ваш контроллер до последней версии, обновление прошивки MegaD можно делать
из HA с помощью [аддона](https://github.com/andvikt/mega_addon.git)
Подробная документация по [ссылке](https://github.com/andvikt/mega_hacs/wiki)