Compare commits

...
286 Commits
Author SHA1 Message Date
Koen Kanters f2e39af12c 1.18.3 2021-05-01 14:52:07 +02:00
Koen Kanters 111d4d70bd Undev 2021-05-01 14:52:01 +02:00
github-actions[bot]andKoenkk fae2e4851c Update zigbee-herdsman-converters to 14.0.131 (#7276)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-05-01 14:15:31 +02:00
43ff2daebe Update zigbee-herdsman-converters to 14.0.129 (#7275)
* Update zigbee-herdsman-converters to 14.0.129

* Update homeassistant.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-05-01 13:56:44 +02:00
github-actions[bot]andnurikk 19373d815f Update zigbee2mqtt-frontend to 0.4.0 (#7271)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-05-01 11:35:36 +08:00
Koen Kanters e9a5ec1a58 Revert "Home Assistant: remove attributes from discovered entities. https://github.com/Koenkk/zigbee2mqtt/discussions/7249"
This reverts commit 54ab0a3f98.
2021-04-30 16:41:18 +02:00
Koen Kanters ec97931ee4 Home Assistant: don't counter action: null payload. 2021-04-30 16:41:00 +02:00
github-actions[bot]andnurikk 20c0712aba Update zigbee2mqtt-frontend to 0.3.121 (#7247)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-04-30 09:15:36 +08:00
github-actions[bot]andKoenkk 2e5e5beb83 Update zigbee-herdsman-converters to 14.0.128 (#7251)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-29 20:03:24 +02:00
Koen Kanters 54ab0a3f98 Home Assistant: remove attributes from discovered entities. https://github.com/Koenkk/zigbee2mqtt/discussions/7249 2021-04-29 19:43:54 +02:00
Koen Kanters d61d6e28f2 Only attempt to configure end device when message is received from it. (#7242) 2021-04-29 18:18:51 +02:00
ssierglandKoen Kanters 1d8b37eee0 Properly handle SIGTERM and other kernel events (#7230)
* Properly handle SIGTERM and other kernel events

see

https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals

* Update Dockerfile

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-04-29 18:13:41 +02:00
github-actions[bot]andKoenkk 2b488ccff4 Update zigbee-herdsman-converters to 14.0.127 (#7241)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-28 20:06:27 +02:00
cedbd1fd4c Update zigbee-herdsman-converters to 14.0.126 (#7240)
* Update zigbee-herdsman-converters to 14.0.126

* Update bind.js

* Update networkMap.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-04-28 19:48:08 +02:00
Koen Kanters f27e5efe0f Fix Home Assistant "KeyError: 'color_mode'" for brightness only lights. https://github.com/Koenkk/zigbee2mqtt/issues/7220 2021-04-28 19:10:39 +02:00
github-actions[bot]andKoenkk 7eeaeafbd0 Update zigbee-herdsman-converters to 14.0.125 (#7221)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-27 16:27:58 +02:00
github-actions[bot]andKoenkk e4d868652a Update zigbee-herdsman-converters to 14.0.124 (#7216)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-26 21:42:48 +02:00
71d77ac27c Update zigbee-herdsman-converters to 14.0.121 (#7213)
* Update zigbee-herdsman-converters to 14.0.121

* Update bridge.test.js

* Update bridge.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-04-26 16:34:08 +02:00
github-actions[bot]andKoenkk 1f80a05477 Update zigbee-herdsman to 0.13.92 (#7211)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-26 11:35:25 +02:00
github-actions[bot]andKoenkk a02c8d242a Update zigbee-herdsman-converters to 14.0.120 (#7205)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-25 19:26:15 +02:00
daxy01andKoen Kanters edc18e3376 Add log_symlink_current option (#7166)
* Update logger.js

Adding a symlink from "log/<timestamp>" to "log/current" to be able to always view the last logfile using "log/current/log.txt".

* Update logger.js

* Update logger.js

Create directory first to prevent startup failure

* Update logger.js

Fixing check on startup

* Update settings.js

* Update logger.js

* Update settings.schema.json

* Update bridge.test.js

* Update logger.test.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-04-25 19:21:38 +02:00
github-actions[bot]andKoenkk 1f797e5e3a Update zigbee-herdsman-converters to 14.0.119 (#7204)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-25 18:55:26 +02:00
Koen Kanters aee4e96619 Attempt to fix https://github.com/Koenkk/zigbee2mqtt/issues/7198 2021-04-25 15:27:40 +02:00
Koen Kanters 7631c191c5 Home Assistant discovery: add color_mode for lights (#7057)
* Home Assistant add color_mode.

* Fix tests

* Fix coverage
2021-04-25 14:56:59 +02:00
github-actions[bot]andKoenkk 8802354ca9 Update dependencies (#7199)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-25 14:30:39 +02:00
github-actions[bot]andKoenkk 627b94e5ab Update zigbee-herdsman-converters to 14.0.118 (#7192)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-24 18:56:42 +02:00
github-actions[bot]andnurikk 8ed842603c Update zigbee2mqtt-frontend to 0.3.120 (#7190)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-04-24 21:58:41 +08:00
github-actions[bot]andKoenkk 6904020f7d Update zigbee-herdsman-converters to 14.0.117 (#7182)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-23 20:44:47 +02:00
d3fd3bcacc Update zigbee-herdsman-converters to 14.0.116 (#7169)
* Fix state of members from group incorrect when toZigbee converter reads a member state. #7038

* Update zigbee-herdsman-converters to 14.0.116

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-22 20:34:13 +02:00
Koen Kanters 366c5fcb4d Fix state of members from group incorrect when toZigbee converter reads a member state. #7038 2021-04-22 20:28:54 +02:00
Koen Kanters 2c44f4ede9 Test case for #7106 2021-04-22 19:20:03 +02:00
Koen Kanters 3f0ef8e08f Improve utils.getObjectProperty() 2021-04-22 19:19:17 +02:00
github-actions[bot]andKoenkk 01af249e95 Update zigbee-herdsman-converters to 14.0.115 (#7162)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-21 19:56:15 +02:00
github-actions[bot]andKoenkk a1e6439a5c Update zigbee-herdsman-converters to 14.0.114 (#7148)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-20 18:42:49 +02:00
github-actions[bot]andKoenkk ba305ad18b Update zigbee-herdsman-converters to 14.0.113 (#7134)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-19 20:17:37 +02:00
pklokke 95dd6c485c Support binding of server clusters towards clients (#7127)
This allows binding of devices such as sleepy temperature sensors to heating actuators
2021-04-19 20:16:31 +02:00
github-actions[bot]andKoenkk dbfdbcba4a Update zigbee-herdsman to 0.13.91 (#7122)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-18 20:43:42 +02:00
github-actions[bot]andKoenkk 14cc33cd44 Update zigbee-herdsman-converters to 14.0.112 (#7121)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-18 20:32:21 +02:00
xyzroeandKoen Kanters 9a53b954f8 Add wifi endpoint name (#7103)
* Update utils.js

added endpointNames wifi, ds, bme
wifi for future devices
ds and bme are using in DIYRuZ_Flower

* Update utils.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-04-18 20:25:18 +02:00
github-actions[bot]andKoenkk 615026984c Update dependencies (#7110)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-18 19:40:27 +02:00
github-actions[bot]andKoenkk bd660dee82 Update zigbee-herdsman-converters to 14.0.111 (#7105)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-17 16:13:09 +02:00
HeddaandKoen Kanters 75dc72550f Add 57600 as another common baud rate speed example (#7094)
* Add 57600 as another common board date example

Add 57600 as another common board date example to avoid confusions like in https://github.com/Koenkk/zigbee-herdsman/issues/319

* Spelling and grammar

Spelling and grammar

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-04-17 14:45:48 +02:00
github-actions[bot]andKoenkk c7443191ff Update zigbee-herdsman-converters to 14.0.110 (#7089)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-15 17:27:08 +02:00
Koen Kanters dff4af94f5 Home Assistant: fix device_automation not published when homeassistant is in device options. https://github.com/Koenkk/zigbee2mqtt/issues/7074 2021-04-14 20:22:26 +02:00
github-actions[bot]andKoenkk a1fc606d99 Update zigbee-herdsman to 0.13.90 (#7076)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-14 16:48:24 +02:00
github-actions[bot]andKoenkk 89ceaac3d1 Update zigbee-herdsman-converters to 14.0.109 (#7072)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-13 21:05:43 +02:00
Koen Kanters 8df8ef629e Fix state of group not changing when state of specific endpoint is changed. https://github.com/Koenkk/zigbee2mqtt/issues/6905 2021-04-13 20:20:44 +02:00
Koen Kanters 07262bd62f Fix device option set to null instead of removed when sending null. https://github.com/nurikk/z2m-frontend/issues/430 2021-04-12 19:49:21 +02:00
Koen Kanters 5963f3b7f4 Home Assistant: set unknown values to null (#7032) 2021-04-12 16:59:41 +02:00
github-actions[bot]andKoenkk 7e861aa3bb Update zigbee-herdsman-converters to 14.0.108 (#7047)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-11 17:54:23 +02:00
github-actions[bot]andKoenkk a4da036266 Update dependencies (#7040)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-11 13:04:15 +02:00
Koen Kanters 487ab0648d Improve logging 2021-04-10 18:51:13 +02:00
github-actions[bot]andKoenkk e91a23b3ed Update zigbee-herdsman-converters to 14.0.107 (#7022)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-09 22:33:05 +02:00
ptvoandptvoinfo d5d16d1616 Update schema with the Icon device property. (#7018)
* Device-specific icon

* Updated tests.

* Updated test

* Updated tests

* Updated tests.

* Updated tests

* Update schema with the Icon device property.

* Fix

Co-authored-by: ptvoinfo <xagga@ya.ru>
2021-04-09 20:52:41 +08:00
ptvoandptvoinfo e81ac11466 Allow to redefine device icon (#7013)
* Device-specific icon

* Updated tests.

* Updated test

* Updated tests

* Updated tests.

* Updated tests

Co-authored-by: ptvoinfo <xagga@ya.ru>
2021-04-09 19:35:44 +08:00
github-actions[bot]andKoenkk 93c0a8a7ba Update zigbee-herdsman-converters to 14.0.106 (#7003)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-08 21:00:02 +02:00
John Doe 42a4596be6 Add device icon from converter definition (#7001) 2021-04-08 22:57:55 +08:00
github-actions[bot]andnurikk 53772e5536 Update zigbee2mqtt-frontend to 0.3.119 (#6998)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-04-08 21:57:42 +08:00
github-actions[bot]andKoenkk 2446e173b6 Update zigbee-herdsman-converters to 14.0.105 (#6990)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-07 19:28:18 +02:00
Koen Kanters 30efb3e824 Fix "Cannot read property 'ID' of undefined". https://github.com/Koenkk/zigbee2mqtt/issues/6981 2021-04-07 18:39:54 +02:00
ab0ae8ce91 Update zigbee-herdsman-converters to 14.0.104 (#6975)
* Update zigbee-herdsman-converters to 14.0.104

* Update bridge.test.js

* Update otaUpdate.test.js

* Update zigbeeHerdsman.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-04-06 22:24:30 +02:00
github-actions[bot]andKoenkk 172689976e Update zigbee-herdsman-converters to 14.0.103 (#6968)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-05 21:06:24 +02:00
Koen Kanters bf47282ea7 Fix optimistic group state property not postfixed with endpoint name when necessary. https://github.com/Koenkk/zigbee2mqtt/issues/6905 2021-04-04 21:00:24 +02:00
github-actions[bot]andnurikk ec8348854b Update zigbee2mqtt-frontend to 0.3.118 (#6952)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-04-04 17:24:22 +08:00
github-actions[bot]andKoenkk 348c562f97 Update zigbee-herdsman to 0.13.89 (#6953)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-04 10:45:20 +02:00
github-actions[bot]andKoenkk 92bd2166e3 Update dependencies (#6948)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-04-04 10:35:29 +02:00
github-actions[bot]andnurikk d6a1d2deb5 Update zigbee2mqtt-frontend to 0.3.115 (#6938)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-04-03 15:54:29 +08:00
Koen Kanters f98d71ba6e Promote to dev 2021-04-01 09:08:03 +02:00
Koen Kanters abd8a09264 1.18.2 2021-04-01 09:07:24 +02:00
Koen Kanters 9bfbd25bd8 Remove -dev 2021-04-01 09:07:21 +02:00
Koen Kanters afc2b2ea95 Merge branch 'dev' 2021-04-01 09:06:39 +02:00
6bf27b9976 Update zigbee-herdsman-converters to 14.0.102 (#6903)
* Update zigbee-herdsman-converters to 14.0.102

* Update publish.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-03-31 18:30:08 +02:00
github-actions[bot]andKoenkk 29c8a179e3 Update zigbee-herdsman-converters to 14.0.101 (#6888)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-30 19:36:23 +02:00
github-actions[bot]andKoenkk be906fec76 Update zigbee-herdsman to 0.13.88 (#6882)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-30 17:52:09 +02:00
github-actions[bot]andnurikk 9aacf32ac7 Update zigbee2mqtt-frontend to 0.3.114 (#6880)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-30 16:36:00 +08:00
github-actions[bot]andKoenkk 010a140835 Update zigbee-herdsman-converters to 14.0.100 (#6875)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-29 21:17:29 +02:00
Koen Kanters 0b72de7056 Log FAQ on failed to start Zigbee 2021-03-29 20:15:47 +02:00
github-actions[bot]andKoenkk ddbfa02d5c Update zigbee-herdsman-converters to 14.0.99 (#6860)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-28 13:45:45 +02:00
github-actions[bot]andKoenkk cd563bffd5 Update dependencies (#6857)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-28 13:13:00 +02:00
github-actions[bot]andKoenkk 306e1b6391 Update zigbee-herdsman-converters to 14.0.98 (#6855)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-27 20:09:50 +01:00
github-actions[bot]andKoenkk f8f79d3d18 Update zigbee-herdsman to 0.13.87 (#6850)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-26 21:56:11 +01:00
github-actions[bot]andKoenkk 58bc53683d Update zigbee-herdsman-converters to 14.0.97 (#6846)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-26 21:21:51 +01:00
github-actions[bot]andKoenkk 41a4dea208 Update zigbee-herdsman to 0.13.86 (#6843)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-26 18:50:42 +01:00
github-actions[bot]andKoenkk e56f6b602e Update zigbee-herdsman-converters to 14.0.96 (#6830)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-25 19:42:32 +01:00
github-actions[bot]andKoenkk fda0270343 Update zigbee-herdsman-converters to 14.0.95 (#6821)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-24 19:23:13 +01:00
0493cf60da Fix no transaction in group and bind responses (#6810)
* Fix no transaction property in /group/members response

* Update groups.js

* Update group.test.js

* Update bind.js

* Update bind.test.js

Co-authored-by: Guillaume COURTOIS <guillaume.courtois@victeon.fr>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-03-24 19:17:49 +01:00
github-actions[bot]andKoenkk 98b98ddf8f Update zigbee-herdsman to 0.13.85 (#6820)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-24 19:16:36 +01:00
github-actions[bot]andnurikk 171e87d6ba Update zigbee2mqtt-frontend to 0.3.113 (#6819)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-24 15:56:21 +00:00
github-actions[bot]andnurikk 6d19579a70 Update zigbee2mqtt-frontend to 0.3.111 (#6795)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-23 22:42:14 +00:00
766ab452cc Update zigbee-herdsman-converters to 14.0.94 (#6806)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-03-23 19:13:16 +01:00
github-actions[bot]andKoenkk add5179fc8 Update zigbee-herdsman to 0.13.84 (#6805)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-23 19:08:29 +01:00
github-actions[bot]andKoenkk e66e2cbc04 Update zigbee-herdsman-converters to 14.0.93 (#6792)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-23 16:25:34 +01:00
github-actions[bot]andKoenkk 9c6ed20d6c Update zigbee-herdsman to 0.13.83 (#6791)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-22 20:30:40 +01:00
Koen Kanters b6085fceae Add buymeacoffee 2021-03-22 20:28:15 +01:00
Koen Kanters 326823ee00 Add permit_join_timeout to bridge/info 2021-03-22 17:03:01 +01:00
github-actions[bot]andKoenkk 4d2eaa882b Update zigbee-herdsman to 0.13.82 (#6787)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-22 17:02:06 +01:00
github-actions[bot]andKoenkk f6e7bec357 Update zigbee-herdsman-converters to 14.0.92 (#6782)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-21 21:23:16 +01:00
github-actions[bot]andKoenkk 9b1772824b Update dependencies (#6773)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-21 13:17:38 +01:00
github-actions[bot]andKoenkk 840e97edd9 Update zigbee-herdsman-converters to 14.0.91 (#6771)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-20 19:21:51 +01:00
github-actions[bot]andnurikk a2f847e370 Update zigbee2mqtt-frontend to 0.3.110 (#6768)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-20 15:13:38 +08:00
github-actions[bot]andnurikk c255625131 Update zigbee2mqtt-frontend to 0.3.109 (#6767)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-20 13:52:12 +08:00
github-actions[bot]andnurikk 5df8d1c4c0 Update zigbee2mqtt-frontend to 0.3.108 (#6766)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-20 11:40:58 +08:00
github-actions[bot]andKoenkk 04ebe253bb Update zigbee-herdsman-converters to 14.0.90 (#6764)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-19 19:36:39 +01:00
Jorge Schrauwen 3541db891d groups: also publish color_mode (#6756) 2021-03-19 17:48:56 +01:00
github-actions[bot]andKoenkk fb4f88295c Update zigbee-herdsman-converters to 14.0.89 (#6754)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-18 18:20:41 +01:00
github-actions[bot]andnurikk 8c6bb7383c Update zigbee2mqtt-frontend to 0.3.107 (#6752)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-18 23:51:39 +08:00
5a37400408 Update zigbee-herdsman-converters to 14.0.88 (#6745)
* Update zigbee-herdsman-converters to 14.0.88

* Update group.test.js

* Update publish.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-03-17 22:19:26 +01:00
Koen Kanters 1acccc002a Only publish changed properties for groups and its members. #6718 2021-03-17 21:44:29 +01:00
github-actions[bot]andKoenkk 144fdb43da Update zigbee-herdsman to 0.13.81 (#6744)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-17 20:14:06 +01:00
Franck Nijhof a17f3b1c5a Fix use of deprecated values in HA Cover platform discovery (#6544) 2021-03-17 16:48:50 +01:00
github-actions[bot]andKoenkk a9b5d5ea58 Update zigbee-herdsman-converters to 14.0.87 (#6735)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-16 20:32:33 +01:00
b85825ba4a Update zigbee-herdsman-converters to 14.0.86 (#6711)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-03-14 17:11:53 +01:00
Koen Kanters 06a14c5b20 Prepare for https://github.com/Koenkk/zigbee2mqtt/pull/6711 2021-03-14 17:09:43 +01:00
github-actions[bot]andKoenkk 1f09b853d2 Update zigbee-herdsman to 0.13.80 (#6710)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-14 16:55:02 +01:00
github-actions[bot]andKoenkk 953e0e7f12 Update dependencies (#6700)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-14 16:33:35 +01:00
github-actions[bot]andnurikk 7b54d188f4 Update zigbee2mqtt-frontend to 0.3.85 (#6703)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-14 16:33:50 +08:00
John Doe 39239d1416 Revert "Add additionalProperties to device schema (#6687)" (#6692)
This reverts commit eb930ac544.
2021-03-13 20:59:44 +08:00
github-actions[bot]andnurikk 036aab2e2e Update zigbee2mqtt-frontend to 0.3.84 (#6688)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-13 18:48:08 +08:00
John Doe eb930ac544 Add additionalProperties to device schema (#6687) 2021-03-13 11:22:23 +01:00
29343f8e9c Update zigbee-herdsman-converters to 14.0.85 (#6680)
* Update zigbee-herdsman-converters to 14.0.85

* Update bridge.test.js

* Update zigbeeHerdsman.js

* Update otaUpdate.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-03-13 10:28:48 +01:00
github-actions[bot]andnurikk 0a2466e55b Update zigbee2mqtt-frontend to 0.3.82 (#6682)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-13 16:40:25 +08:00
github-actions[bot]andnurikk 07c02f94da Update zigbee2mqtt-frontend to 0.3.81 (#6681)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-13 11:38:35 +08:00
github-actions[bot]andKoenkk f70e1b343d Update zigbee-herdsman to 0.13.79 (#6679)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-12 20:16:59 +01:00
Koen Kanters 26d4f3d70b Changes for https://github.com/Koenkk/zigbee2mqtt/pull/6669. 2021-03-11 20:11:34 +01:00
github-actions[bot]andKoenkk 581145eae2 Update zigbee-herdsman-converters to 14.0.84 (#6669)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-11 20:11:17 +01:00
github-actions[bot]andKoenkk 50cbc85da6 Update zigbee-herdsman to 0.13.78 (#6668)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-11 20:00:36 +01:00
df670a872e Update zigbee-herdsman-converters to 14.0.83 (#6659)
* Update zigbee-herdsman-converters to 14.0.83

* Update bridge.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-03-10 23:04:53 +01:00
github-actions[bot]andKoenkk 6163265323 Update zigbee-herdsman to 0.13.77 (#6658)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-10 22:43:47 +01:00
github-actions[bot]andKoenkk 20e60ba812 Update zigbee-herdsman-converters to 14.0.81 (#6639)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-09 20:34:58 +01:00
Koen Kanters 00c2894bb4 Reread settings on restart. https://github.com/Koenkk/zigbee2mqtt/discussions/6628 2021-03-09 19:50:05 +01:00
github-actions[bot]andKoenkk f10b0e3070 Update zigbee-herdsman-converters to 14.0.80 (#6627)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-08 19:30:33 +01:00
github-actions[bot]andnurikk ece9e03fba Update zigbee2mqtt-frontend to 0.3.80 (#6624)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-08 12:34:08 +08:00
github-actions[bot]andKoenkk 87f0408bd5 Update zigbee-herdsman-converters to 14.0.79 (#6619)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-07 17:43:30 +01:00
github-actions[bot]andKoenkk 46848cb5c9 Update dependencies (#6610)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-07 17:25:17 +01:00
John Doe 0ca834cc70 Add timer api to extensions sandbox (#6604) 2021-03-06 17:41:11 +01:00
github-actions[bot]andKoenkk 0d60ae2f58 Update zigbee-herdsman to 0.13.76 (#6605)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-06 17:40:46 +01:00
github-actions[bot]andKoenkk 2712d98871 Update zigbee-herdsman-converters to 14.0.78 (#6603)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-06 17:27:01 +01:00
Koen Kanters 377356b9c1 Fix no attribute property in configured_reportings when attribute is manufacturer specific. #6580 2021-03-06 14:51:07 +01:00
github-actions[bot]andnurikk 6fa5f75510 Update zigbee2mqtt-frontend to 0.3.79 (#6592)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-06 15:34:03 +08:00
github-actions[bot]andKoenkk 199d970947 Update zigbee-herdsman-converters to 14.0.77 (#6566)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-04 17:56:49 +01:00
Koen Kanters 42f2bd496d Merge branch 'master' into dev
# Conflicts:
#	npm-shrinkwrap.json
#	package.json
2021-03-04 17:28:52 +01:00
github-actions[bot]andKoenkk 5508b6cc60 Update zigbee-herdsman-converters to 14.0.76 (#6557)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-04 17:14:22 +01:00
github-actions[bot]andKoenkk 92a374e63c Update zigbee-herdsman to 0.13.75 (#6553)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-03 17:17:53 +01:00
Jorge Schrauwen e5a9c947aa Home Assistant does not support 'sleep' as system_mode (#6549)
But 'sleep' is a valid ZCL system_mode, so far only the Viessmann TRV supports it.
We simply drop the exposed system_mode value to not confuse homeassistent.
2021-03-03 16:59:03 +01:00
github-actions[bot]andnurikk c97fce71f7 Update zigbee2mqtt-frontend to 0.3.78 (#6546)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-03 21:25:38 +08:00
github-actions[bot]andnurikk 1a266637c5 Update zigbee2mqtt-frontend to 0.3.77 (#6545)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-03-03 21:14:55 +08:00
Koen Kanters a43d1f3917 1.18.1 2021-03-02 21:42:12 +01:00
Koen Kanters 8b9dfb3ee3 Fix not starting with CC2652RB. https://github.com/Koenkk/zigbee2mqtt/issues/6517 2021-03-02 21:42:02 +01:00
github-actions[bot]andKoenkk b9c69f96fd Update zigbee-herdsman to 0.13.74 (#6534)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-02 21:03:43 +01:00
Koen Kanters 5ed30eb005 Promote to dev 2021-03-02 20:42:24 +01:00
Koen Kanters 7e52087c73 Merge branch 'master' into dev 2021-03-02 20:41:40 +01:00
Koen Kanters 4100ebb670 Update update_check_interval in settings.schema.json. https://github.com/Koenkk/zigbee2mqtt/commit/fb2ab44e3e28a242ae12bbaf78864f470910dc62 2021-03-02 20:24:27 +01:00
Koen Kanters 52b8022173 Revert "Update update_check_interval in settings.schema.json. https://github.com/Koenkk/zigbee2mqtt/commit/fb2ab44e3e28a242ae12bbaf78864f470910dc62"
This reverts commit e552b76b04.
2021-03-02 20:24:10 +01:00
Ilya Kirov d77231b99f Added "ezsp" adapter to schema (#6532) 2021-03-02 19:41:56 +01:00
github-actions[bot]andKoenkk 1341973f07 Update zigbee-herdsman to 0.13.73 (#6533)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-02 19:41:38 +01:00
Koen Kanters e552b76b04 Update update_check_interval in settings.schema.json. https://github.com/Koenkk/zigbee2mqtt/commit/fb2ab44e3e28a242ae12bbaf78864f470910dc62 2021-03-02 19:31:21 +01:00
John Doe fb2ab44e3e Set default update_check_interval to 1 day (#6523) 2021-03-02 19:22:39 +01:00
github-actions[bot]andKoenkk 827f9cdcdf Update zigbee-herdsman-converters to 14.0.75 (#6510)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-03-01 18:58:49 +01:00
Koen Kanters 3fd66193d7 1.18.0 2021-03-01 17:22:17 +01:00
Koen Kanters 2e80a9422f Undev 2021-03-01 17:22:11 +01:00
Koen Kanters 82ccf0290f Merge remote-tracking branch 'origin/master' into dev 2021-03-01 17:21:26 +01:00
ptvoandptvoinfo 18269d65f8 Added support for 16-channels DIY devices. (#6508)
* Added support for 16-channels DIY devices.

* Fixed a typo

Co-authored-by: ptvoinfo <xagga@ya.ru>
2021-03-01 21:39:45 +08:00
github-actions[bot]andnurikk 57165e3d71 Update zigbee2mqtt-frontend to 0.3.76 (#6498)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-28 22:18:40 +08:00
github-actions[bot]andKoenkk 12c34a8ed7 Update zigbee-herdsman-converters to 14.0.74 (#6496)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-28 11:33:07 +01:00
github-actions[bot]andKoenkk 9d835ff9dc Update zigbee-herdsman to 0.13.72 (#6495)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-28 11:15:03 +01:00
github-actions[bot]andKoenkk 744dd47336 Update dependencies (#6485)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-28 11:03:45 +01:00
github-actions[bot]andnurikk fa5a7b19af Update zigbee2mqtt-frontend to 0.3.75 (#6490)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-28 13:46:51 +08:00
John Doe 4ca3aabf49 Add remove extension API (#6489)
* Add remove extension API

* Fix type
2021-02-28 13:44:54 +08:00
github-actions[bot]andnurikk 1a56874b65 Update zigbee2mqtt-frontend to 0.3.74 (#6488)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-28 11:56:37 +08:00
1ddd2ac30a Update zigbee-herdsman-converters to 14.0.73 (#6483)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-27 21:40:05 +01:00
Koen Kanters b2e28acc79 Fix test for new zhc version 2021-02-27 21:37:58 +01:00
github-actions[bot]andKoenkk 95b99a3ece Update zigbee-herdsman-converters to 14.0.72 (#6478)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-27 17:43:31 +01:00
github-actions[bot]andnurikk a8cd925c6a Update zigbee2mqtt-frontend to 0.3.73 (#6475)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-27 18:12:32 +08:00
github-actions[bot]andnurikk 1f60c1733f Update zigbee2mqtt-frontend to 0.3.72 (#6474)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-27 17:06:54 +08:00
github-actions[bot]andnurikk cd71a9d918 Update zigbee2mqtt-frontend to 0.3.70 (#6471)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-27 16:48:57 +08:00
John Doe 26326f71a6 stateChange event brodcasts complete entity state (#6473) 2021-02-27 09:46:33 +01:00
github-actions[bot]andnurikk cb7ac97f79 Update zigbee2mqtt-frontend to 0.3.66 (#6451)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-27 13:34:20 +08:00
github-actions[bot]andKoenkk efc4428a7c Update zigbee-herdsman-converters to 14.0.71 (#6463)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-26 19:34:58 +01:00
github-actions[bot]andKoenkk 212ed66139 Update zigbee-herdsman-converters to 14.0.70 (#6454)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-25 22:17:11 +01:00
github-actions[bot]andKoenkk 88a105ede9 Update zigbee-herdsman-converters to 14.0.69 (#6440)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-24 21:43:48 +01:00
github-actions[bot]andKoenkk 428c9c581c Update zigbee-herdsman-converters to 14.0.68 (#6436)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-24 18:16:26 +01:00
Jorge Schrauwen 2ed9582d6b Add meter endpoint name (#6427)
Prerequisite for https://github.com/Koenkk/zigbee-herdsman-converters/pull/2273
2021-02-24 17:14:45 +01:00
dae989bbf2 Update zigbee-herdsman-converters to 14.0.66 (#6426)
* Update zigbee-herdsman-converters to 14.0.66

* Update bridge.test.js

* Update networkMap.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-23 20:00:13 +01:00
Koen Kanters 3768504480 Fix user extensions api topic 2021-02-23 19:27:30 +01:00
github-actions[bot]andnurikk 6346e07532 Update zigbee2mqtt-frontend to 0.3.64 (#6421)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-23 21:07:46 +08:00
John Doe 2d0b18c17b Simplify user extensions api (#6420)
* Simplify user extensions api

* Change structure
2021-02-23 21:00:45 +08:00
github-actions[bot]andKoenkk 9badca362a Update zigbee-herdsman-converters to 14.0.65 (#6417)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-22 20:45:50 +01:00
declanshanaghyandKoen Kanters 7f9dc04112 Customize icons for accelerometer axes (#6355)
* Publish Docker images to Github container registry. #6326

* Customize icons for accelerometer axes

* Remove fallback on '-' for unit_of_measurement

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-22 19:37:43 +01:00
github-actions[bot]andnurikk d5b2486772 Update zigbee2mqtt-frontend to 0.3.62 (#6410)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-22 21:45:01 +08:00
John DoeandKoen Kanters 028792e5b4 Implement extension loading api (#6378)
* Implement extension loading api

* Fix frontend tests

* Implement saving and unloading extensions

* Save after loading

* Add tests

* Fix coverage

* Refactor frontent test calls

* Update controller.js

* Review fixes

* Update utils.js

* Rename list topic

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-22 21:29:58 +08:00
github-actions[bot]andKoenkk 0c011e0a97 Update zigbee-herdsman-converters to 14.0.64 (#6401)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-21 21:05:27 +01:00
Koen Kanters 049deaf590 Change +- to ≈ for OTA update message. https://github.com/Koenkk/zigbee2mqtt/issues/6397 2021-02-21 20:47:00 +01:00
github-actions[bot]andKoenkk 20def3ff37 Update zigbee-herdsman-converters to 14.0.63 (#6389)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-21 11:21:34 +01:00
github-actions[bot]andKoenkk 207e84d382 Update dependencies (#6384)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-21 10:47:49 +01:00
github-actions[bot]andKoenkk 51cec634a6 Update zigbee-herdsman-converters to 14.0.62 (#6376)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-19 20:25:28 +01:00
github-actions[bot]andnurikk a16635d475 Update zigbee2mqtt-frontend to 0.3.59 (#6368)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-19 20:15:14 +08:00
github-actions[bot]andnurikk 5953d68916 Update zigbee2mqtt-frontend to 0.3.58 (#6356)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-19 15:40:56 +08:00
github-actions[bot]andKoenkk 32388fb960 Update zigbee-herdsman-converters to 14.0.61 (#6349)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-18 20:52:45 +01:00
github-actions[bot]andnurikk 67bb215894 Update zigbee2mqtt-frontend to 0.3.57 (#6343)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-18 21:19:05 +08:00
Koen Kanters a151d765ca Publish Docker images to Github container registry. #6326 (#6329) 2021-02-17 13:04:58 +01:00
Koen Kanters 6484feecb7 Publish Docker images to Github container registry. #6326 2021-02-17 13:01:59 +01:00
github-actions[bot]andKoenkk 1914673ef7 Update zigbee-herdsman-converters to 14.0.60 (#6323)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-16 20:49:29 +01:00
Timoline f1a473d506 Updated transmit_power description (#6318)
Updated transmit_power description
2021-02-16 19:20:04 +01:00
TimolineandKoen Kanters 6f2210e792 Added syslog settings (#6312)
* Added syslog settings

not perfect yet, should be a dependencie of other setting

* fixed typo

* Update settings.schema.json

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-16 19:09:43 +01:00
github-actions[bot]andnurikk 91173a71b8 Update zigbee2mqtt-frontend to 0.3.55 (#6320)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-16 21:22:06 +08:00
github-actions[bot]andKoenkk 0b459e4075 Update zigbee-herdsman-converters to 14.0.59 (#6307)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-15 20:36:37 +01:00
8eb1a986fd Update zigbee-herdsman-converters to 14.0.58 (#6305)
* Update zigbee-herdsman-converters to 14.0.58

* Update availability.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-15 20:12:51 +01:00
Timoline 0b66e84295 Added examples for MQTT protocol version (#6303) 2021-02-15 19:36:17 +01:00
github-actions[bot]andKoenkk 8d3edf1dd5 Update zigbee-herdsman to 0.13.71 (#6301)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-15 19:30:27 +01:00
Timoline c3ad65688a changed description to minutes (#6280) 2021-02-15 17:59:58 +01:00
Timoline 6ab609cd46 added some examples for channel in schema (#6279) 2021-02-15 17:59:01 +01:00
Timoline e358ec305e Update update_check_interval to type number (#6278) 2021-02-15 17:58:39 +01:00
flatsiedatsieandKoen Kanters 9dd9f33b4a OTA updates: add update_check_interval and disable_automatic_update_check options (#6268)
* OTA privacy enhancement

Adds the option to disallow device-initiated OTA update checks.

Currently these OTA update checks can be quite frequent, and essentially mean the device is indirectly able to ping a cloud server. In the EU it's wise to handle an IP address as personal information, and therefore it might be wise to not enable OTA checking by default. Ideally a user would agree to a privacy policy that clarifies things a bit first.

* Added default settings for OTA update management

* Set update interval in minutes

* OTA update settings

* Adds both settings for OTA updates control

* Update settings.schema.json

* Update settings.js

* Update otaUpdate.js

* Update otaUpdate.test.js

* Update bridge.test.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-14 18:20:32 +01:00
github-actions[bot]andKoenkk 89a9dbba42 Update zigbee-herdsman-converters to 14.0.57 (#6275)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-14 14:30:47 +01:00
github-actions[bot]andKoenkk d1550c7511 Update zigbee-herdsman to 0.13.70 (#6269)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-14 13:54:10 +01:00
github-actions[bot]andKoenkk b34d795b77 Update dependencies (#6263)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-14 11:55:43 +01:00
Andrew OstroumovandKoen Kanters 1df52cc6e8 Prevent devices always being pinged at exactly the same time (#6133)
* Add availability lag of rand 10% from timeout

* Update availability.js

* Update utils.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-14 11:37:31 +01:00
github-actions[bot]andnurikk 1b76cd0ae5 Update zigbee2mqtt-frontend to 0.3.54 (#6265)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-14 16:00:54 +08:00
github-actions[bot]andnurikk 7f60605c09 Update zigbee2mqtt-frontend to 0.3.53 (#6256)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-13 23:51:14 +08:00
github-actions[bot]andnurikk a63b6c3eb4 Update zigbee2mqtt-frontend to 0.3.52 (#6252)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-13 17:03:17 +08:00
github-actions[bot]andnurikk 10fe61d4ff Update zigbee2mqtt-frontend to 0.3.51 (#6248)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-13 11:15:52 +08:00
Koen Kanters 1e37ba1271 Don't log no converter available for genBasic cluster. https://github.com/Koenkk/zigbee-herdsman-converters/issues/2237 2021-02-12 20:46:32 +01:00
Koen Kanters 5986152349 Expose max/min color temp to Home Assistant. https://github.com/Koenkk/zigbee-herdsman-converters/issues/2217 2021-02-12 20:38:06 +01:00
github-actions[bot]andnurikk 545caef6d5 Update zigbee2mqtt-frontend to 0.3.50 (#6246)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-12 23:58:16 +08:00
github-actions[bot]andnurikk e07f065cb5 Update zigbee2mqtt-frontend to 0.3.49 (#6244)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-12 21:47:24 +08:00
github-actions[bot]andnurikk 07f012a577 Update zigbee2mqtt-frontend to 0.3.48 (#6241)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-12 20:22:08 +08:00
github-actions[bot]andKoenkk cf333cacd8 Update zigbee-herdsman-converters to 14.0.56 (#6233)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-11 21:31:49 +01:00
Marius 6cf32b9fb1 Preparing to make org switch for the add-on (#5546) 2021-02-11 18:38:55 +01:00
github-actions[bot]andKoenkk 084d6bad1a Update zigbee-herdsman-converters to 14.0.55 (#6219)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-10 20:30:32 +01:00
github-actions[bot]andKoenkk 2b71b2a19a Update zigbee-herdsman-converters to 14.0.54 (#6216)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-10 18:41:50 +01:00
Koen Kanters b8284370d0 Bind to coordinator when setting-up reporting. https://github.com/Koenkk/zigbee2mqtt/issues/6191 2021-02-10 18:24:37 +01:00
github-actions[bot]andKoenkk c08a8ec230 Update zigbee-herdsman-converters to 14.0.53 (#6200)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-09 20:53:51 +01:00
Koen Kanters 85ed093008 Prep for next zigbee-herdsman-converters 2021-02-09 20:31:48 +01:00
github-actions[bot]andKoenkk 6c5e1cc346 Update zigbee-herdsman to 0.13.68 (#6199)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-09 18:33:00 +01:00
Crowbar ZandKoen Kanters 2515d5f816 Support Tradfri Remote buttons triggering polling (#6192)
* Support Tradfri Remote buttons triggering polling

* Update Tradfri remote test commandStepWithOnOff
Will now poll both LevelCtrl and OnOff in case light turns on

* Update the right test this time

* Updating both tests

* Update debounce test

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-09 17:20:33 +01:00
github-actions[bot]andnurikk 5a5237c39d Update zigbee2mqtt-frontend to 0.3.45 (#6198)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-09 23:36:04 +08:00
github-actions[bot]andnurikk 1d90c32719 Update zigbee2mqtt-frontend to 0.3.44 (#6195)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-09 22:27:10 +08:00
github-actions[bot]andKoenkk d2a62297df Update zigbee-herdsman to 0.13.67 (#6189)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-09 10:50:22 +01:00
github-actions[bot]andnurikk fddada4c1c Update zigbee2mqtt-frontend to 0.3.43 (#6183)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-09 10:04:57 +08:00
github-actions[bot]andKoenkk 1f29f86f49 Update zigbee-herdsman to 0.13.66 (#6173)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-08 17:33:18 +01:00
TimolineandJohn Doe 294bc2aab1 Update settings.schema.json (#6165)
* Update settings.schema.json

Added missing descriptions

* Update settings.schema.json

changed description Adapter delay

Co-authored-by: John Doe <nurikk@users.noreply.github.com>
2021-02-08 13:38:22 +08:00
Timoline 0d1782bfd0 Update settings.schema.json (#6166)
* Update settings.schema.json

Added missing settings legacy_api and homeassistant_legacy_triggers

* Update settings.schema.json

Removed legacy_api, it was already in
2021-02-08 13:34:58 +08:00
github-actions[bot]andKoenkk 1bcc5f1bab Update zigbee-herdsman-converters to 14.0.50 (#6164)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-07 17:59:42 +01:00
github-actions[bot]andKoenkk 036c884452 Update zigbee-herdsman to 0.13.65 (#6163)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-07 17:50:51 +01:00
Timoline 012d7d9d7b Added description for Frontend binding host (#6157)
Added description for Frontend binding host
2021-02-07 17:05:27 +01:00
TimolineandKoen Kanters 5d3d6fc59c Update settings.schema.json (#6159)
* Update settings.schema.json

added setting ikea_ota_use_test_url

* Update settings.schema.json

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-07 17:04:13 +01:00
Koen Kanters f992597392 bridge/request/options: fix exception on null value. 2021-02-07 14:07:27 +01:00
github-actions[bot]andKoenkk e996eb5c7c Update zigbee-herdsman-converters to 14.0.49 (#6155)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-07 13:39:32 +01:00
0e821cc7c8 Fix descriptions for cached state settings (#6152)
Co-authored-by: John Doe <nurikk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-07 12:59:37 +01:00
Timoline 9df40bd9be fixed small typo (#6149) 2021-02-07 12:45:07 +01:00
github-actions[bot]andKoenkk 385531cec7 Update dependencies (#6141)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-07 12:36:32 +01:00
Koen Kanters 687511ec0c Mark as -dev. https://github.com/Koenkk/zigbee2mqtt/pull/6145 2021-02-07 12:34:54 +01:00
github-actions[bot]andnurikk 654e66b280 Update zigbee2mqtt-frontend to 0.3.42 (#6148)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-07 15:08:50 +08:00
Timoline d43b445749 Changed some titles in the settings schema (#6147) 2021-02-07 15:04:18 +08:00
github-actions[bot]andnurikk f673840246 Update zigbee2mqtt-frontend to 0.3.41 (#6146)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-07 11:21:04 +08:00
John Doe 33b6ea86f9 Add frontend config (#6145)
* 1.17.1

* Add frontend config json schema
2021-02-07 11:19:00 +08:00
github-actions[bot]andnurikk f30d9eb276 Update zigbee2mqtt-frontend to 0.3.40 (#6144)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-07 09:53:47 +08:00
github-actions[bot]andnurikk a35ae185ba Update zigbee2mqtt-frontend to 0.3.39 (#6140)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-07 08:24:55 +08:00
69d3843794 Update zigbee-herdsman-converters to 14.0.48 (#6132)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-06 18:32:21 +01:00
Koen Kanters 0b006ae9e7 Fix tests 2021-02-06 18:29:36 +01:00
Koen Kanters b9e0293325 Send offline state to frontend. 2021-02-06 17:50:44 +01:00
github-actions[bot]andnurikk 9114bc91ed Update zigbee2mqtt-frontend to 0.3.38 (#6131)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-07 00:38:44 +08:00
Koen Kanters 5889588a8a Add zigbee2mqtt/bridge/request/options and zigbee2mqtt/bridge/request/restart (#6089)
* Initial

* Update schema

* Log changed options

* Implement zigbee2mqtt/bridge/request/restart

* Implement restart required

* Updates

* Updatos

* Set rtscts: false defaults

* Updates

* Updates
2021-02-06 23:32:20 +08:00
Koen Kanters bf2d793577 Don't log publishes to bridge/info, bridge/devices and bridge/groups to reduce log spam 2021-02-06 15:44:10 +01:00
github-actions[bot]andnurikk 0d7e471fe5 Update zigbee2mqtt-frontend to 0.3.37 (#6127)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-06 22:41:18 +08:00
github-actions[bot]andKoenkk caafca3d6d Update zigbee-herdsman to 0.13.64 (#6110)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-06 13:53:53 +01:00
github-actions[bot]andnurikk 3687863a62 Update zigbee2mqtt-frontend to 0.3.36 (#6116)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-06 13:04:37 +08:00
github-actions[bot]andnurikk e3dde51f8f Update zigbee2mqtt-frontend to 0.3.31 (#6115)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-06 10:00:06 +08:00
github-actions[bot]andnurikk 9add83acdb Update zigbee2mqtt-frontend to 0.3.30 (#6114)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-06 09:20:55 +08:00
github-actions[bot]andKoenkk 11e7aec9c6 Update zigbee-herdsman-converters to 14.0.47 (#6109)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-05 19:45:40 +01:00
github-actions[bot]andKoenkk 27111ef3fd Update zigbee-herdsman to 0.13.63 (#6108)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-05 19:37:55 +01:00
github-actions[bot]andnurikk ade6fabb1f Update zigbee2mqtt-frontend to 0.3.29 (#6103)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-05 23:24:03 +08:00
github-actions[bot]andKoenkk 62e0ef0cff Update zigbee-herdsman to 0.13.62 (#6098)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-05 13:26:29 +01:00
github-actions[bot]andnurikk 7982715502 Update zigbee2mqtt-frontend to 0.3.26 (#6094)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-05 14:38:19 +08:00
github-actions[bot]andnurikk 6c3f4713a1 Update zigbee2mqtt-frontend to 0.3.25 (#6064)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-05 13:38:42 +08:00
github-actions[bot]andKoenkk 341d1853fa Update zigbee-herdsman-converters to 14.0.46 (#6085)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-04 18:19:32 +01:00
Koen Kanters 6c3ac2cd85 Don't allow adding groups with invalid name. #6066 2021-02-04 18:01:44 +01:00
Koen Kanters 836aac46c7 Deprecate retrieve_state option 2021-02-03 20:55:05 +01:00
github-actions[bot]andKoenkk e31de39c52 Update zigbee-herdsman-converters to 14.0.45 (#6072)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-03 18:18:46 +01:00
John Doe 7c1e2c1bbf Log info after changing entity option (#6063) 2021-02-03 12:26:12 +08:00
f042b9df2c Update zigbee-herdsman-converters to 14.0.44 (#6055)
* Update zigbee-herdsman-converters to 14.0.44

* Update availability.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-02-02 22:50:22 +01:00
noned80 ed7f12eb89 Spelling correction of error message (#6056) 2021-02-02 22:44:29 +01:00
github-actions[bot]andKoenkk efd1a2e8ad Update zigbee-herdsman to 0.13.61 (#6053)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-02 20:37:30 +01:00
github-actions[bot]andKoenkk e6c71d79a7 Update zigbee-herdsman-converters to 14.0.43 (#6034)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-01 20:14:55 +01:00
github-actions[bot]andKoenkk ada49fa764 Update zigbee-herdsman to 0.13.60 (#6031)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-01 19:09:05 +01:00
Koen Kanters 50d33a202c Promote to dev 2021-02-01 18:14:39 +01:00
51 changed files with 4148 additions and 2587 deletions
+2 -1
View File
@@ -3,6 +3,7 @@
.git
.github
.gitignore
.idea
.npmrc
.travis*
.travis.yml
@@ -12,4 +13,4 @@ images
node_modules
scripts
test
update.sh
update.sh
+3 -1
View File
@@ -1,2 +1,4 @@
github: [koenkk]
custom: https://www.paypal.me/koenkk
custom:
- https://www.paypal.me/koenkk
- https://www.buymeacoffee.com/koenkk
+1 -1
View File
@@ -7,7 +7,7 @@ contact_links:
url: https://github.com/nurikk/z2m-frontend/issues
about: Issues related to the frontend.
- name: Home Assistant addon issues
url: https://github.com/danielwelch/hassio-zigbee2mqtt/issues
url: https://github.com/zigbee2mqtt/hassio-zigbee2mqtt/issues
about: Issues related to the Home Assistant addon.
- name: FAQ
url: https://www.zigbee2mqtt.io/information/FAQ.html
+5 -11
View File
@@ -31,6 +31,9 @@ jobs:
- name: Docker login
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
run: echo ${{ secrets.DOCKER_KEY }} | docker login -u koenkk --password-stdin
- name: Docker login ghcr.io
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u koenkk --password-stdin
- name: Docker setup - QEMU
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
uses: docker/setup-qemu-action@v1
@@ -50,7 +53,7 @@ jobs:
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest-dev \
-t koenkk/zigbee2mqtt:latest-dev -t ghcr.io/koenkk/zigbee2mqtt:latest-dev \
.
- name: Docker build release
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
@@ -61,22 +64,13 @@ jobs:
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest -t "koenkk/zigbee2mqtt:$TAG" \
-t koenkk/zigbee2mqtt:latest -t "koenkk/zigbee2mqtt:$TAG" -t ghcr.io/koenkk/zigbee2mqtt:latest -t "ghcr.io/koenkk/zigbee2mqtt:$TAG" \
.
- name: Publish to npm
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN }}
- name: Trigger danielwelch/hassio-zigbee2mqtt build
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
curl -s \
--request POST \
--header "Content-Type: application/json" \
--data '{"definition": {"id": 1}}' \
-u ${{ secrets.HASSIO_TRIGGER_SECRET }} \
"https://dev.azure.com/danielwelch2101/hassio-zigbee2mqtt/_apis/build/builds?api-version=5.1"
- name: Trigger zigbee2mqtt/hassio-zigbee2mqtt build
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
+1 -1
View File
@@ -53,7 +53,7 @@ Zigbee2MQTT integrates well with (almost) every home automation solution because
<img align="left" height="100px" width="100px" src="https://user-images.githubusercontent.com/7738048/40914297-49e6e560-6800-11e8-8904-36cce896e5a8.png">
### [Home Assistant](https://www.home-assistant.io/)
- [Home Assistant](https://www.home-assistant.io/hassio/): Using [the official addon](https://github.com/danielwelch/hassio-zigbee2mqtt) from [danielwelch](https://github.com/danielwelch)
- [Home Assistant](https://www.home-assistant.io/hassio/): Using [the official addon](https://github.com/zigbee2mqtt/hassio-zigbee2mqtt)
- Generic install or Hassbian: Using instructions [here](https://www.zigbee2mqtt.io/integration/home_assistant.html)
<img align="left" height="100px" width="100px" src="https://user-images.githubusercontent.com/2734836/47615848-b8dd8700-dabd-11e8-9d77-175002dd8987.png">
+2 -2
View File
@@ -1,7 +1,7 @@
FROM node:14-alpine3.12 as base
WORKDIR /app
RUN apk add --no-cache tzdata eudev
RUN apk add --no-cache tzdata eudev tini
COPY package.json ./
@@ -30,4 +30,4 @@ ARG COMMIT
RUN echo "{\"hash\": \"$COMMIT\"}" > .hash.json
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["npm", "start"]
CMD [ "/sbin/tini", "--", "node", "index.js"]
+48 -26
View File
@@ -1,38 +1,60 @@
const semver = require('semver');
const engines = require('./package.json').engines;
const indexJsRestart = 'indexjs.restart';
const version = engines.node;
if (!semver.satisfies(process.version, version)) {
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`); // eslint-disable-line
let controller;
let stopping = false;
async function restart() {
await stop(indexJsRestart);
await start();
}
// Validate settings
const settings = require('./lib/util/settings');
const errors = settings.validate();
if (errors.length > 0) {
console.log(`\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`);
console.log(' READ THIS CAREFULLY\n');
console.log(`Refusing to start because configuration is not valid, found the following errors:`);
for (const error of errors) {
console.log(`- ${error}`);
async function exit(code, reason) {
if (reason !== indexJsRestart) {
process.exit(code);
}
console.log(`\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/information/configuration.html`);
console.log(`\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n`);
process.exit(1);
}
const Controller = require('./lib/controller');
const controller = new Controller();
controller.start();
async function start() {
const version = engines.node;
if (!semver.satisfies(process.version, version)) {
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`); // eslint-disable-line
}
// Validate settings
const settings = require('./lib/util/settings');
settings.reRead();
const errors = settings.validate();
if (errors.length > 0) {
console.log(`\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`);
console.log(' READ THIS CAREFULLY\n');
console.log(`Refusing to start because configuration is not valid, found the following errors:`);
for (const error of errors) {
console.log(`- ${error}`);
}
console.log(`\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/information/configuration.html`); // eslint-disable-line
console.log(`\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n`);
exit(1);
}
const Controller = require('./lib/controller');
controller = new Controller(restart, exit);
await controller.start();
}
async function stop(reason=null) {
await controller.stop(reason);
}
async function handleQuit() {
if (!stopping && controller) {
stopping = true;
await stop();
}
}
process.on('SIGINT', handleQuit);
process.on('SIGTERM', handleQuit);
let stopping = false;
function handleQuit() {
if (!stopping) {
stopping = true;
controller.stop();
}
}
start();
+27 -25
View File
@@ -4,12 +4,8 @@ const EventBus = require('./eventBus');
const State = require('./state');
const logger = require('./util/logger');
const settings = require('./util/settings');
const objectAssignDeep = require('object-assign-deep');
const utils = require('./util/utils');
const stringify = require('json-stable-stringify-without-jsonify');
const fs = require('fs');
const data = require('./util/data');
const path = require('path');
const assert = require('assert');
// Extensions
@@ -30,29 +26,33 @@ const ExtensionReport = require('./extension/legacy/report');
const ExtensionOnEvent = require('./extension/onEvent');
const ExtensionOTAUpdate = require('./extension/otaUpdate');
const ExtensionExternalConverters = require('./extension/externalConverters');
const ExtensionExternalExtension = require('./extension/externalExtension');
const AllExtensions = [
ExtensionPublish, ExtensionReceive, ExtensionNetworkMap, ExtensionSoftReset, ExtensionHomeAssistant,
ExtensionConfigure, ExtensionDeviceGroupMembership, ExtensionBridgeLegacy, ExtensionBridge, ExtensionGroups,
ExtensionAvailability, ExtensionBind, ExtensionReport, ExtensionOnEvent, ExtensionOTAUpdate,
ExtensionExternalConverters, ExtensionFrontend,
ExtensionExternalConverters, ExtensionFrontend, ExtensionExternalExtension,
];
class Controller {
constructor() {
constructor(restartCallback, exitCallback) {
this.zigbee = new Zigbee();
this.mqtt = new MQTT();
this.eventBus = new EventBus();
this.state = new State(this.eventBus);
this.restartCallback = restartCallback;
this.exitCallback = exitCallback;
this.publishEntityState = this.publishEntityState.bind(this);
this.enableDisableExtension = this.enableDisableExtension.bind(this);
this.onZigbeeAdapterDisconnected = this.onZigbeeAdapterDisconnected.bind(this);
this.addExtension = this.addExtension.bind(this);
// Initialize extensions.
const args = [this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus];
this.extensions = [
new ExtensionBridge(...args, this.enableDisableExtension),
new ExtensionBridge(...args, this.enableDisableExtension, this.restartCallback),
new ExtensionPublish(...args),
new ExtensionReceive(...args),
new ExtensionDeviceGroupMembership(...args),
@@ -89,15 +89,7 @@ class Controller {
if (settings.get().advanced.availability_timeout) {
this.extensions.push(new ExtensionAvailability(...args));
}
const extensionPath = data.joinPath('extension');
if (fs.existsSync(extensionPath)) {
const extensions = fs.readdirSync(extensionPath).filter((f) => f.endsWith('.js'));
for (const extension of extensions) {
const Extension = require(path.join(extensionPath, extension.split('.')[0]));
this.extensions.push(new Extension(...args, settings, logger));
}
}
this.extensions.push(new ExtensionExternalExtension(...args, this.addExtension, this.enableDisableExtension));
}
async start() {
@@ -115,9 +107,11 @@ class Controller {
this.zigbee.on('adapterDisconnected', this.onZigbeeAdapterDisconnected);
} catch (error) {
logger.error('Failed to start zigbee');
// eslint-disable-next-line
logger.error('Check https://www.zigbee2mqtt.io/information/FAQ.html#help-zigbee2mqtt-fails-to-start for possible solutions');
logger.error('Exiting...');
logger.error(error.stack);
process.exit(1);
this.exitCallback(1);
}
// Log zigbee clients on startup
@@ -185,7 +179,11 @@ class Controller {
}
}
async stop() {
addExtension(extension) {
this.extensions.push(extension);
}
async stop(reason=null) {
// Call extensions
await this.callExtensionMethod('stop', []);
@@ -195,10 +193,11 @@ class Controller {
try {
await this.zigbee.stop();
process.exit(0);
logger.info('Stopped Zigbee2MQTT');
this.exitCallback(0, reason);
} catch (error) {
logger.error('Failed to stop zigbee');
process.exit(1);
logger.error('Failed to stop Zigbee2MQTT');
this.exitCallback(1, reason);
}
}
@@ -281,12 +280,9 @@ class Controller {
}
let messagePayload = {...payload};
const currentState = this.state.exists(resolvedEntity.settings.ID) ?
this.state.get(resolvedEntity.settings.ID) : {};
const newState = objectAssignDeep.noMutate(currentState, payload);
// Update state cache with new state.
this.state.set(resolvedEntity.settings.ID, newState, stateChangeReason);
const newState = this.state.set(resolvedEntity.settings.ID, payload, stateChangeReason);
if (settings.get().advanced.cache_state) {
// Add cached state to payload
@@ -334,6 +330,12 @@ class Controller {
resolvedEntity.settings.filtered_attributes.forEach((a) => delete messagePayload[a]);
}
for (const extension of this.extensions) {
if (extension.adjustMessagePayloadBeforePublish) {
extension.adjustMessagePayloadBeforePublish(resolvedEntity, messagePayload);
}
}
if (Object.entries(messagePayload).length) {
const output = settings.get().experimental.output;
if (output === 'attribute_and_json' || output === 'json') {
+9 -1
View File
@@ -14,6 +14,13 @@ const pingableEndDevices = [
];
const Hours25 = 1000 * 60 * 60 * 25;
const AvailabilityLagRatio = 0.1;
function timeoutLag(timeout, ratio) {
const lag = timeout * ratio;
return Math.floor(Math.random() * Math.floor(lag));
}
/**
* This extensions pings devices to check if they are online.
@@ -137,10 +144,11 @@ class Availability extends Extension {
}
setTimerPingable(device) {
const timeout = this.availability_timeout + timeoutLag(this.availability_timeout, AvailabilityLagRatio);
clearTimeout(this.timers[device.ieeeAddr]);
this.timers[device.ieeeAddr] = setTimeout(async () => {
await this.handleIntervalPingable(device);
}, utils.secondsToMilliseconds(this.availability_timeout));
}, utils.secondsToMilliseconds(timeout));
}
async stop() {
Regular → Executable
+26 -5
View File
@@ -8,9 +8,10 @@ const stringify = require('json-stable-stringify-without-jsonify');
const debounce = require('debounce');
const zigbeeHersdman = require('zigbee-herdsman');
const clusterCandidates = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering'];
const clusterCandidates = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering',
'hvacThermostat', 'msTemperatureMeasurement'];
// See zigbee-herdsman-converters devices.js
// See zigbee-herdsman-converters
const defaultBindGroup = {type: 'group_number', ID: 901};
const defaultReportConfiguration = {
@@ -71,6 +72,7 @@ const pollOnMessage = [
{type: 'commandMoveWithOnOff', data: {}},
{type: 'commandStopWithOnOff', data: {}},
{type: 'commandMove', data: {}},
{type: 'commandMoveToLevelWithOnOff', data: {}},
],
},
// Read the following attributes
@@ -85,10 +87,17 @@ const pollOnMessage = [
},
{
cluster: {
genLevelCtrl: [
{type: 'commandStepWithOnOff', data: {}},
{type: 'commandMoveWithOnOff', data: {}},
{type: 'commandStopWithOnOff', data: {}},
{type: 'commandMoveToLevelWithOnOff', data: {}},
],
genOnOff: [
{type: 'commandOn', data: {}},
{type: 'commandOff', data: {}},
{type: 'commandOffWithEffect', data: {}},
{type: 'commandToggle', data: {}},
],
manuSpecificPhilips: [
{type: 'commandHueNotification', data: {button: 1}},
@@ -138,6 +147,7 @@ class Bind extends Extension {
async onMQTTMessage(topic, message) {
const {type, sourceKey, targetKey, clusters} = this.parseMQTTMessage(topic, message);
if (!type) return null;
message = utils.parseJSON(message, message);
let error = null;
const source = this.zigbee.resolveEntity(sourceKey);
@@ -164,11 +174,22 @@ class Bind extends Extension {
// Groups are assumed to support all clusters.
for (const cluster of clusterCandidates) {
if (clusters && !clusters.includes(cluster)) continue;
let matchingClusters = false;
const targetValid = target.type === 'group' || target.type === 'group_number' ||
target.device.type === 'Coordinator' || target.endpoint.supportsInputCluster(cluster);
const anyClusterValid = target.type === 'group' || target.type === 'group_number' ||
target.device.type === 'Coordinator';
if (source.endpoint.supportsOutputCluster(cluster) && targetValid) {
if (!anyClusterValid) {
matchingClusters = ((target.endpoint.supportsInputCluster(cluster) &&
source.endpoint.supportsOutputCluster(cluster)) ||
(source.endpoint.supportsInputCluster(cluster) &&
target.endpoint.supportsOutputCluster(cluster)) );
}
const sourceValid = source.endpoint.supportsInputCluster(cluster) ||
source.endpoint.supportsOutputCluster(cluster);
if ( sourceValid && (anyClusterValid || matchingClusters)) {
logger.debug(`${type}ing cluster '${cluster}' from '${sourceName}' to '${targetName}'`);
attemptedClusters.push(cluster);
+102 -20
View File
@@ -6,15 +6,18 @@ const settings = require('../util/settings');
const Transport = require('winston-transport');
const stringify = require('json-stable-stringify-without-jsonify');
const objectAssignDeep = require(`object-assign-deep`);
const {updatedDiff, addedDiff} = require('deep-object-diff');
const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);
class Bridge extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension) {
constructor(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension, restartCallback) {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.enableDisableExtension = enableDisableExtension;
this.restartCallback = restartCallback;
this.lastJoinedDeviceIeeeAddr = null;
this.setupMQTTLogging();
this.restartRequired = false;
this.requestLookup = {
'device/options': this.deviceOptions.bind(this),
@@ -26,15 +29,17 @@ class Bridge extends Extension {
'group/remove': this.groupRemove.bind(this),
'group/rename': this.groupRename.bind(this),
'permit_join': this.permitJoin.bind(this),
'config/last_seen': this.configLastSeen.bind(this),
'config/homeassistant': this.configHomeAssistant.bind(this),
'config/elapsed': this.configElapsed.bind(this),
'config/log_level': this.configLogLevel.bind(this),
'restart': this.restart.bind(this),
'touchlink/factory_reset': this.touchlinkFactoryReset.bind(this),
'touchlink/identify': this.touchlinkIdentify.bind(this),
'touchlink/scan': this.touchlinkScan.bind(this),
'health_check': this.healthCheck.bind(this),
'options': this.bridgeOptions.bind(this),
// Below are deprecated
'config/last_seen': this.configLastSeen.bind(this),
'config/homeassistant': this.configHomeAssistant.bind(this),
'config/elapsed': this.configElapsed.bind(this),
'config/log_level': this.configLogLevel.bind(this),
};
}
@@ -73,7 +78,9 @@ class Bridge extends Extension {
}
permitJoinChanged(data) {
this.publishInfo();
if (!this.zigbee.isStopping()) {
this.publishInfo();
}
}
async onMQTTMessage(topic, message) {
@@ -109,7 +116,7 @@ class Bridge extends Extension {
if (data.status === 'successful') {
const definition = resolvedEntity.definition;
payload.supported = !!definition;
payload.definition = this.getDefinitionPayload(definition);
payload.definition = this.getDefinitionPayload(definition, resolvedEntity);
}
} else if (type === 'deviceAnnounce') {
payload = {
@@ -142,7 +149,46 @@ class Bridge extends Extension {
}
async bridgeOptions(message) {
return utils.getResponse(message, {}, 'Saving settings is not yet implemented');
if (typeof message !== 'object' || typeof message.options !== 'object') {
throw new Error(`Invalid payload`);
}
const diffUpdated = updatedDiff(settings.get(), message.options);
const diffAdded = addedDiff(settings.get(), message.options);
const newSettings = objectAssignDeep.noMutate(diffUpdated, diffAdded);
// deep-object-diff converts arrays to objects, set original array back here
const convertBackArray = (before, after) => {
for (const [key, afterValue] of Object.entries(after)) {
const beforeValue = before[key];
if (Array.isArray(beforeValue)) {
after[key] = beforeValue;
} else if (afterValue && typeof beforeValue === 'object') {
convertBackArray(beforeValue, afterValue);
}
}
};
convertBackArray(message.options, newSettings);
const restartRequired = settings.apply(newSettings);
if (restartRequired) this.restartRequired = true;
// Apply some settings on-the-fly.
if (newSettings.hasOwnProperty('permit_join')) {
await this.zigbee.permitJoin(newSettings.permit_join);
}
if (newSettings.hasOwnProperty('homeassistant')) {
this.enableDisableExtension(newSettings.homeassistant, 'HomeAssistant');
}
if (newSettings.hasOwnProperty('advanced') && newSettings.advanced.hasOwnProperty('log_level')) {
logger.setLevel(newSettings.advanced.log_level);
}
logger.info('Succesfully changed options');
this.publishInfo();
return utils.getResponse(message, {restart_required: this.restartRequired}, null);
}
async deviceRemove(message) {
@@ -178,6 +224,13 @@ class Bridge extends Extension {
return this.renameEntity('group', message);
}
async restart(message) {
// Wait 500 ms before restarting so response can be send.
setTimeout(this.restartCallback, 500);
logger.info('Restarting Zigbee2MQTT');
return utils.getResponse(message, {}, null);
}
async permitJoin(message) {
if (typeof message === 'object' && !message.hasOwnProperty('value')) {
throw new Error('Invalid payload');
@@ -210,6 +263,7 @@ class Bridge extends Extension {
return utils.getResponse(message, response, null);
}
// Deprecated
configLastSeen(message) {
const allowed = ['disable', 'ISO_8601', 'epoch', 'ISO_8601_local'];
const value = this.getValue(message);
@@ -222,6 +276,7 @@ class Bridge extends Extension {
return utils.getResponse(message, {value}, null);
}
// Deprecated
configHomeAssistant(message) {
const allowed = [true, false];
const value = this.getValue(message);
@@ -235,6 +290,7 @@ class Bridge extends Extension {
return utils.getResponse(message, {value}, null);
}
// Deprecated
configElapsed(message) {
const allowed = [true, false];
const value = this.getValue(message);
@@ -247,6 +303,7 @@ class Bridge extends Extension {
return utils.getResponse(message, {value}, null);
}
// Deprecated
configLogLevel(message) {
const allowed = ['error', 'warn', 'info', 'debug'];
const value = this.getValue(message);
@@ -334,6 +391,9 @@ class Bridge extends Extension {
const oldOptions = cleanup(entity.settings);
const newOptions = cleanup(settings.getEntity(ID));
await this.publishInfo();
logger.info(`Changed config for ${entityType} ${ID}`);
return utils.getResponse(message, {from: oldOptions, to: newOptions, id: ID}, null);
}
@@ -346,6 +406,11 @@ class Bridge extends Extension {
const ID = message.id;
const endpoint = this.getEntity('device', ID).endpoint;
const coordinator = this.zigbee.getDevicesByType('Coordinator')[0];
const coordinatorEndpoint = coordinator.getEndpoint(1);
await endpoint.bind(message.cluster, coordinatorEndpoint);
await endpoint.configureReporting(message.cluster, [{
attribute: message.attribute, minimumReportInterval: message.minimum_report_interval,
maximumReportInterval: message.maximum_report_interval, reportableChange: message.reportable_change,
@@ -373,11 +438,6 @@ class Bridge extends Extension {
throw new Error('No device has joined since start');
}
const validationErrors = utils.validateFriendlyName(message.to);
if (validationErrors.length !== 0) {
throw new Error(validationErrors[0]);
}
const from = deviceAndHasLast ? this.lastJoinedDeviceIeeeAddr : message.from;
const to = message.to;
const homeAssisantRename = message.hasOwnProperty('homeassistant_rename') ?
@@ -499,12 +559,15 @@ class Bridge extends Extension {
coordinator: this.coordinatorVersion,
network: utils.toSnakeCase(await this.zigbee.getNetworkParameters()),
log_level: logger.getLevel(),
permit_join: await this.zigbee.getPermitJoin(),
permit_join: this.zigbee.getPermitJoin(),
permit_join_timeout: this.zigbee.getPermitJoinTimeout(),
restart_required: this.restartRequired,
config,
config_schema: settings.schema,
};
await this.mqtt.publish('bridge/info', stringify(payload), {retain: true, qos: 0});
await this.mqtt.publish(
'bridge/info', stringify(payload), {retain: true, qos: 0}, settings.get().mqtt.base_topic, true);
}
async publishDevices() {
@@ -539,7 +602,7 @@ class Bridge extends Extension {
for (const configuredReporting of endpoint.configuredReportings) {
data.configured_reportings.push({
cluster: configuredReporting.cluster.name,
attribute: configuredReporting.attribute.name,
attribute: configuredReporting.attribute.name || configuredReporting.attribute.ID,
minimum_report_interval: configuredReporting.minimumReportInterval,
maximum_report_interval: configuredReporting.maximumReportInterval,
reportable_change: configuredReporting.reportableChange,
@@ -555,7 +618,7 @@ class Bridge extends Extension {
network_address: device.networkAddress,
supported: !!definition,
friendly_name: resolved.name,
definition: this.getDefinitionPayload(definition),
definition: this.getDefinitionPayload(definition, resolved),
power_source: device.powerSource,
software_build_id: device.softwareBuildID,
date_code: device.dateCode,
@@ -566,7 +629,8 @@ class Bridge extends Extension {
};
});
await this.mqtt.publish('bridge/devices', stringify(devices), {retain: true, qos: 0});
await this.mqtt.publish(
'bridge/devices', stringify(devices), {retain: true, qos: 0}, settings.get().mqtt.base_topic, true);
}
async publishGroups() {
@@ -584,17 +648,35 @@ class Bridge extends Extension {
};
});
await this.mqtt.publish('bridge/groups', stringify(groups), {retain: true, qos: 0});
await this.mqtt.publish(
'bridge/groups', stringify(groups), {retain: true, qos: 0}, settings.get().mqtt.base_topic, true);
}
getDefinitionPayload(definition) {
getDefinitionPayload(definition, resolvedDevice) {
if (definition) {
let icon = null;
if (resolvedDevice && resolvedDevice.hasOwnProperty('settings')) {
icon = resolvedDevice.settings.icon;
}
if (!icon) {
icon = definition.icon;
}
if (icon) {
let zigbeeModel = '';
/* istanbul ignore else */
if (resolvedDevice && resolvedDevice.hasOwnProperty('device')) {
zigbeeModel = resolvedDevice.device.modelID;
}
icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(zigbeeModel));
icon = icon.replace('${model}', utils.sanitizeImageParameter(definition.model));
}
return {
model: definition.model,
vendor: definition.vendor,
description: definition.description,
exposes: definition.exposes,
supports_ota: !!definition.ota,
icon,
};
} else {
return null;
+9 -4
View File
@@ -31,11 +31,11 @@ class Configure extends Extension {
}
if (this.shouldConfigure(resolvedEntity)) {
this.configure(resolvedEntity);
this.configure(resolvedEntity, 'reporting_disabled');
}
}
shouldConfigure(resolvedEntity) {
shouldConfigure(resolvedEntity, event) {
if (!resolvedEntity || !resolvedEntity.definition || !resolvedEntity.definition.configure) {
return false;
}
@@ -50,6 +50,11 @@ class Configure extends Extension {
return false;
}
// Only configure end devices when a message is received, otherwise it will likely fails as they are sleeping.
if (resolvedEntity.device.type === 'EndDevice' && event !== 'message_received') {
return false;
}
return true;
}
@@ -95,7 +100,7 @@ class Configure extends Extension {
for (const device of this.zigbee.getClients()) {
const resolvedEntity = this.zigbee.resolveEntity(device);
if (this.shouldConfigure(resolvedEntity)) {
if (this.shouldConfigure(resolvedEntity, 'started')) {
await this.configure(resolvedEntity);
}
}
@@ -109,7 +114,7 @@ class Configure extends Extension {
device.save();
}
if (this.shouldConfigure(resolvedEntity)) {
if (this.shouldConfigure(resolvedEntity, 'message_received')) {
this.configure(resolvedEntity);
}
}
+113
View File
@@ -0,0 +1,113 @@
const settings = require('../util/settings');
const Extension = require('./extension');
const utils = require('../util/utils');
const fs = require('fs');
const data = require('./../util/data');
const path = require('path');
const logger = require('./../util/logger');
const stringify = require('json-stable-stringify-without-jsonify');
const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/extension/(save|remove)`);
class ExternalExtension extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus, addExtension, enableDisableExtension) {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.args = [zigbee, mqtt, state, publishEntityState, eventBus];
this.addExtension = addExtension;
this.enableDisableExtension = enableDisableExtension;
this.extensionsBaseDir = 'extension';
this.loadExtension = this.loadExtension.bind(this);
this.requestLookup = {
'save': this.saveExtension.bind(this),
'remove': this.removeExtension.bind(this),
};
this.loadUserDefinedExtensions();
}
getExtensionsBasePath() {
return data.joinPath(this.extensionsBaseDir);
}
getListOfUserDefinedExtensions() {
const basePath = this.getExtensionsBasePath();
if (fs.existsSync(basePath)) {
return fs.readdirSync(basePath).filter((f) => f.endsWith('.js')).map((fileName) => {
const extensonFilePath = path.join(basePath, fileName);
return {'name': fileName, 'code': fs.readFileSync(extensonFilePath, 'utf-8')};
});
} else {
return [];
}
}
removeExtension(message) {
const {name} = message;
const extensions = this.getListOfUserDefinedExtensions();
const extensionToBeRemoved = extensions.find((e) => e.name === name);
if (extensionToBeRemoved) {
this.enableDisableExtension(false, extensionToBeRemoved.name);
const basePath = this.getExtensionsBasePath();
const extensonFilePath = path.join(basePath, path.basename(name));
fs.unlinkSync(extensonFilePath);
this.publishExtensions();
logger.info(`Extension ${name} removed`);
return utils.getResponse(message, {}, null);
} else {
return utils.getResponse(message, {}, `Extension ${name} doesn't exists`);
}
}
saveExtension(message) {
const {name, code} = message;
const ModuleConstructor = utils.loadModuleFromText(code);
this.loadExtension(ModuleConstructor);
const basePath = this.getExtensionsBasePath();
/* istanbul ignore else */
if (!fs.existsSync(basePath)) {
fs.mkdirSync(basePath);
}
const extensonFilePath = path.join(basePath, path.basename(name));
fs.writeFileSync(extensonFilePath, code);
this.publishExtensions();
logger.info(`Extension ${name} loaded`);
return utils.getResponse(message, {}, null);
}
async onMQTTMessage(topic, message) {
const match = topic.match(requestRegex);
if (match && this.requestLookup[match[1].toLowerCase()]) {
message = utils.parseJSON(message, message);
try {
const response = await this.requestLookup[match[1].toLowerCase()](message);
await this.mqtt.publish(`bridge/response/extension/${match[1]}`, stringify(response));
} catch (error) {
logger.error(`Request '${topic}' failed with error: '${error.message}'`);
const response = utils.getResponse(message, {}, error.message);
await this.mqtt.publish(`bridge/response/extension/${match[1]}`, stringify(response));
}
}
}
loadExtension(ConstructorClass) {
this.enableDisableExtension(false, ConstructorClass.name);
this.addExtension(new ConstructorClass(...this.args, settings, logger));
}
loadUserDefinedExtensions() {
const extensions = this.getListOfUserDefinedExtensions();
extensions
.map(({code}) => utils.loadModuleFromText(code))
.map(this.loadExtension);
}
async onMQTTConnected() {
this.publishExtensions();
}
async publishExtensions() {
const extensions = this.getListOfUserDefinedExtensions();
await this.mqtt.publish('bridge/extensions', stringify(extensions), {
retain: true,
qos: 0,
}, settings.get().mqtt.base_topic, true);
}
}
module.exports = ExternalExtension;
+1
View File
@@ -46,6 +46,7 @@ class Frontend extends Extension {
async stop() {
for (const client of this.wss.clients) {
client.send(stringify({topic: 'bridge/state', payload: 'offline'}));
client.close();
}
+24 -7
View File
@@ -82,17 +82,24 @@ class Groups extends Extension {
return;
}
const properties = ['state', 'brightness', 'color_temp', 'color'];
const properties = ['state', 'brightness', 'color_temp', 'color', 'color_mode'];
const payload = {};
properties.forEach((prop) => {
if (data.to.hasOwnProperty(prop)) {
payload[prop] = data.to[prop];
let endpointName;
for (let [prop, value] of Object.entries(data.update)) {
const endpointNameMatch = utils.getEndpointNames().find((n) => prop.endsWith(`_${n}`));
if (endpointNameMatch) {
prop = prop.substring(0, prop.length - endpointNameMatch.length - 1);
endpointName = endpointNameMatch;
}
});
if (properties.includes(prop)) {
payload[prop] = value;
}
}
if (Object.keys(payload).length) {
const resolvedEntity = this.zigbee.resolveEntity(data.ID);
const resolvedEntity = this.zigbee.resolveEntity(`${data.ID}${endpointName ? `/${endpointName}` : ''}`);
const zigbeeGroups = this.zigbee.getGroups().filter((zigbeeGroup) => {
const settingsGroup = settings.getGroup(zigbeeGroup.groupID);
return settingsGroup && settingsGroup.optimistic;
@@ -114,7 +121,16 @@ class Groups extends Extension {
const groupIDsToPublish = new Set();
for (const member of resolvedEntity.group.members) {
await this.publishEntityState(member.getDevice().ieeeAddr, payload, reason);
const memberPayload = {...payload};
const endpointName = this.zigbee.resolveEntity(member).endpointName;
if (endpointName) {
Object.keys(memberPayload).forEach((key) => {
memberPayload[`${key}_${endpointName}`] = memberPayload[key];
delete memberPayload[key];
});
}
await this.publishEntityState(member.getDevice().ieeeAddr, memberPayload, reason);
for (const zigbeeGroup of zigbeeGroups) {
if (zigbeeGroup.hasMember(member)) {
if (!payload || payload.state !== 'OFF' || this.areAllMembersOff(zigbeeGroup)) {
@@ -237,6 +253,7 @@ class Groups extends Extension {
groupKey, deviceKey,
} = this.parseMQTTMessage(topic, message);
if (!type) return;
message = utils.parseJSON(message, message);
const responseData = {device: deviceKey};
if (groupKey) {
+65 -16
View File
@@ -16,6 +16,7 @@ const sensorClick = {
},
};
const ACCESS_STATE = 1;
const defaultStatusTopic = 'homeassistant/status';
/**
@@ -81,16 +82,16 @@ class HomeAssistant extends Extension {
let discoveryEntry = null;
/* istanbul ignore else */
if (expose.type === 'light') {
const supportsXY = !!expose.features.find((e) => e.name === 'color_xy');
const supportsHS = !!expose.features.find((e) => e.name === 'color_hs');
const colorXY = expose.features.find((e) => e.name === 'color_xy');
const colorHS = expose.features.find((e) => e.name === 'color_hs');
const brightness = expose.features.find((e) => e.name === 'brightness');
const colorTemp = expose.features.find((e) => e.name === 'color_temp');
discoveryEntry = {
type: 'light',
object_id: expose.endpoint ? `light_${expose.endpoint}` : 'light',
discovery_payload: {
brightness: !!expose.features.find((e) => e.name === 'brightness'),
color_temp: !!expose.features.find((e) => e.name === 'color_temp'),
xy: supportsXY,
hs: !supportsXY && supportsHS,
brightness: !!brightness,
schema: 'json',
command_topic: true,
brightness_scale: 254,
@@ -99,6 +100,23 @@ class HomeAssistant extends Extension {
},
};
const colorModes = [
colorXY ? 'xy' : null,
!colorXY && colorHS ? 'hs' : null,
colorTemp ? 'color_temp' : null,
].filter((c) => c);
if (colorModes.length) {
discoveryEntry.discovery_payload.color_mode = true;
discoveryEntry.discovery_payload.supported_color_modes = colorModes;
}
if (colorTemp) {
discoveryEntry.discovery_payload.max_mireds = colorTemp.value_max;
discoveryEntry.discovery_payload.min_mireds = colorTemp.value_min;
}
const effect = def.exposes.find((e) => e.type === 'enum' && e.name === 'effect');
if (effect) {
discoveryEntry.discovery_payload.effect = true;
@@ -156,6 +174,12 @@ class HomeAssistant extends Extension {
const mode = expose.features.find((f) => f.name === 'system_mode');
if (mode) {
if (mode.values.includes('sleep')) {
// 'sleep' is not supported by homeassistent, but is valid according to ZCL
// TRV that support sleep (e.g. Viessmann) will have it removed from here,
// this allows other expose consumers to still use it, e.g. the frontend.
mode.values.splice(mode.values.indexOf('sleep'), 1);
}
discoveryEntry.discovery_payload.mode_state_topic = true;
discoveryEntry.discovery_payload.mode_state_template = `{{ value_json.${mode.property} }}`;
discoveryEntry.discovery_payload.modes = mode.values;
@@ -239,8 +263,8 @@ class HomeAssistant extends Extension {
// deprecated: child_lock is messy, but changing is breaking
discoveryEntry.discovery_payload.payload_lock = state.value_on;
discoveryEntry.discovery_payload.payload_unlock = state.value_off;
discoveryEntry.discovery_payload.state_locked = 'LOCKED';
discoveryEntry.discovery_payload.state_unlocked = 'UNLOCKED';
discoveryEntry.discovery_payload.state_locked = 'LOCK';
discoveryEntry.discovery_payload.state_unlocked = 'UNLOCK';
discoveryEntry.discovery_payload.state_topic = true;
discoveryEntry.object_id = 'child_lock';
} else {
@@ -271,7 +295,7 @@ class HomeAssistant extends Extension {
if (hasPosition) {
discoveryEntry.discovery_payload = {...discoveryEntry.discovery_payload,
value_template: '{{ value_json.position }}',
position_template: '{{ value_json.position }}',
set_position_template: '{ "position": {{ position }} }',
set_position_topic: true,
position_topic: true,
@@ -359,19 +383,21 @@ class HomeAssistant extends Extension {
eco2: {icon: 'mdi:molecule-co2'},
co2: {icon: 'mdi:molecule-co2'},
local_temperature: {device_class: 'temperature'},
x_axis: {icon: 'mdi:axis-x-arrow'},
y_axis: {icon: 'mdi:axis-y-arrow'},
z_axis: {icon: 'mdi:axis-z-arrow'},
};
discoveryEntry = {
type: 'sensor',
object_id: expose.endpoint ? `${expose.name}_${expose.endpoint}` : `${expose.name}`,
discovery_payload: {
unit_of_measurement: expose.unit ? expose.unit : '-',
value_template: `{{ value_json.${expose.property} }}`,
...(expose.unit && {unit_of_measurement: expose.unit}),
...lookup[expose.name],
},
};
} else if (expose.type === 'enum' || expose.type === 'text' || expose.type === 'composite') {
const ACCESS_STATE = 1;
if (expose.access & ACCESS_STATE) {
const lookup = {
action: {icon: 'mdi:gesture-double-tap'},
@@ -448,8 +474,7 @@ class HomeAssistant extends Extension {
* https://github.com/Koenkk/zigbee2mqtt/issues/959#issuecomment-480341347
*/
if (settings.get().advanced.homeassistant_legacy_triggers) {
const keys = ['action', 'click'].filter((k) =>
data.messagePayload.hasOwnProperty(k) && data.messagePayload[k] !== '');
const keys = ['action', 'click'].filter((k) => data.messagePayload[k]);
for (const key of keys) {
this.publishEntityState(data.entity.device.ieeeAddr, {[key]: ''});
}
@@ -462,7 +487,7 @@ class HomeAssistant extends Extension {
* and republish it to zigbee2mqtt/my_devic/action
*/
if (data.entity.definition) {
const keys = ['action', 'click'].filter((k) => data.messagePayload[k] && data.messagePayload[k] !== '');
const keys = ['action', 'click'].filter((k) => data.messagePayload[k]);
for (const key of keys) {
const value = data.messagePayload[key].toString();
await this.publishDeviceTriggerDiscover(data.entity, key, value);
@@ -862,13 +887,37 @@ class HomeAssistant extends Extension {
};
}
adjustMessagePayloadBeforePublish(resolvedEntity, messagePayload) {
// Set missing values of state to 'null': https://github.com/Koenkk/zigbee2mqtt/issues/6987
if (!resolvedEntity || !resolvedEntity.definition) return null;
const add = (expose) => {
if (!messagePayload.hasOwnProperty(expose.property) && expose.access & ACCESS_STATE) {
messagePayload[expose.property] = null;
}
};
for (const expose of resolvedEntity.definition.exposes) {
if (expose.hasOwnProperty('features')) {
for (const feature of expose.features) {
if (feature.name === 'state') {
add(feature);
}
}
} else {
add(expose);
}
}
}
getDiscoveryTopic(config, device) {
return `${config.type}/${device.ieeeAddr}/${config.object_id}/config`;
}
async publishDeviceTriggerDiscover(entity, key, value, force=false) {
if (entity.settings.hasOwnProperty('homeassistant') &&
(entity.settings.homeassistant == null || entity.settings.homeassistant.device_automation == null)) {
const haConfig = entity.settings.homeassistant;
if (entity.settings.hasOwnProperty('homeassistant') && (haConfig == null ||
(haConfig.hasOwnProperty('device_automation') && haConfig.device_automation == null))) {
return;
}
+1 -1
View File
@@ -382,7 +382,7 @@ class BridgeLegacy extends Extension {
coordinator,
network: await this.zigbee.getNetworkParameters(),
log_level: logger.getLevel(),
permit_join: await this.zigbee.getPermitJoin(),
permit_join: this.zigbee.getPermitJoin(),
};
await this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0});
+6 -5
View File
@@ -8,7 +8,6 @@ const topicRegex =
new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check)`, 'i');
const Extension = require('./extension');
const MINUTES_10 = 1000 * 60 * 10;
class OTAUpdate extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
@@ -36,15 +35,17 @@ class OTAUpdate extends Extension {
}
async onZigbeeEvent(type, data, resolvedEntity) {
if (settings.get().ota.disable_automatic_update_check) return;
if (data.type !== 'commandQueryNextImageRequest' || !resolvedEntity || !resolvedEntity.definition) return;
const supportsOTA = resolvedEntity.definition.hasOwnProperty('ota');
if (supportsOTA) {
// When a device does a next image request, it will usually do it a few times after each other
// with only 10 - 60 seconds inbetween. It doesn' make sense to check for a new update
// each time.
// with only 10 - 60 seconds inbetween. It doesn't make sense to check for a new update
// each time, so this interval can be set by the user. The default is 1,440 minutes (one day).
const updateCheckInterval = settings.get().ota.update_check_interval * 1000 * 60;
const check = this.lastChecked.hasOwnProperty(data.device.ieeeAddr) ?
(Date.now() - this.lastChecked[data.device.ieeeAddr]) > MINUTES_10 : true;
(Date.now() - this.lastChecked[data.device.ieeeAddr]) > updateCheckInterval : true;
if (!check || this.inProgress.has(data.device.ieeeAddr)) return;
this.lastChecked[data.device.ieeeAddr] = Date.now();
@@ -199,7 +200,7 @@ class OTAUpdate extends Extension {
const onProgress = (progress, remaining) => {
let msg = `Update of '${resolvedEntity.name}' at ${progress.toFixed(2)}%`;
if (remaining) {
msg += `, +- ${Math.round(remaining / 60)} minutes remaining`;
msg += `, ${Math.round(remaining / 60)} minutes remaining`;
}
logger.info(msg);
+1
View File
@@ -253,6 +253,7 @@ class EntityPublish extends Extension {
// When reporting is requested for a device (report: true in device-specific settings) we won't
// ever issue a read here, as we assume the device will properly report changes.
// Only do this when the retrieve_state option is enabled for this device.
// retrieve_state == decprecated
if (
resolvedEntity.type === 'device' && result && result.hasOwnProperty('readAfterWriteTime') &&
resolvedEntity.settings.retrieve_state
+1 -1
View File
@@ -119,7 +119,7 @@ class Receive extends Extension {
});
// Check if there is an available converter, genOta messages are not interesting.
if (!converters.length && data.cluster !== 'genOta' && data.cluster !== 'genTime') {
if (!converters.length && !['genOta', 'genTime', 'genBasic'].includes(data.cluster)) {
logger.debug(
`No converter available for '${resolvedEntity.definition.model}' with cluster '${data.cluster}' ` +
`and type '${data.type}' and data '${stringify(data.data)}'`,
+6 -6
View File
@@ -75,8 +75,10 @@ class State {
return this.state[ID];
}
set(ID, state, reason=null) {
const toState = objectAssignDeep.noMutate(state);
set(ID, update, reason=null) {
const fromState = this.state[ID] || {};
const toState = objectAssignDeep.noMutate(fromState, update);
const result = {...toState};
for (const property of Object.keys(toState)) {
if (dontCacheProperties.find((p) => property.match(p))) {
@@ -84,11 +86,9 @@ class State {
}
}
const fromState = this.state[ID];
this.state[ID] = toState;
this.eventBus.emit('stateChange', {ID, from: fromState, to: toState, reason});
this.eventBus.emit('stateChange', {ID, from: fromState, to: toState, reason, update});
return result;
}
removeKey(ID, path) {
+9
View File
@@ -19,6 +19,15 @@ const logFilename = settings.get().advanced.log_file.replace('%TIMESTAMP%', time
// Make sure that log directoy exsists when not logging to stdout only
if (output.includes('file')) {
fx.mkdirSync(directory);
if (settings.get().advanced.log_symlink_current) {
const current = settings.get().advanced.log_directory.replace('%TIMESTAMP%', 'current');
const actual = './' + timestamp;
if (fs.existsSync(current)) {
fs.unlinkSync(current);
}
fs.symlinkSync(actual, current);
}
}
const z2mToWinstonLevel = (level) => level === 'warn' ? 'warning' : level;
+44 -9
View File
@@ -6,8 +6,10 @@ const objectAssignDeep = require(`object-assign-deep`);
const path = require('path');
const yaml = require('./yaml');
const Ajv = require('ajv');
const ajv = new Ajv({allErrors: true});
const schema = require('./settings.schema.json');
const ajvSetting = new Ajv({allErrors: true}).compile(schema);
const ajvRestartRequired = new Ajv({allErrors: true})
.addKeyword('requiresRestart', {validate: (v) => !v}).compile(schema);
const defaults = {
passlist: [],
@@ -55,6 +57,7 @@ const defaults = {
advanced: {
legacy_api: true,
log_rotation: true,
log_symlink_current: false,
log_output: ['console', 'file'],
log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'),
log_file: 'log.txt',
@@ -135,6 +138,17 @@ const defaults = {
*/
timestamp_format: 'YYYY-MM-DD HH:mm:ss',
},
ota: {
/**
* Minimal time delta in minutes between polling third party server for potential firmware updates
*/
update_check_interval: 24 * 60,
/**
* Completely disallow Zigbee devices to initiate a search for a potential firmware update.
* If set to true, only a user-initiated update search will be possible.
*/
disable_automatic_update_check: false,
},
external_converters: [],
};
@@ -206,10 +220,8 @@ function validate() {
return [error.message];
}
const validate = ajv.compile(schema);
validate(_settings);
if (validate.errors) {
return validate.errors.map((v) => `${v.dataPath.substring(1)} ${v.message}`);
if (!ajvSetting(_settings)) {
return ajvSetting.errors.map((v) => `${v.dataPath.substring(1)} ${v.message}`);
}
const errors = [];
@@ -392,6 +404,24 @@ function set(path, value) {
write();
}
function apply(newSettings) {
ajvSetting(newSettings);
const errors = ajvSetting.errors && ajvSetting.errors.filter((e) => e.keyword !== 'required');
if (errors.length) {
const error = errors[0];
throw new Error(`${error.dataPath.substring(1)} ${error.message}`);
}
get(); // Ensure _settings is intialized.
_settings = objectAssignDeep.noMutate(_settings, newSettings);
write();
ajvRestartRequired(newSettings);
const restartRequired = ajvRestartRequired.errors &&
!!ajvRestartRequired.errors.find((e) => e.keyword === 'requiresRestart');
return restartRequired;
}
function getGroup(IDorName) {
const settings = getWithDefaults();
const byID = settings.groups[IDorName];
@@ -522,6 +552,7 @@ function removeDevice(IDorName) {
}
function addGroup(name, ID=null) {
utils.validateFriendlyName(name, true);
if (getGroup(name) || getDevice(name)) {
throw new Error(`friendly_name '${name}' is already in use`);
}
@@ -605,8 +636,10 @@ function changeEntityOptions(IDorName, newOptions) {
delete newOptions.devices;
if (getDevice(IDorName)) {
objectAssignDeep(settings.devices[getDevice(IDorName).ID], newOptions);
utils.removeNullPropertiesFromObject(settings.devices[getDevice(IDorName).ID]);
} else if (getGroup(IDorName)) {
objectAssignDeep(settings.groups[getGroup(IDorName).ID], newOptions);
utils.removeNullPropertiesFromObject(settings.groups[getGroup(IDorName).ID]);
} else {
throw new Error(`Device or group '${IDorName}' does not exist`);
}
@@ -615,6 +648,7 @@ function changeEntityOptions(IDorName, newOptions) {
}
function changeFriendlyName(IDorName, newName) {
utils.validateFriendlyName(newName, true);
if (getGroup(newName) || getDevice(newName)) {
throw new Error(`friendly_name '${newName}' is already in use`);
}
@@ -635,6 +669,7 @@ module.exports = {
validate,
get: getWithDefaults,
set,
apply,
getDevice,
getGroup,
getGroups,
@@ -651,15 +686,15 @@ module.exports = {
changeEntityOptions,
changeFriendlyName,
schema,
// For tests only
_write: write,
_reRead: () => {
reRead: () => {
_settings = null;
get();
_settingsWithDefaults = null;
getWithDefaults();
},
// For tests only
_write: write,
_clear: () => {
_settings = null;
_settingsWithDefaults = null;
+227 -37
View File
@@ -5,7 +5,7 @@
"type": "object"
},
"homeassistant": {
"title": "Homeassistant integration",
"title": "Home Assistant integration",
"type": "boolean",
"description": "Home Assistant integration (MQTT discovery)",
"default": false
@@ -14,11 +14,13 @@
"type": "boolean",
"default": false,
"title": "Permit join",
"description": "Allow new devices to join. WARNING: Disable this after all devices have been paired!"
"description": "Allow new devices to join (re-applied at restart)"
},
"external_converters": {
"type": "array",
"title": "External converters",
"description": "You can define external converters to e.g. add support for a DiY device",
"requiresRestart": true,
"items": {
"type": "string"
},
@@ -26,65 +28,75 @@
},
"mqtt": {
"type": "object",
"title": "MQTT settings",
"title": "MQTT",
"properties": {
"base_topic": {
"type": "string",
"title": "Base topic",
"requiresRestart": true,
"description": "MQTT base topic for Zigbee2MQTT MQTT messages",
"examples": ["zigbee2mqtt"]
},
"server": {
"type": "string",
"title": "MQTT server",
"requiresRestart": true,
"description": "MQTT server URL (use mqtts:// for SSL/TLS connection)",
"examples": ["mqtt://localhost:1883"]
},
"keepalive": {
"type": "number",
"title": "Keepalive",
"requiresRestart": true,
"description": "MQTT keepalive in second",
"default": 60
},
"ca": {
"type": "string",
"title": "Certificate authority",
"requiresRestart": true,
"description": "Absolute path to SSL/TLS certificate of CA used to sign server and client certificates",
"examples": ["/etc/ssl/mqtt-ca.crt"]
},
"key": {
"type": "string",
"title": "SSL/TLS key",
"description": "Absolute paths to SSL/TLS key and certificate for client-authentication",
"requiresRestart": true,
"description": "Absolute path to SSL/TLS key for client-authentication",
"examples": ["/etc/ssl/mqtt-client.key"]
},
"cert": {
"type": "string",
"title": "SSL/TLS certificate",
"description": "Absolute path to SSL/TLS certificate for client-authentication",
"requiresRestart": true,
"examples": ["/etc/ssl/mqtt-client.crt"]
},
"user": {
"type": "string",
"title": "User",
"requiresRestart": true,
"description": "MQTT server authentication user",
"examples": ["johnnysilverhand"]
},
"password": {
"type": "string",
"title": "Password",
"requiresRestart": true,
"description": "MQTT server authentication password",
"examples": ["ILOVEPELMENI"]
},
"client_id": {
"type": "string",
"title": "Client ID",
"requiresRestart": true,
"description": "MQTT client ID",
"examples": ["MY_CLIENT_ID"]
},
"reject_unauthorized": {
"type": "boolean",
"title": "Reject unauthorized",
"requiresRestart": true,
"description": "Disable self-signed SSL certificate",
"default": true
},
@@ -97,12 +109,15 @@
"version": {
"type": ["number", "null"],
"title": "Version",
"requiresRestart": true,
"description": "MQTT protocol version",
"default": 4
"default": 4,
"examples": [4, 5]
},
"force_disable_retain": {
"type": "boolean",
"title": "Force disable retain",
"requiresRestart": true,
"description": "Disable retain for all send messages. ONLY enable if you MQTT broker doesn't support retained message (e.g. AWS IoT core, Azure IoT Hub, Google Cloud IoT core, IBM Watson IoT Platform). Enabling will break the Home Assistant integration",
"default": false
}
@@ -111,30 +126,34 @@
},
"serial": {
"type": "object",
"title": "Serial settings",
"title": "Serial",
"properties": {
"port": {
"type": ["string", "null"],
"title": "Port",
"requiresRestart": true,
"description": "Location of the adapter. To autodetect the port, set null",
"examples": ["/dev/ttyACM0"]
},
"disable_led": {
"type": "boolean",
"title": "Disable led",
"requiresRestart": true,
"description": "Disable LED of the adapter if supported",
"default": false
},
"adapter": {
"type": "string",
"enum": ["deconz", "zstack", "zigate"],
"type": ["string", "null"],
"enum": ["deconz", "zstack", "zigate", "ezsp"],
"title": "Adapter",
"requiresRestart": true,
"description": "Adapter type, not needed unless you are experiencing problems"
}
}
},
"blocklist": {
"title": "Blocklist",
"requiresRestart": true,
"description": "Block devices from the network (by ieeeAddr)",
"type": "array",
"items": {
@@ -143,6 +162,7 @@
},
"passlist": {
"title": "Passlist",
"requiresRestart": true,
"description": "Allow only certain devices to join the network (by ieeeAddr). Note that all devices not on the passlist will be removed from the network!",
"type": "array",
"items": {
@@ -150,26 +170,32 @@
}
},
"whitelist": {
"readOnly": true,
"type": "array",
"title": "Whitelist (deprecated)",
"requiresRestart": true,
"title": "Whitelist (deprecated, use passlist)",
"items": {
"type": "string"
}
},
"ban": {
"readOnly": true,
"type": "array",
"title": "Ban (deprecated)",
"requiresRestart": true,
"title": "Ban (deprecated, use blocklist)",
"items": {
"type": "string"
}
},
"experimental": {
"type": "object",
"title": "Experimental options",
"title": "Experimental",
"properties": {
"transmit_power": {
"type": ["number", "null"],
"title": "Transmit power"
"title": "Transmit power",
"requiresRestart": true,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
},
"output": {
"type": "string",
@@ -181,11 +207,12 @@
},
"advanced": {
"type": "object",
"title": "Advanced settings",
"title": "Advanced",
"properties": {
"legacy_api": {
"type": "boolean",
"title": "Legacy API",
"requiresRestart": true,
"description": "Disables the legacy api (false = disable)",
"default": true
},
@@ -200,7 +227,8 @@
}
],
"title": "Pan ID",
"description": "ZigBee pan ID"
"requiresRestart": true,
"description": "ZigBee pan ID, changing requires repairing all devices!"
},
"ext_pan_id": {
"type": "array",
@@ -208,7 +236,8 @@
"type": "number"
},
"title": "Ext Pan ID",
"description": "Zigbee extended pan ID"
"requiresRestart": true,
"description": "Zigbee extended pan ID, changing requires repairing all devices!"
},
"channel": {
"type": "number",
@@ -216,7 +245,9 @@
"maximum": 26,
"default": 11,
"title": "ZigBee channel",
"description": "Changing requires re-pairing of all devices. (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)"
"requiresRestart": true,
"description": "Zigbee channel, changing requires repairing all devices! (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)",
"examples": [11, 15, 20, 25]
},
"cache_state": {
"type": "boolean",
@@ -226,22 +257,30 @@
},
"cache_state_persistent": {
"type": "boolean",
"title": "Chache state persistent",
"description": "Persist cached state, only used when Cache state(cache_state: true)",
"title": "Persist cache state",
"description": "Persist cached state, only used when cache_state: true",
"default": true
},
"cache_state_send_on_startup": {
"type": "boolean",
"title": "Cache state send on startup",
"description": "Cache state send on startup, only used when Cache state(cache_state: true)",
"title": "Send cached state on startup",
"description": "Send cached state on startup, only used when cache_state: true",
"default": true
},
"log_rotation": {
"type": "boolean",
"title": "Log rotation",
"requiresRestart": true,
"description": "Log rotation",
"default": true
},
"log_symlink_current": {
"type": "boolean",
"title": "Log symlink current",
"requiresRestart": true,
"description": "Create symlink to current logs in the log directory",
"default": false
},
"log_level": {
"type": "string",
"enum": ["info", "warn", "error", "debug"],
@@ -251,6 +290,7 @@
},
"log_output": {
"type": "array",
"requiresRestart": true,
"items": {
"type": "string",
"enum": ["console", "file", "syslog"]
@@ -261,12 +301,14 @@
"log_directory": {
"type": "string",
"title": "Log directory",
"requiresRestart": true,
"description": "Location of log directory",
"examples": ["data/log/%TIMESTAMP%"]
},
"log_file": {
"type": "string",
"title": "Log file",
"requiresRestart": true,
"description": "Log file name, can also contain timestamp",
"examples": ["zigbee2mqtt_%TIMESTAMP%.log"],
"default": "log.txt"
@@ -274,18 +316,20 @@
"baudrate": {
"type": "number",
"title": "Baudrate",
"description": "Baudrate for serial port, default: 115200 for Z-Stack, 38400 for Deconz",
"examples": [38400, 115200]
"requiresRestart": true,
"description": "Baud rate speed for serial port, this can be anything firmware support but default is 115200 for Z-Stack and EZSP, 38400 for Deconz, however note that some EZSP firmware need 57600",
"examples": [38400, 57600, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS",
"description": "RTS / CTS Hardware Flow Control for serial port",
"default": false
"requiresRestart": true,
"description": "RTS / CTS Hardware Flow Control for serial port"
},
"soft_reset_timeout": {
"type": "number",
"minimum": 0,
"requiresRestart": true,
"title": "Soft reset timeout (deprecated)",
"description": "Soft reset ZNP after timeout",
"readOnly": true
@@ -304,7 +348,8 @@
}
],
"title": "Network key",
"description": "Network encryption key, will improve security (Note: changing requires repairing of all devices)"
"requiresRestart": true,
"description": "Network encryption key, changing requires repairing all devices!"
},
"last_seen": {
"type": "string",
@@ -323,14 +368,16 @@
"type": "number",
"minimum": 0,
"default": 0,
"requiresRestart": true,
"title": "Availability Timeout",
"description": "Availability timeout in seconds When enabled, devices will be checked if they are still online. Only AC powered routers are checked for availability"
"description": "Availability timeout in seconds when enabled, devices will be checked if they are still online. Only AC powered routers are checked for availability"
},
"availability_blocklist": {
"type": "array",
"items": {
"type": "string"
},
"requiresRestart": true,
"title": "Availability Blocklist",
"description": "Prevent devices from being checked for availability"
},
@@ -339,58 +386,157 @@
"items": {
"type": "string"
},
"requiresRestart": true,
"title": "Availability passlist",
"description": "Only enable availability check for certain devices"
},
"availability_blacklist": {
"type": "array",
"title": "Availability blacklist (deprecated)",
"readOnly": true,
"requiresRestart": true,
"title": "Availability blacklist (deprecated, use availability_blocklist)",
"items": {
"type": "string"
}
},
"availability_whitelist": {
"type": "array",
"readOnly": true,
"requiresRestart": true,
"items": {
"type": "string"
},
"title": "Availability whitelist (deprecated)"
"title": "Availability whitelist (deprecated, use passlist)"
},
"report": {
"type": "boolean",
"title": "Reporting",
"description": "Enables report feature"
"requiresRestart": true,
"readOnly": true,
"description": "Enables report feature (deprecated)"
},
"homeassistant_discovery_topic": {
"type": "string",
"title": "Homeassistant discovery topic",
"description": "Home Assistant discovery topic",
"requiresRestart": true,
"examples": ["homeassistant"]
},
"homeassistant_status_topic": {
"type": "string",
"title": "Home Assistant status topic",
"description": "Home Assistant status topic",
"requiresRestart": true,
"examples": ["homeassistant/status"]
},
"timestamp_format": {
"type": "string",
"title": "Timestamp format",
"requiresRestart": true,
"description": "Log timestamp format",
"examples": ["YYYY-MM-DD HH:mm:ss"]
},
"adapter_concurrent": {
"title": "Adapter concurrency",
"requiresRestart": true,
"type": ["number", "null"],
"description": "Adapter concurrency (e.g. 2 for CC2531 or 16 for CC26X2R1) (default: null, uses recommended value)"
},
"adapter_delay": {
"type": ["number", "null"],
"title": "Adapter delay"
"requiresRestart": true,
"title": "Adapter delay",
"description": "Adapter delay"
},
"ikea_ota_use_test_url": {
"type": "boolean",
"title": "IKEA TRADFRI OTA use test url",
"requiresRestart": true,
"description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation",
"default": false
},
"homeassistant_legacy_triggers": {
"type": "boolean",
"title": "Home Assistant legacy triggers",
"description": "Home Assistant legacy triggers, when enabled Zigbee2mqt will send an empty 'action' or 'click' after one has been send. A 'sensor_action' and 'sensor_click' will be discoverd",
"default": true
},
"log_syslog": {
"type": "object",
"title": "syslog",
"properties": {
"host": {
"type": "string",
"title": "Host",
"description": "The host running syslogd, defaults to localhost.",
"default": "localhost"
},
"port": {
"type": "number",
"title": "Port",
"description": "The port on the host that syslog is running on, defaults to syslogd's default port.",
"default": 123
},
"protocol": {
"type": "string",
"title": "Protocol",
"description": "The network protocol to log over (e.g. tcp4, udp4, tls4, unix, unix-connect, etc).",
"default": "tcp4",
"examples": [
"tcp4",
"udp4",
"tls4",
"unix",
"unix-connect"
]
},
"path": {
"type": "string",
"title": "Path",
"description": "The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).",
"default": "/dev/log",
"examples": [
"/dev/log",
"/var/run/syslog"
]
},
"pid": {
"type": "string",
"title": "PID",
"description": "PID of the process that log messages are coming from (Default process.pid).",
"default": "process.pid"
},
"localhost": {
"type": "string",
"title": "Localhost",
"description": "Host to indicate that log messages are coming from (Default: localhost).",
"default": "localhost"
},
"type": {
"type": "string",
"title": "Type",
"description": "The type of the syslog protocol to use (Default: BSD, also valid: 5424).",
"default": "5424"
},
"app_name": {
"type": "string",
"title": "Localhost",
"description": "The name of the application (Default: Zigbee2MQTT).",
"default": "Zigbee2MQTT"
},
"eol": {
"type": "string",
"title": "eol",
"description": "The end of line character to be added to the end of the message (Default: Message without modifications).",
"default": "/n"
}
}
}
}
},
"map_options": {
"type": "object",
"title": "Networkmap options",
"title": "Networkmap",
"properties": {
"graphviz": {
"type": "object",
@@ -443,6 +589,24 @@
}
}
},
"ota": {
"type": "object",
"title": "OTA updates",
"properties": {
"update_check_interval": {
"type": "number",
"title": "Update check interval",
"description": "Your device may request a check for a new firmware update. This value determines how frequently third party servers may actually be contacted to look for firmware updates. The value is set in minutes, and the default is 1 day.",
"default": 1440
},
"disable_automatic_update_check": {
"type": "boolean",
"title": "Disable automatic update check",
"description": "Zigbee devices may request a firmware update, and do so frequently, causing Zigbee2MQTT to reach out to third party servers. If you disable these device initiated checks, you can still initiate a firmware update check manually.",
"default": false
}
}
},
"devices": {
"type": "object",
"propertyNames": {
@@ -464,6 +628,32 @@
"$ref": "#/definitions/group"
}
}
},
"frontend": {
"type": "object",
"title": "Frontend",
"properties": {
"port": {
"type": "number",
"title": "Port",
"description": "Frontend binding port",
"default": 8080,
"requiresRestart": true
},
"host": {
"type": "string",
"title": "Bind host",
"description": "Frontend binding host",
"default":" 0.0.0.0",
"requiresRestart": true
},
"auth_token": {
"type": ["string", "null"],
"title": "Auth token",
"description": "Enables authentication, disabled by default",
"requiresRestart": true
}
}
}
},
"required": ["mqtt"],
@@ -506,11 +696,6 @@
"title": "Ignore debounce",
"description": "Protects unique payload values of specified payload properties from overriding within debounce time"
},
"retrieve_state": {
"type": "boolean",
"title": "Retrieve State",
"description": "Retrieves the state after setting it (Should only be enabled when the reporting feature does not work for this device)"
},
"optimistic": {
"type": "boolean",
"title": "Optimistic",
@@ -524,6 +709,11 @@
"examples": ["temperature", "battery", "action"],
"title": "Filtered attributes",
"description": "Allows to prevent certain attributes from being published"
},
"icon": {
"type": "string",
"title": "Icon",
"description": "The user-defined device icon for the frontend. It can be a link to an image (not a path to a file) or base64 encoded data URL like: image/svg+xml;base64,PHN2ZyB3aW....R0aD"
}
},
"required": ["friendly_name"]
+67 -11
View File
@@ -3,6 +3,7 @@ const humanizeDuration = require('humanize-duration');
const data = require('./data');
const vm = require('vm');
const fs = require('fs');
const path = require('path');
// Xiaomi uses 4151 and 4447 (lumi.plug) as manufacturer ID.
const xiaomiManufacturerID = [4151, 4447];
@@ -35,12 +36,13 @@ const endpointNames = [
'top_left', 'top_right', 'white', 'rgb', 'cct', 'system', 'top', 'bottom', 'center_left', 'center_right',
'ep1', 'ep2', 'row_1', 'row_2', 'row_3', 'row_4', 'relay',
'l1', 'l2', 'l3', 'l4', 'l5', 'l6', 'l7', 'l8',
'l9', 'l10', 'l11', 'l12', 'l13', 'l14', 'l15', 'l16',
'button_1', 'button_2', 'button_3', 'button_4', 'button_5',
'button_6', 'button_7', 'button_8', 'button_9', 'button_10',
'button_11', 'button_12', 'button_13', 'button_14', 'button_15',
'button_16', 'button_17', 'button_18', 'button_19', 'button_20',
'button_light', 'button_fan_high', 'button_fan_med', 'button_fan_low',
'heat', 'cool', 'water',
'heat', 'cool', 'water', 'meter', 'wifi',
];
function capitalize(s) {
@@ -124,7 +126,7 @@ function equalsPartial(object, expected) {
}
function getObjectProperty(object, key, defaultValue) {
return object.hasOwnProperty(key) ? object[key] : defaultValue;
return object && object.hasOwnProperty(key) ? object[key] : defaultValue;
}
function getResponse(request, data, error) {
@@ -144,6 +146,28 @@ function parseJSON(value, failedReturnValue) {
}
}
function loadModuleFromText(moduleCode) {
const moduleFakePath = path.join(__dirname, 'externally-loaded.js');
const sandbox = {
require: require,
module: {},
console,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
setImmediate,
clearImmediate,
};
vm.runInNewContext(moduleCode, sandbox, moduleFakePath);
return sandbox.module.exports;
}
function loadModuleFromFile(modulePath) {
const moduleCode = fs.readFileSync(modulePath, {encoding: 'utf8'});
return loadModuleFromText(moduleCode);
}
function* getExternalConvertersDefinitions(settings) {
const externalConverters = settings.get().external_converters;
@@ -151,13 +175,7 @@ function* getExternalConvertersDefinitions(settings) {
let converter;
if (moduleName.endsWith('.js')) {
const sandbox = {
require,
module: {},
};
const converterCode = fs.readFileSync(data.joinPath(moduleName), {encoding: 'utf8'});
vm.runInNewContext(converterCode, sandbox);
converter = sandbox.module.exports;
converter = loadModuleFromFile(data.joinPath(moduleName));
} else {
converter = require(moduleName);
}
@@ -172,6 +190,27 @@ function* getExternalConvertersDefinitions(settings) {
}
}
function removeNullPropertiesFromObject(obj) {
for (const key of Object.keys(obj)) {
const value = obj[key];
if (value == null) {
delete obj[key];
} else if (typeof value === 'object') {
removeNullPropertiesFromObject(value);
}
}
}
function getKey(object, value, fallback, convertTo) {
for (const key in object) {
if (object[key]===value) {
return convertTo ? convertTo(key) : key;
}
}
return fallback;
}
function toSnakeCase(value) {
if (typeof value === 'object') {
value = {...value};
@@ -188,7 +227,7 @@ function toSnakeCase(value) {
}
}
function validateFriendlyName(name) {
function validateFriendlyName(name, throwFirstError=false) {
const errors = [];
for (const endpointName of endpointNames) {
if (name.toLowerCase().endsWith('/' + endpointName)) {
@@ -196,7 +235,7 @@ function validateFriendlyName(name) {
}
}
if (name.length === 0) errors.push(`friendly_name shoud atleast be 1 char long`);
if (name.length === 0) errors.push(`friendly_name must be at least 1 char long`);
if (name.endsWith('/') || name.startsWith('/')) errors.push(`friendly_name is not allowed to end or start with /`);
if (name.endsWith(String.fromCharCode(0))) errors.push(`friendly_name is not allowed to contain null char`);
if (endpointNames.includes(name)) errors.push(`Following friendly_name are not allowed: '${endpointNames}'`);
@@ -204,12 +243,24 @@ function validateFriendlyName(name) {
if (name.includes('#') || name.includes('+')) {
errors.push(`MQTT wildcard (+ and #) not allowed in friendly_name ('${name}')`);
}
if (throwFirstError && errors.length) {
throw new Error(errors[0]);
}
return errors;
}
function sleep(seconds) {
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
function sanitizeImageParameter(parameter) {
const replaceByDash = [/\?/g, /&/g, /[^a-z\d\- _./:]/gi];
let sanitized = parameter;
replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-'));
return sanitized;
}
module.exports = {
millisecondsToSeconds: (milliseconds) => milliseconds / 1000,
secondsToMilliseconds: (seconds) => seconds * 1000,
@@ -232,4 +283,9 @@ module.exports = {
parseJSON,
getExternalConvertersDefinitions,
validateFriendlyName,
loadModuleFromFile,
loadModuleFromText,
getKey,
sanitizeImageParameter,
removeNullPropertiesFromObject,
};
+41 -16
View File
@@ -112,6 +112,10 @@ class Zigbee extends events.EventEmitter {
return this.herdsman.getCoordinatorVersion();
}
isStopping() {
return this.herdsman.isStopping();
}
async getNetworkParameters() {
return this.herdsman.getNetworkParameters();
}
@@ -121,8 +125,9 @@ class Zigbee extends events.EventEmitter {
}
async stop() {
logger.info('Stopping zigbee-herdsman...');
await this.herdsman.stop();
logger.info('zigbee-herdsman stopped');
logger.info('Stopped zigbee-herdsman');
}
async permitJoin(permit, resolvedEntity, time=undefined) {
@@ -137,10 +142,14 @@ class Zigbee extends events.EventEmitter {
}
}
async getPermitJoin() {
getPermitJoin() {
return this.herdsman.getPermitJoin();
}
getPermitJoinTimeout() {
return this.herdsman.getPermitJoinTimeout();
}
getClients() {
return this.herdsman.getDevices().filter((device) => device.type !== 'Coordinator');
}
@@ -175,10 +184,16 @@ class Zigbee extends events.EventEmitter {
resolveEntity(key) {
assert(
typeof key === 'string' || typeof key === 'number' ||
key.constructor.name === 'Device' || key.constructor.name === 'Group',
key.constructor.name === 'Device' || key.constructor.name === 'Group' ||
key.constructor.name === 'Endpoint',
`Wrong type '${typeof key}'`,
);
const getEndpointName = (endpointNames, endpoint) => {
return endpoint ?
utils.getKey(endpointNames, endpoint.ID, null, ((v) => v === 'default' ? null : v)) : null;
};
const deviceOptions = settings.get().device_options;
if (typeof key === 'string' || typeof key === 'number') {
if (typeof key === 'number') {
@@ -193,6 +208,7 @@ class Zigbee extends events.EventEmitter {
endpoint: coordinator.getEndpoint(1),
settings: {friendlyName: 'Coordinator'},
name: 'Coordinator',
endpointName: null,
};
}
@@ -215,20 +231,21 @@ class Zigbee extends events.EventEmitter {
}
const definition = zigbeeHerdsmanConverters.findByDevice(device);
const endpoints = definition && definition.endpoint ? definition.endpoint(device) : null;
const endpointNames = definition && definition.endpoint ? definition.endpoint(device) : null;
let endpoint;
if (endpointKey) {
if (endpointByNumber) {
endpoint = device.getEndpoint(endpointKey);
} else {
assert(definition != null, `Endpoint name '${endpointKey}' is given but device is unsupported`);
assert(endpoints != null, `Endpoint name '${endpointKey}' is given but no endpoints defined`);
const endpointID = endpoints[endpointKey];
assert(endpointNames != null,
`Endpoint name '${endpointKey}' is given but no endpoints defined`);
const endpointID = endpointNames[endpointKey];
assert(endpointID, `Endpoint name '${endpointKey}' is given but device has no such endpoint`);
endpoint = device.getEndpoint(endpointID);
}
} else if (endpoints && endpoints['default']) {
endpoint = device.getEndpoint(endpoints['default']);
} else if (endpointNames && endpointNames['default']) {
endpoint = device.getEndpoint(endpointNames['default']);
} else {
endpoint = device.endpoints[0];
}
@@ -236,21 +253,29 @@ class Zigbee extends events.EventEmitter {
return {
type: 'device', device, endpoint, settings: {...deviceOptions, ...entity},
name: entity.friendlyName, definition,
endpointName: getEndpointName(endpointNames, endpoint),
};
} else {
let group = this.getGroupByID(entity.ID);
if (!group) group = this.createGroup(entity.ID);
return {type: 'group', group, settings: {...deviceOptions, ...entity}, name: entity.friendlyName};
}
} else if (key.constructor.name === 'Device') {
const setting = settings.getEntity(key.ieeeAddr);
} else if (key.constructor.name === 'Device' || key.constructor.name === 'Endpoint') {
const device = key.constructor.name === 'Endpoint' ? key.getDevice() : key;
const setting = settings.getEntity(device.ieeeAddr);
const definition = zigbeeHerdsmanConverters.findByDevice(device);
const name = setting ? setting.friendlyName :
(device.type === 'Coordinator' ? 'Coordinator' : device.ieeeAddr);
const endpointNames = definition && definition.endpoint ? definition.endpoint(device) : null;
let endpoint;
if (key.constructor.name === 'Endpoint') endpoint = key;
else if (endpointNames && endpointNames['default']) endpoint = device.getEndpoint(endpointNames['default']);
else endpoint = device.endpoints[0];
return {
type: 'device',
device: key,
endpoint: key.endpoints[0],
settings: {...deviceOptions, ...(setting || {})},
name: setting ? setting.friendlyName : (key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr),
definition: zigbeeHerdsmanConverters.findByDevice(key),
type: 'device', definition, name, device, endpoint, settings: {...deviceOptions, ...(setting || {})},
endpointName: getEndpointName(endpointNames, endpoint),
};
} else { // Group
const setting = settings.getEntity(key.groupID);
+2619 -2178
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "1.17.1",
"version": "1.18.3",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"repository": {
@@ -35,6 +35,7 @@
"dependencies": {
"ajv": "^6.12.4",
"debounce": "^1.2.0",
"deep-object-diff": "^1.1.0",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.1.2",
"git-last-commit": "^1.0.0",
@@ -51,9 +52,9 @@
"winston": "^3.3.3",
"winston-syslog": "^2.4.4",
"ws": "^7.3.1",
"zigbee-herdsman": "0.13.59",
"zigbee-herdsman-converters": "14.0.42",
"zigbee2mqtt-frontend": "0.3.23"
"zigbee-herdsman": "0.13.92",
"zigbee-herdsman-converters": "14.0.131",
"zigbee2mqtt-frontend": "0.4.0"
},
"devDependencies": {
"eslint": "*",
+1
View File
@@ -1,6 +1,7 @@
class Example {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
this.mqtt = mqtt;
this.mqtt.publish('example/extension', 'call from constructor')
}
onMQTTConnected() {
+11 -13
View File
@@ -25,11 +25,11 @@ describe('Availability', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
jest.useFakeTimers();
settings.set(['advanced', 'availability_timeout'], 10);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
mocksClear.forEach((m) => m.mockClear());
await controller.start();
await flushPromises();
@@ -172,11 +172,9 @@ describe('Availability', () => {
endpoint.read.mockClear();
await zigbeeHerdsman.events.deviceAnnounce({device});
await flushPromises();
expect(endpoint.read).toHaveBeenCalledTimes(4);
expect(endpoint.read).toHaveBeenCalledTimes(2);
expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']);
expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']);
expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl', ['currentX', 'currentY']);
expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl', ['colorTemperature']);
});
it('Should not retrieve the state when device is turned on/off within availability timeout on deviceJoined', async () => {
@@ -203,7 +201,7 @@ describe('Availability', () => {
settings.set(['advanced', 'availability_blocklist'], ['bulb_color'])
await controller.stop();
await flushPromises();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
device.ping.mockClear();
@@ -217,7 +215,7 @@ describe('Availability', () => {
settings.set(['advanced', 'availability_blacklist'], ['bulb_color'])
await controller.stop();
await flushPromises();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
device.ping.mockClear();
@@ -231,7 +229,7 @@ describe('Availability', () => {
settings.set(['advanced', 'availability_blocklist'], [device.ieeeAddr]);
await controller.stop();
await flushPromises();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
device.ping.mockClear();
@@ -258,7 +256,7 @@ describe('Availability', () => {
settings.set(['advanced', 'availability_passlist'], ['bulb_color']);
await controller.stop();
await flushPromises();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
device.ping.mockClear();
@@ -272,7 +270,7 @@ describe('Availability', () => {
settings.set(['advanced', 'availability_whitelist'], ['bulb_color']);
await controller.stop();
await flushPromises();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
device.ping.mockClear();
@@ -286,7 +284,7 @@ describe('Availability', () => {
settings.set(['advanced', 'availability_passlist'], [device.ieeeAddr]);
await controller.stop();
await flushPromises();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
device.ping.mockClear();
@@ -301,7 +299,7 @@ describe('Availability', () => {
settings.set(['advanced', 'availability_passlist'], ['0x000b57fffec6a5b3'])
await controller.stop();
await flushPromises();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
jest.advanceTimersByTime(11 * 1000);
@@ -387,7 +385,7 @@ describe('Availability', () => {
await controller.stop();
await flushPromises();
MQTT.publish.mockClear();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
getExtension().state[device.ieeeAddr] = false;
await controller.start();
await flushPromises();
+25 -8
View File
@@ -25,14 +25,14 @@ describe('Bind', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
zigbeeHerdsman.groups.group_1.members = [];
zigbeeHerdsman.devices.bulb_color.getEndpoint(1).configureReporting.mockClear();
zigbeeHerdsman.devices.bulb_color.getEndpoint(1).bind.mockClear();
zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read.mockClear();
debounce.mockClear();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
this.coordinatorEndoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
@@ -53,7 +53,7 @@ describe('Bind', () => {
mockClear(device);
target.configureReporting.mockImplementationOnce(() => {throw new Error("timeout")});
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({transaction: "1234", from: 'remote', to: 'bulb_color'}));
await flushPromises();
expect(target.read).toHaveBeenCalledWith('lightingColorCtrl', [ 'colorCapabilities' ]);
expect(endpoint.bind).toHaveBeenCalledTimes(4);
@@ -67,7 +67,7 @@ describe('Bind', () => {
expect(target.configureReporting).toHaveBeenCalledWith("lightingColorCtrl",[{"attribute":"colorTemperature","minimumReportInterval":5,"maximumReportInterval":3600,"reportableChange":1},{"attribute":"currentX","minimumReportInterval":5,"maximumReportInterval":3600,"reportableChange":1},{"attribute":"currentY","minimumReportInterval":5,"maximumReportInterval":3600,"reportableChange":1}]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl", "lightingColorCtrl"],"failed":[]},"status":"ok"}),
stringify({"transaction": "1234","data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl", "lightingColorCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
@@ -279,6 +279,22 @@ describe('Bind', () => {
);
});
it('Should bind server clusters to client clusters', async () => {
const device = zigbeeHerdsman.devices.temperature_sensor;
const target = zigbeeHerdsman.devices.heating_actuator.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'temperature_sensor', to: 'heating_actuator'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith("msTemperatureMeasurement", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({"data":{"from":"temperature_sensor","to":"heating_actuator","clusters":["msTemperatureMeasurement"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Should bind to default endpoint returned by endpoints()', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.QBKG04LM.getEndpoint(2);
@@ -519,15 +535,16 @@ describe('Bind', () => {
const payload = {data, cluster: 'genLevelCtrl', device: remote, endpoint: remote.getEndpoint(1), type: 'commandStepWithOnOff', linkquality: 10, groupID: 15071};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(debounce).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(1);
expect(debounce).toHaveBeenCalledTimes(2);
expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(2);
expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]);
expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledWith("genOnOff", ["onOff"]);
// Should also only debounce once
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(debounce).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(2);
expect(debounce).toHaveBeenCalledTimes(2);
expect(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledTimes(4);
// Should only call Hue bulb, not e.g. tradfri
expect(zigbeeHerdsman.devices.bulb_2.getEndpoint(1).read).toHaveBeenCalledTimes(0);
+217 -16
View File
File diff suppressed because one or more lines are too long
+36 -17
View File
@@ -26,6 +26,22 @@ describe('Configure', () => {
expect(device.meta.configured).toBe(1);
}
expectBulbConfigured = () => {
const device = zigbeeHerdsman.devices.bulb;
const endpoint1 = device.getEndpoint(1);
console.log(endpoint1.read.mock.calls);
expect(endpoint1.read).toHaveBeenCalledTimes(2);
expect(endpoint1.read).toHaveBeenCalledWith('lightingColorCtrl', ['colorCapabilities']);
expect(endpoint1.read).toHaveBeenCalledWith('lightingColorCtrl', [ 'colorTempPhysicalMin', 'colorTempPhysicalMax' ]);
}
expectBulbNotConfigured = () => {
const device = zigbeeHerdsman.devices.bulb;
const endpoint1 = device.getEndpoint(1);
console.log(endpoint1.read.mock.calls);
expect(endpoint1.read).toHaveBeenCalledTimes(0);
}
expectRemoteNotConfigured = () => {
const device = zigbeeHerdsman.devices.remote;
const endpoint1 = device.getEndpoint(1);
@@ -44,65 +60,68 @@ describe('Configure', () => {
beforeEach(async () => {
jest.useRealTimers();
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
mocksClear.forEach((m) => m.mockClear());
await flushPromises();
this.coordinatorEndoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
});
it('Should configure on startup', async () => {
expectRemoteConfigured();
it('Should configure Router on startup', async () => {
expectBulbConfigured();
});
it('Should not configure EndDevice on startup', async () => {
expectRemoteNotConfigured();
});
it('Should re-configure when device rejoins', async () => {
expectRemoteConfigured();
const device = zigbeeHerdsman.devices.remote;
expectBulbConfigured();
const device = zigbeeHerdsman.devices.bulb;
const endpoint = device.getEndpoint(1);
await flushPromises();
mockClear(device);
const payload = {device};
zigbeeHerdsman.events.deviceJoined(payload);
await flushPromises();
expectRemoteConfigured();
expectBulbConfigured();
});
it('Should reconfigure reporting on reportingDisabled event', async () => {
expectRemoteConfigured();
const device = zigbeeHerdsman.devices.remote;
expectBulbConfigured();
const device = zigbeeHerdsman.devices.bulb;
mockClear(device);
expectRemoteNotConfigured();
expectBulbNotConfigured();
controller.eventBus.emit('reportingDisabled', {device})
await flushPromises();
expectRemoteConfigured();
expectBulbConfigured();
});
it('Should not configure twice', async () => {
expectRemoteConfigured();
const device = zigbeeHerdsman.devices.remote;
expectBulbConfigured();
const device = zigbeeHerdsman.devices.bulb;
const endpoint = device.getEndpoint(1);
mockClear(device);
const payload = {data: {zclVersion: 1}, cluster: 'genBasic', device, endpoint, type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(0);
expectBulbNotConfigured();
});
it('Should configure on zigbee message when not configured yet', async () => {
const device = zigbeeHerdsman.devices.remote;
const device = zigbeeHerdsman.devices.bulb;
delete device.meta.configured;
const endpoint = device.getEndpoint(1);
mockClear(device);
const payload = {data: {zclVersion: 1}, cluster: 'genBasic', device, endpoint, type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expectRemoteConfigured();
expectBulbConfigured();
});
it('Should allow to configure via MQTT', async () => {
expectRemoteConfigured();
mockClear(zigbeeHerdsman.devices.remote);
expectRemoteNotConfigured();
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', 'remote');
+8 -17
View File
@@ -3,14 +3,13 @@ const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const MQTT = require('./stub/mqtt');
const path = require('path');
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const stringify = require('json-stable-stringify-without-jsonify');
const flushPromises = () => new Promise(setImmediate);
const tmp = require('tmp');
const mocksClear = [
zigbeeHerdsman.permitJoin, mockExit, MQTT.end, zigbeeHerdsman.stop, logger.debug,
zigbeeHerdsman.permitJoin, MQTT.end, zigbeeHerdsman.stop, logger.debug,
MQTT.publish, MQTT.connect, zigbeeHerdsman.devices.bulb_color.removeFromNetwork,
zigbeeHerdsman.devices.bulb.removeFromNetwork, logger.error,
];
@@ -19,13 +18,15 @@ const fs = require('fs');
describe('Controller', () => {
let controller;
let mockExit;
beforeEach(() => {
zigbeeHerdsman.returnDevices.splice(0);
controller = new Controller();
mockExit = jest.fn();
controller = new Controller(jest.fn(), mockExit);
mocksClear.forEach((m) => m.mockClear());
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeDefaultState();
});
@@ -228,7 +229,7 @@ describe('Controller', () => {
expect(MQTT.end).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(0);
expect(mockExit).toHaveBeenCalledWith(0, null);
});
it('Start controller and stop', async () => {
@@ -238,7 +239,7 @@ describe('Controller', () => {
expect(MQTT.end).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(1);
expect(mockExit).toHaveBeenCalledWith(1, null);
});
it('Start controller adapter disconnects', async () => {
@@ -249,7 +250,7 @@ describe('Controller', () => {
expect(MQTT.end).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(1);
expect(mockExit).toHaveBeenCalledWith(1, null);
});
it('Handle mqtt message', async () => {
@@ -602,16 +603,6 @@ describe('Controller', () => {
expect(controller.state.state).toStrictEqual({});
});
it('Load user extension', async () => {
const extensionPath = path.join(data.mockDir, 'extension');
fs.mkdirSync(extensionPath);
fs.copyFileSync(path.join(__dirname, 'assets', 'exampleExtension.js'), path.join(extensionPath, 'exampleExtension.js'))
controller = new Controller();
await controller.start();
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', { retain: false, qos: 0 }, expect.any(Function));
});
it('Start controller with force_disable_retain', async () => {
settings.set(['mqtt', 'force_disable_retain'], true);
await controller.start();
+6 -6
View File
@@ -47,12 +47,12 @@ describe('Loads external converters', () => {
mocksClear.forEach((m) => m.mockClear());
data.writeDefaultConfiguration();
data.writeEmptyState();
settings._reRead();
settings.reRead();
});
it('Does not load external converters', async () => {
settings.set(['external_converters'], []);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(0);
@@ -62,7 +62,7 @@ describe('Loads external converters', () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter.js'), path.join(data.mockDir, 'mock-external-converter.js'));
const devicesCount = zigbeeHerdsman.devices.lenght;
settings.set(['external_converters'], ['mock-external-converter.js']);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(1);
@@ -82,7 +82,7 @@ describe('Loads external converters', () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
const devicesCount = zigbeeHerdsman.devices.lenght;
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2);
@@ -110,7 +110,7 @@ describe('Loads external converters', () => {
it('Loads external converters from package', async () => {
settings.set(['external_converters'], ['mock-external-converter-module']);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(1);
@@ -121,7 +121,7 @@ describe('Loads external converters', () => {
it('Loads multiple external converters from package', async () => {
settings.set(['external_converters'], ['mock-multiple-external-converter-module']);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2);
+102
View File
@@ -0,0 +1,102 @@
const data = require('./stub/data');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const MQTT = require('./stub/mqtt');
const path = require('path');
const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const stringify = require('json-stable-stringify-without-jsonify');
const flushPromises = () => new Promise(setImmediate);
const tmp = require('tmp');
const mocksClear = [
zigbeeHerdsman.permitJoin, MQTT.end, zigbeeHerdsman.stop, logger.debug,
MQTT.publish, MQTT.connect, zigbeeHerdsman.devices.bulb_color.removeFromNetwork,
zigbeeHerdsman.devices.bulb.removeFromNetwork, logger.error,
];
const fs = require('fs');
const mkdirSyncSpy = jest.spyOn(fs, 'mkdirSync');
const unlinkSyncSpy = jest.spyOn(fs, 'unlinkSync');
describe('User extensions', () => {
let controller;
let mockExit;
beforeEach(() => {
zigbeeHerdsman.returnDevices.splice(0);
mockExit = jest.fn();
controller = new Controller(jest.fn(), mockExit);
mocksClear.forEach((m) => m.mockClear());
data.writeDefaultConfiguration();
settings.reRead();
data.writeDefaultState();
});
afterEach(() => {
const extensionPath = path.join(data.mockDir, 'extension');
fs.rmdirSync(extensionPath, {recursive: true});
})
it('Load user extension', async () => {
const extensionPath = path.join(data.mockDir, 'extension');
const extensionCode = fs.readFileSync(path.join(__dirname, 'assets', 'exampleExtension.js'), 'utf-8');
fs.mkdirSync(extensionPath);
fs.copyFileSync(path.join(__dirname, 'assets', 'exampleExtension.js'), path.join(extensionPath, 'exampleExtension.js'))
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', { retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([{"name": "exampleExtension.js", "code": extensionCode}]), { retain: true, qos: 0 }, expect.any(Function));
});
it('Load user extension from api call', async () => {
const extensionPath = path.join(data.mockDir, 'extension');
const extensionCode = fs.readFileSync(path.join(__dirname, 'assets', 'exampleExtension.js'), 'utf-8');
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/extension/save', stringify({"name": "foo.js", "code": extensionCode}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([{"name": "foo.js", "code": extensionCode}]), { retain: true, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from constructor', { retain: false, qos: 0 }, expect.any(Function));
expect(mkdirSyncSpy).toHaveBeenCalledWith(extensionPath);
});
it('Do not load corrupted extensions', async () => {
const extensionPath = path.join(data.mockDir, 'extension');
const extensionCode = "definetly not a correct javascript code";
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/extension/save', stringify({"name": "foo.js", "code": extensionCode}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/response/extension/save', stringify({"data":{},"error":"Unexpected identifier","status":"error"}), { retain: false, qos: 0 }, expect.any(Function));
});
it('Removes user extension', async () => {
const extensionPath = path.join(data.mockDir, 'extension');
const extensionCode = fs.readFileSync(path.join(__dirname, 'assets', 'exampleExtension.js'), 'utf-8');
fs.mkdirSync(extensionPath);
const extensionFilePath = path.join(extensionPath, 'exampleExtension.js')
fs.copyFileSync(path.join(__dirname, 'assets', 'exampleExtension.js'), extensionFilePath)
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', { retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([{"name": "exampleExtension.js", "code": extensionCode}]), { retain: true, qos: 0 }, expect.any(Function));
MQTT.events.message('zigbee2mqtt/bridge/request/extension/remove', stringify({"name": "exampleExtension.js"}));
await flushPromises();
expect(unlinkSyncSpy).toHaveBeenCalledWith(extensionFilePath);
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/extension/remove', stringify({"name": "non existing.js"}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/extension/remove',
stringify({"data":{},"status":"error","error":"Extension non existing.js doesn't exists"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
});
+12 -11
View File
@@ -74,7 +74,7 @@ describe('Frontend', () => {
mockWS.implementation.clients = [];
data.writeDefaultConfiguration();
data.writeDefaultState();
settings._reRead();
settings.reRead();
settings.set(['frontend'], {port: 8081, host: "127.0.0.1"});
settings.set(['homeassistant'], true);
zigbeeHerdsman.devices.bulb.linkquality = 10;
@@ -85,7 +85,7 @@ describe('Frontend', () => {
});
it('Start/stop', async () => {
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
expect(mockNodeStatic.variables.path).toBe("my/dummy/path");
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
@@ -93,6 +93,7 @@ describe('Frontend', () => {
const mockWSClient = {
implementation: {
close: jest.fn(),
send: jest.fn(),
},
events: {},
};
@@ -103,7 +104,7 @@ describe('Frontend', () => {
});
it('Websocket interaction', async () => {
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
// Connect
@@ -118,8 +119,8 @@ describe('Frontend', () => {
mockWS.implementation.clients.push(mockWSClient.implementation);
await mockWS.events.connection(mockWSClient.implementation);
expect(JSON.parse(mockWSClient.implementation.send.mock.calls[0])).toStrictEqual({topic: 'bridge/state', payload: 'online'});
expect(JSON.parse(mockWSClient.implementation.send.mock.calls[12])).toStrictEqual({topic:"remote", payload:{brightness:255, update:{state: "idle"}, update_available: false}});
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bridge/state', payload: 'online'}));
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic:"remote", payload:{brightness:255, update:{state: "idle"}, update_available: false}}));
// Message
MQTT.publish.mockClear();
@@ -129,7 +130,7 @@ describe('Frontend', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({state: 'ON'}),
stringify({state: 'ON', linkquality: null}),
{ retain: false, qos: 0 },
expect.any(Function)
);
@@ -140,7 +141,7 @@ describe('Frontend', () => {
// Received message on socket
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(4);
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON'}}));
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', linkquality: null}}));
// Shouldnt set when not ready
mockWSClient.implementation.send.mockClear();
@@ -154,11 +155,11 @@ describe('Frontend', () => {
settings.set(['advanced'], {last_seen: 'ISO_8601'});
mockWS.implementation.clients.push(mockWSClient.implementation);
await mockWS.events.connection(mockWSClient.implementation);
expect(JSON.parse(mockWSClient.implementation.send.mock.calls[12])).toStrictEqual({topic:"remote", payload:{brightness:255, last_seen: "1970-01-01T00:00:01.000Z", update:{state: "idle"}, update_available: false}});
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic:"remote", payload:{brightness:255, last_seen: "1970-01-01T00:00:01.000Z", update:{state: "idle"}, update_available: false}}));
});
it('onReques/onUpgrade', async () => {
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
const mockSocket = {destroy: jest.fn()};
@@ -176,7 +177,7 @@ describe('Frontend', () => {
});
it('Static server', async () => {
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
@@ -185,7 +186,7 @@ describe('Frontend', () => {
it('Authentification', async () => {
const authToken = 'sample-secure-token'
settings.set(['frontend'], {auth_token: authToken});
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
const mockSocket = {destroy: jest.fn()};
+63 -4
View File
@@ -9,6 +9,7 @@ zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b2');
zigbeeHerdsman.returnDevices.push('0x0017880104e45542');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b4');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b7');
zigbeeHerdsman.returnDevices.push('0x0017880104e45724');
const MQTT = require('./stub/mqtt');
const Controller = require('../lib/controller');
@@ -20,11 +21,12 @@ describe('Groups', () => {
beforeEach(() => {
data.writeEmptyState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
Object.values(zigbeeHerdsman.groups).forEach((g) => g.members = []);
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
MQTT.publish.mockClear();
zigbeeHerdsman.groups.gledopto_group.command.mockClear();
zigbeeHerdsmanConverters.toZigbeeConverters.__clearStore__();
})
@@ -371,6 +373,35 @@ describe('Groups', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should publish state of device with endpoint name', async () => {
const group = zigbeeHerdsman.groups.gledopto_group;
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/gledopto_group/set', stringify({state: 'ON'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/GLEDOPTO_2ID", stringify({"state_cct":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/gledopto_group", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith("genOnOff", "on", {}, {});
});
it('Should publish state of group when specific state of specific endpoint is changed', async () => {
const group = zigbeeHerdsman.groups.gledopto_group;
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/GLEDOPTO_2ID/set', stringify({state_cct: 'ON'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/GLEDOPTO_2ID", stringify({"state_cct":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/gledopto_group", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(group.command).toHaveBeenCalledTimes(0);
});
it('Should publish state change of all members when a group changes its state, filtered', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
@@ -505,6 +536,34 @@ describe('Groups', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should only update group state with changed properties', async () => {
const device_1 = zigbeeHerdsman.devices.bulb_color;
const device_2 = zigbeeHerdsman.devices.bulb;
const endpoint_1 = device_1.getEndpoint(1);
const endpoint_2 = device_2.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint_1);
group.members.push(endpoint_2);
settings.set(['groups'], {
'1': {friendly_name: 'group_1', devices: [device_1.ieeeAddr, device_2.ieeeAddr], retain: false}
});
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF', color_temp: 200}));
await MQTT.events.message('zigbee2mqtt/bulb/set', stringify({state: 'ON', color_temp: 250}));
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({color_temp: 300}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"color_mode": "color_temp", "color":{"x":0.415211980162654,"y":0.395434886759171},"color_temp":300,"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"color_mode": "color_temp", "color":{"x":0.415211980162654,"y":0.395434886759171},"color_temp":300,"state":"ON"}), {"retain": true, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"color_mode": "color_temp", "color":{"x":0.415211980162654,"y":0.395434886759171},"color_temp":300,"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should publish state change off even when missing current state', async () => {
const device_1 = zigbeeHerdsman.devices.bulb_color;
const device_2 = zigbeeHerdsman.devices.bulb;
@@ -541,14 +600,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({transaction: "123", group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/1`]);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"bulb_color","group":"group_1"},"transaction": "123", "status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
+46 -27
View File
@@ -18,7 +18,7 @@ describe('HomeAssistant extension', () => {
this.version = `Zigbee2MQTT ${this.version.version}`;
jest.useRealTimers();
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
MQTT.publish.mockClear();
settings.set(['homeassistant'], true);
@@ -248,7 +248,10 @@ describe('HomeAssistant extension', () => {
],
"brightness":true,
"brightness_scale":254,
"color_temp":true,
"color_mode": true,
"supported_color_modes": ["color_temp"],
"min_mireds": 250,
"max_mireds": 454,
"command_topic":"zigbee2mqtt/bulb/set",
"device":{
"identifiers":[
@@ -268,13 +271,11 @@ describe('HomeAssistant extension', () => {
"finish_effect",
"stop_effect"
],
"hs":false,
"json_attributes_topic":"zigbee2mqtt/bulb",
"name":"bulb",
"schema":"json",
"state_topic":"zigbee2mqtt/bulb",
"unique_id":"0x000b57fffec6a5b2_light_zigbee2mqtt",
"xy":false
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -601,8 +602,6 @@ describe('HomeAssistant extension', () => {
await flushPromises();
payload = {
"action_template":"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}",
"action_topic":"zigbee2mqtt/TS0601_thermostat",
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
"away_mode_command_topic":"zigbee2mqtt/TS0601_thermostat/set/away_mode",
"away_mode_state_template":"{{ value_json.away_mode }}",
@@ -667,7 +666,7 @@ describe('HomeAssistant extension', () => {
position_topic: 'zigbee2mqtt/smart vent',
set_position_topic: 'zigbee2mqtt/smart vent/set',
set_position_template: '{ "position": {{ position }} }',
value_template: '{{ value_json.position }}',
position_template: '{{ value_json.position }}',
json_attributes_topic: 'zigbee2mqtt/smart vent',
name: 'smart vent',
unique_id: '0x0017880104e45551_cover_zigbee2mqtt',
@@ -738,6 +737,26 @@ describe('HomeAssistant extension', () => {
expect(logger.warn).toHaveBeenCalledWith("In order for HomeAssistant integration to work properly set `cache_state: true");
});
it('Should set missing values to null', async () => {
// https://github.com/Koenkk/zigbee2mqtt/issues/6987
controller = new Controller(false);
await controller.start();
await flushPromises();
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
const data = {measuredValue: -85}
const payload = {data, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
MQTT.publish.mockClear();
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/weather_sensor',
stringify({"battery":null,"humidity":null,"linkquality":null,"pressure":null,"temperature":-0.85,"voltage":null}),
{ retain: false, qos: 1 },
expect.any(Function),
);
});
it('Shouldt discover when already discovered', async () => {
controller = new Controller(false);
await controller.start();
@@ -790,7 +809,7 @@ describe('HomeAssistant extension', () => {
});
it('Shouldnt discover when device leaves', async () => {
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant').discovered = {};
@@ -804,7 +823,7 @@ describe('HomeAssistant extension', () => {
it('Should send all status when home assistant comes online (default topic)', async () => {
jest.useFakeTimers();
data.writeDefaultState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
expect(MQTT.subscribe).toHaveBeenCalledWith('homeassistant/status');
await flushPromises();
@@ -821,7 +840,7 @@ describe('HomeAssistant extension', () => {
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/remote',
stringify({"brightness":255,"update_available":false,"update":{"state":"idle"}}),
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null,"update":{"state":"idle"},"update_available":false}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -830,7 +849,7 @@ describe('HomeAssistant extension', () => {
it('Should send all status when home assistant comes online', async () => {
jest.useFakeTimers();
data.writeDefaultState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(MQTT.subscribe).toHaveBeenCalledWith('hass/status');
@@ -847,7 +866,7 @@ describe('HomeAssistant extension', () => {
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/remote',
stringify({"brightness":255,"update_available":false,"update":{"state":"idle"}}),
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null,"update":{"state":"idle"},"update_available":false}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -856,7 +875,7 @@ describe('HomeAssistant extension', () => {
it('Shouldnt send all status when home assistant comes offline', async () => {
jest.useFakeTimers();
data.writeDefaultState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
@@ -870,7 +889,7 @@ describe('HomeAssistant extension', () => {
it('Shouldnt send all status when home assistant comes online with different topic', async () => {
jest.useFakeTimers();
data.writeDefaultState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
@@ -1109,7 +1128,7 @@ describe('HomeAssistant extension', () => {
await flushPromises();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(4);
expect(discovered.length).toBe(5);
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/click/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/action/config');
@@ -1184,21 +1203,21 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({action: "single", click: "single"}),
stringify({action: "single", click: "single", battery: null, linkquality: null, voltage: null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({action: ""}),
stringify({action: "", battery: null, linkquality: null, voltage: null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({click: ""}),
stringify({click: "", action: null, battery: null, linkquality: null, voltage: null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -1289,7 +1308,7 @@ describe('HomeAssistant extension', () => {
await flushPromises();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(3);
expect(discovered.length).toBe(4);
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/action/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/battery/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/linkquality/config');
@@ -1302,7 +1321,7 @@ describe('HomeAssistant extension', () => {
await flushPromises();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(2);
expect(discovered.length).toBe(3);
expect(discovered).not.toContain('homeassistant/sensor/0x0017880104e45520/click/config');
expect(discovered).not.toContain('homeassistant/sensor/0x0017880104e45520/action/config');
@@ -1347,7 +1366,7 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({action: "single"}),
stringify({action: "single", "battery":null,"linkquality":null,"voltage":null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -1363,9 +1382,9 @@ describe('HomeAssistant extension', () => {
await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', stringify({state: 'ON', brightness: 20}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB', stringify({state_l2:"ON", brightness_l2:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB', stringify({state_l2:"ON", brightness_l2:20, linkquality: null, state_l1: null}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l2', stringify({state:"ON", brightness:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l1', stringify({}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l1', stringify({state: null}), {"qos": 0, "retain": false}, expect.any(Function));
});
it('Shouldnt crash in onPublishEntityState on group publish', async () => {
@@ -1393,10 +1412,10 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single'});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single', battery: null, linkquality: null, voltage: null});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/button');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({action: ''});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({action: '', battery: null, linkquality: null, voltage: null});
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('homeassistant/device_automation/0x0017880104e45520/action_single/config');
expect(MQTT.publish.mock.calls[3][0]).toStrictEqual('zigbee2mqtt/button/action');
@@ -1405,7 +1424,7 @@ describe('HomeAssistant extension', () => {
it('Load Home Assistant mapping from external converters', async () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
const ha = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant');
await controller.start();
await flushPromises();
@@ -1424,7 +1443,7 @@ describe('HomeAssistant extension', () => {
});
it('Should clear outdated configs', async () => {
controller = new Controller(false);
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
+4 -4
View File
@@ -15,13 +15,13 @@ describe('Bridge legacy', () => {
beforeAll(async () => {
this.version = await require('../../lib/util/utils').getZigbee2mqttVersion();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
})
beforeEach(() => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeDefaultState();
logger.info.mockClear();
logger.warn.mockClear();
@@ -188,7 +188,7 @@ describe('Bridge legacy', () => {
await flushPromises();
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
const payload = JSON.parse(MQTT.publish.mock.calls[0][1]);
expect(payload).toStrictEqual({"message":[{"ID":1,"devices":[],"friendly_name":"group_1","optimistic":true,"retain":false},{"ID":2,"devices":[],"friendly_name":"group_2","optimistic":true,"retain":false},{"ID":11,"devices":["bulb_2"],"friendly_name":"group_with_tradfri","optimistic":true,"retain":false},{"ID":12,"devices":["TS0601_thermostat"],"friendly_name":"thermostat_group","optimistic":true,"retain":false},{"ID":14,"devices":["power_plug"],"friendly_name":"switch_group","optimistic":true,"retain":false},{"ID":15071,"devices":["bulb_color_2","bulb_2"],"friendly_name":"group_tradfri_remote","optimistic":true,"retain":false}],"type":"groups"});
expect(payload).toStrictEqual({"message":[{"ID":1,"devices":[],"friendly_name":"group_1","optimistic":true,"retain":false},{"ID":2,"devices":[],"friendly_name":"group_2","optimistic":true,"retain":false},{"ID":11,"devices":["bulb_2"],"friendly_name":"group_with_tradfri","optimistic":true,"retain":false},{"ID":12,"devices":["TS0601_thermostat"],"friendly_name":"thermostat_group","optimistic":true,"retain":false},{"ID":14,"devices":["power_plug"],"friendly_name":"switch_group","optimistic":true,"retain":false},{"ID":21,"devices":["GLEDOPTO_2ID/cct"],"friendly_name":"gledopto_group","optimistic":true},{"ID":15071,"devices":["bulb_color_2","bulb_2"],"friendly_name":"group_tradfri_remote","optimistic":true,"retain":false}],"type":"groups"});
});
it('Should allow rename devices', async () => {
@@ -251,7 +251,7 @@ describe('Bridge legacy', () => {
});
it('Shouldnt rename when no device has been joined', async () => {
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(settings.getDevice('0x000b57fffec6a5b2').friendlyName).toStrictEqual('bulb');
+4 -4
View File
@@ -71,7 +71,7 @@ describe('Report', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
settings.set(['advanced', 'report'], true);
for (const device of Object.values(zigbeeHerdsman.devices)) {
@@ -79,7 +79,7 @@ describe('Report', () => {
delete device.meta.reporting;
}
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
mocksClear.forEach((m) => m.mockClear());
await flushPromises();
@@ -97,7 +97,7 @@ describe('Report', () => {
mockClear(device);
delete device.meta.report;
settings.set(['advanced', 'report'], false);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expect(device.meta.reporting).toBe(undefined);
@@ -110,7 +110,7 @@ describe('Report', () => {
const endpoint = device.getEndpoint(1);
settings.set(['advanced', 'report'], false);
mockClear(device);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
expectOnOffBrightnessColorReportDisabled(endpoint, true);
+11 -1
View File
@@ -14,7 +14,7 @@ describe('Logger', () => {
jest.resetModules();
settings = require('../lib/util/settings');
settings.set(['advanced', 'log_directory'], dir.name + '/%TIMESTAMP%');
settings._reRead();
settings.reRead();
stdOutWriteOriginal = console._stdout.write;
console._stdout.write = () => {};
});
@@ -138,4 +138,14 @@ describe('Logger', () => {
expect(pipes[1].tailable).toBeFalsy();
expect(pipes[1].maxsize).toBeNull();
});
it('Should allow to symlink logs to current directory', () => {
settings.set(['advanced', 'log_symlink_current'], true);
let logger = require('../lib/util/logger.js');
logger.logOutput();
expect(fs.readdirSync(dir.name).includes('current')).toBeTruthy()
jest.resetModules();
logger = require('../lib/util/logger.js');
});
});
+4 -4
View File
@@ -27,11 +27,11 @@ describe('Networkmap', () => {
beforeAll(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter.js'), path.join(data.mockDir, 'mock-external-converter.js'));
settings.set(['external_converters'], ['mock-external-converter.js']);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
mocksClear.forEach((m) => m.mockClear());
await flushPromises();
@@ -97,7 +97,7 @@ describe('Networkmap', () => {
let call = MQTT.publish.mock.calls[0];
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/raw');
const expected = {"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description": "external","model":"external_converter_device","supports":"","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]};
const expected = {"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description": "external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]};
expect(JSON.parse(call[1])).toStrictEqual(expected);
/**
@@ -271,7 +271,7 @@ describe('Networkmap', () => {
let call = MQTT.publish.mock.calls[0];
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"};
const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"};
const actual = JSON.parse(call[1]);
expect(actual).toStrictEqual(expected);
});
+2 -2
View File
@@ -23,9 +23,9 @@ describe('On event', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
mocksClear.forEach((m) => m.mockClear());
zigbeeHerdsmanConverters.onEvent.mockClear();
+24 -10
View File
@@ -18,10 +18,10 @@ describe('OTA update', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
settings.set(['advanced', 'ikea_ota_use_test_url'], true);
data.writeEmptyState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
@@ -51,7 +51,7 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(1);
expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, logger, expect.any(Function));
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, +- 60 minutes remaining`);
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, 60 minutes remaining`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`);
expect(device.save).toHaveBeenCalledTimes(1);
expect(device.dateCode).toBe('20190102');
@@ -166,11 +166,11 @@ describe('OTA update', () => {
});
it('Should not check for OTA when device does not support it', async () => {
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "ZNLDP12LM");
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "dimmer_wall_switch");
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({"data":{"id": "ZNLDP12LM"},"status":"error","error": `Device 'ZNLDP12LM' does not support OTA updates`}),
stringify({"data":{"id": "dimmer_wall_switch"},"status":"error","error": `Device 'dimmer_wall_switch' does not support OTA updates`}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -250,8 +250,22 @@ describe('OTA update', () => {
);
});
it('Should not check for update when device requests it and disable_automatic_update_check is set to true', async () => {
settings.set(['ota', 'disable_automatic_update_check'], true);
const device = zigbeeHerdsman.devices.bulb;
const data = {imageType: 12382};
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockReturnValueOnce(true);
const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10};
logger.info.mockClear();
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(0);
});
it('Should respond with NO_IMAGE_AVAILABLE when not supporting OTA', async () => {
const device = zigbeeHerdsman.devices.QBKG04LM;
const device = zigbeeHerdsman.devices.HGZB04D;
const data = {imageType: 12382};
const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
@@ -261,7 +275,7 @@ describe('OTA update', () => {
});
it('Shouldnt respond with NO_IMAGE_AVAILABLE when not supporting OTA and device has no OTA endpoint', async () => {
const device = zigbeeHerdsman.devices.QBKG03LM;
const device = zigbeeHerdsman.devices.SV01;
const data = {imageType: 12382};
const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10};
logger.error.mockClear();
@@ -296,7 +310,7 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(1);
expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, logger, expect.any(Function));
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, +- 60 minutes remaining`);
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, 60 minutes remaining`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`);
expect(logger.error).toHaveBeenCalledTimes(0);
expect(device.save).toHaveBeenCalledTimes(1);
@@ -360,9 +374,9 @@ describe('OTA update', () => {
});
it('Legacy api: Should not check for OTA when device does not support it', async () => {
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'ZNLDP12LM');
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'dimmer_wall_switch');
await flushPromises();
expect(logger.error).toHaveBeenCalledWith(`Device 'ZNLDP12LM' does not support OTA updates`);
expect(logger.error).toHaveBeenCalledWith(`Device 'dimmer_wall_switch' does not support OTA updates`);
});
it('Legacy api: Shouldnt crash when read modelID after OTA update fails', async () => {
+51 -29
View File
@@ -28,7 +28,7 @@ describe('Publish', () => {
beforeAll(async () => {
data.writeEmptyState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
});
@@ -38,7 +38,7 @@ describe('Publish', () => {
await flushPromises();
data.writeDefaultConfiguration();
controller.state.state = {};
settings._reRead();
settings.reRead();
mocksClear.forEach((m) => m.mockClear());
Object.values(zigbeeHerdsman.devices).forEach((d) => {
d.endpoints.forEach((e) => {
@@ -139,7 +139,7 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColorTemp", {colortemp: 222, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 222, color: {x: 0.360786471097048, y: 0.363543544699483}});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_mode: 'color_temp', color_temp: 222, color: {x: 0.360786471097048, y: 0.363543544699483}});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -152,7 +152,7 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColorTemp", {colortemp: 500, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 500, color: {x: 0.526676280311873, y: 0.41329727450763}});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_mode: 'color_temp', color_temp: 500, color: {x: 0.526676280311873, y: 0.41329727450763}});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -219,7 +219,7 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 6553500, colory: 3276750, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 62, color: {x: 100, y: 50}});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_mode: 'xy', color_temp: 62, color: {x: 100, y: 50}});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -236,7 +236,7 @@ describe('Publish', () => {
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color: {x: 100, y: 50}, color_temp: 62, state: 'ON'});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color_mode: 'xy', color: {x: 100, y: 50}, color_temp: 62, state: 'ON'});
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -253,7 +253,7 @@ describe('Publish', () => {
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 20});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color: {x: 100, y: 50}, state: 'ON', color_temp: 62, brightness: 20});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color_mode: 'xy', color: {x: 100, y: 50}, state: 'ON', color_temp: 62, brightness: 20});
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -270,7 +270,7 @@ describe('Publish', () => {
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 20});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color: {x: 100, y: 50}, state: 'ON', color_temp: 62, brightness: 20});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color: {x: 100, y: 50}, state: 'ON', color_temp: 62, brightness: 20, color_mode: 'xy'});
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -284,10 +284,10 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenNthCalledWith(2, "genOnOff", "off", {}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color: {x: 100, y: 50}, color_temp: 62});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_mode: 'xy', color: {x: 100, y: 50}, color_temp: 62});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color: {x: 100, y: 50}, color_temp: 62, state: 'OFF'});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({color_mode: 'xy', color: {x: 100, y: 50}, color_temp: 62, state: 'OFF'});
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -300,7 +300,7 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 17721, colory: 43148, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 157, color: {x: 0.2704, y: 0.6584}});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 157, color: {x: 0.2704, y: 0.6584}, color_mode: 'xy'});
});
it('Should publish messages to zigbee devices with color rgb', async () => {
@@ -312,7 +312,7 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 17721, colory: 43148, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 157, color: {x: 0.2704, y: 0.6584}});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 157, color: {x: 0.2704, y: 0.6584}, color_mode: 'xy'});
});
it('Should publish messages to zigbee devices with color rgb', async () => {
@@ -343,10 +343,10 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({brightness_percent: 50}));
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 127, transtime: 0}, {});
expect(group.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 128, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 127});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 128});
});
it('Should publish messages to groups when converter is not in the default list but device in it supports it', async () => {
@@ -387,7 +387,7 @@ describe('Publish', () => {
expect(group.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 24248, colory: 18350, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color: {x: 0.37, y: 0.28}, color_temp: 249});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color: {x: 0.37, y: 0.28}, color_temp: 249, color_mode: 'xy'});
});
it('Should publish messages to groups color temperature', async () => {
@@ -398,15 +398,15 @@ describe('Publish', () => {
expect(group.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColorTemp", {colortemp: 100, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color: {x: 0.280632719756407, y: 0.288286029784579}, color_temp: 100});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color: {x: 0.280632719756407, y: 0.288286029784579}, color_temp: 100, color_mode: 'color_temp'});
});
it('Should create and publish to group which is in configuration.yaml but not in zigbee-herdsman', async () => {
delete zigbeeHerdsman.groups.group_2;
expect(Object.values(zigbeeHerdsman.groups).length).toBe(7);
expect(Object.values(zigbeeHerdsman.groups).length).toBe(8);
await MQTT.events.message('zigbee2mqtt/group_2/set', stringify({state: 'ON'}));
await flushPromises();
expect(Object.values(zigbeeHerdsman.groups).length).toBe(8);
expect(Object.values(zigbeeHerdsman.groups).length).toBe(9);
expect(zigbeeHerdsman.groups.group_2.command).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.groups.group_2.command).toHaveBeenCalledWith("genOnOff", "on", {}, {});
});
@@ -554,7 +554,7 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 17721, colory: 43148, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 157, color: {x: 0.2704, y: 0.6584}});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color_temp: 157, color: {x: 0.2704, y: 0.6584}, color_mode: "xy"});
});
it('Should parse set with ieeeAddr topic', async () => {
@@ -910,10 +910,10 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "enhancedMoveToHueAndSaturation", {"direction": 0, "enhancehue": 44891.475, "saturation": 199.21474, "transtime": 0,}, {}]);
expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "enhancedMoveToHueAndSaturation", {"direction": 0, "enhancehue": 44891, "saturation": 199, "transtime": 0,}, {}]);
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({"color":{"hue":250,"saturation":50}});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({"color":{"hue":250,"saturation":50}, "color_mode": "hs"});
});
it('ZNCLDJ11LM open', async () => {
@@ -1064,6 +1064,7 @@ describe('Publish', () => {
it('Home Assistant: should not set state when color temperature is also set and device is already on', async () => {
settings.set(['homeassistant'], true);
const device = zigbeeHerdsman.devices.bulb_color;
controller.state.remove(device.ieeeAddr);
controller.state.set(device.ieeeAddr, {state: 'ON'})
const endpoint = device.getEndpoint(1);
const payload = {state: 'ON', color_temp: 100};
@@ -1073,12 +1074,13 @@ describe('Publish', () => {
expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "moveToColorTemp", {colortemp: 100, transtime: 0}, {}]);
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON', color: {x: 0.280632719756407, y: 0.288286029784579}, color_temp: 100});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON', color: {x: 0.280632719756407, y: 0.288286029784579}, color_temp: 100, color_mode: 'color_temp'});
});
it('Home Assistant: should set state when color temperature is also set and device is off', async () => {
settings.set(['homeassistant'], true);
const device = zigbeeHerdsman.devices.bulb_color;
controller.state.remove(device.ieeeAddr);
controller.state.set(device.ieeeAddr, {state: 'OFF'})
const endpoint = device.getEndpoint(1);
const payload = {state: 'ON', color_temp: 100};
@@ -1091,12 +1093,13 @@ describe('Publish', () => {
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({state: 'ON', color: {x: 0.280632719756407, y: 0.288286029784579}, color_temp: 100});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({state: 'ON', color: {x: 0.280632719756407, y: 0.288286029784579}, color_temp: 100, color_mode: 'color_temp'});
});
it('Home Assistant: should not set state when color is also set', async () => {
settings.set(['homeassistant'], true);
const device = zigbeeHerdsman.devices.bulb_color;
controller.state.remove(device.ieeeAddr);
controller.state.set(device.ieeeAddr, {state: 'ON'})
const endpoint = device.getEndpoint(1);
const payload = {state: 'ON', color: {x: 0.41, y: 0.25}};
@@ -1106,7 +1109,7 @@ describe('Publish', () => {
expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "moveToColor", {colorx: 26869, colory: 16384, transtime: 0}, {}]);
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color: {x: 0.41, y: 0.25}, color_temp: 150, state: 'ON'});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({color: {x: 0.41, y: 0.25}, color_temp: 150, state: 'ON', color_mode: 'xy'});
});
it('Should publish correct state on toggle command to zigbee bulb', async () => {
@@ -1282,6 +1285,25 @@ describe('Publish', () => {
expect(logger.error).toHaveBeenCalledWith("No converter available for 'get' 'brightness_move' (20)");
});
it('Should restore brightness when its turned on with transition, Z2M is restarted and turned on again', async () => {
// https://github.com/Koenkk/zigbee2mqtt/issues/7106
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
endpoint.command.mockClear();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', brightness: 20, transition: 0.0}));
await flushPromises();
zigbeeHerdsmanConverters.toZigbeeConverters.__clearStore__();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "transition": 1.0}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 20, transtime: 0}, {});
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 20, transtime: 10}, {});
});
it('Should restore brightness when its turned off without transition and is turned on with', async () => {
// https://github.com/Koenkk/zigbee-herdsman-converters/issues/1097
const device = zigbeeHerdsman.devices.bulb_color;
@@ -1362,27 +1384,27 @@ describe('Publish', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(5);
expect(MQTT.publish).toHaveBeenNthCalledWith(1,
'zigbee2mqtt/group_tradfri_remote',
stringify({"brightness":50,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON"}),
stringify({"brightness":50,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON","color_mode": "color_temp"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenNthCalledWith(2,
'zigbee2mqtt/bulb_color_2',
stringify({"brightness":50,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON"}),
stringify({"color_mode": "color_temp", "brightness":50,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenNthCalledWith(3,
'zigbee2mqtt/group_tradfri_remote',
stringify({"brightness":100,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON"}),
stringify({"brightness":100,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON","color_mode": "color_temp"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenNthCalledWith(4,
'zigbee2mqtt/bulb_2',
stringify({"brightness":100,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON"}),
stringify({"brightness":100,"state":"ON"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenNthCalledWith(5,
'zigbee2mqtt/group_with_tradfri',
stringify({"brightness":100,"color":{"x":0.408707336668894,"y":0.39239142575868},"color_temp":290,"state":"ON"}),
stringify({"brightness":100,"state":"ON"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
+3 -3
View File
@@ -15,9 +15,9 @@ describe('Receive', () => {
beforeEach(async () => {
jest.useRealTimers();
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
mocksClear.forEach((m) => m.mockClear());
delete zigbeeHerdsman.devices.WXKG11LM.linkquality;
@@ -64,7 +64,7 @@ describe('Receive', () => {
const payload = {data, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -0.85});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": false});
+17 -17
View File
@@ -22,7 +22,7 @@ describe('Settings', () => {
const write = (file, json, reread=true) => {
fs.writeFileSync(file, yaml.safeDump(json))
if (reread) {
settings._reRead();
settings.reRead();
}
};
const read = (file) => yaml.safeLoad(fs.readFileSync(file, 'utf8'));
@@ -616,7 +616,7 @@ describe('Settings', () => {
advanced: {network_key: 'NOT_GENERATE'},
});
settings._reRead();
settings.reRead();
const error = `advanced.network_key: should be array or 'GENERATE' (is 'NOT_GENERATE')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -628,7 +628,7 @@ describe('Settings', () => {
advanced: {pan_id: 'NOT_GENERATE'},
});
settings._reRead();
settings.reRead();
const error = `advanced.pan_id: should be number or 'GENERATE' (is 'NOT_GENERATE')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -641,7 +641,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'tain', retention: 900}},
});
settings._reRead();
settings.reRead();
expect(settings.validate()).toEqual([]);
});
@@ -652,7 +652,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'tain', retention: 900}},
});
settings._reRead();
settings.reRead();
const error = 'MQTT retention requires protocol version 5';
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -665,7 +665,7 @@ describe('Settings', () => {
advanced: {availability_blocklist: ['0x0017880104e45519', 'non_existing']},
});
settings._reRead();
settings.reRead();
const error = `Non-existing entity 'non_existing' specified in 'availability_blocklist'`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -677,7 +677,7 @@ describe('Settings', () => {
advanced: null,
});
settings._reRead();
settings.reRead();
const error = `advanced should be object`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -723,7 +723,7 @@ describe('Settings', () => {
groups: {'1': {friendly_name: 'myname', retain: false}},
});
settings._reRead();
settings.reRead();
const error = `Duplicate friendly_name 'myname' found`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -735,9 +735,9 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: '', retain: false}},
});
settings._reRead();
settings.reRead();
const error = `friendly_name shoud atleast be 1 char long`;
const error = `friendly_name must be at least 1 char long`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
@@ -747,7 +747,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'blaa/', retain: false}},
});
settings._reRead();
settings.reRead();
const error = `friendly_name is not allowed to end or start with /`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -759,7 +759,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'blaa/blaa' + String.fromCharCode(0), retain: false}},
});
settings._reRead();
settings.reRead();
const error = `friendly_name is not allowed to contain null char`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -771,7 +771,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'myname/123', retain: false}},
});
settings._reRead();
settings.reRead();
const error = `Friendly name cannot end with a "/DIGIT" ('myname/123')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -783,7 +783,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'myname#', retain: false}},
});
settings._reRead();
settings.reRead();
const error = `MQTT wildcard (+ and #) not allowed in friendly_name ('myname#')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -795,7 +795,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'left', retain: false}},
});
settings._reRead();
settings.reRead();
const error = `Following friendly_name are not allowed: '${utils.getEndpointNames()}'`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -809,7 +809,7 @@ describe('Settings', () => {
},
});
settings._reRead();
settings.reRead();
expect(() => {
settings.changeFriendlyName('myname1', 'myname');
@@ -824,7 +824,7 @@ describe('Settings', () => {
},
});
settings._reRead();
settings.reRead();
expect(() => {
settings.removeDevice('myname33');
+13
View File
@@ -165,6 +165,15 @@ function writeDefaultConfiguration() {
'0x0017882194e45543': {
friendly_name: 'QS-Zigbee-D02-TRIAC-2C-LN',
},
'0x0017880104e45724': {
friendly_name: 'GLEDOPTO_2ID',
},
'0x0017880104e45561': {
friendly_name: 'temperature_sensor',
},
'0x0017880104e45562': {
friendly_name: 'heating_actuator',
}
},
groups: {
'1': {
@@ -194,6 +203,10 @@ function writeDefaultConfiguration() {
friendly_name: 'switch_group',
retain: false,
devices: ['power_plug'],
},
'21': {
friendly_name: 'gledopto_group',
devices: ['GLEDOPTO_2ID/cct'],
}
},
external_converters: [],
+16 -4
View File
@@ -26,10 +26,12 @@ const clusters = {
'genLevelCtrl': 8,
'lightingColorCtrl': 768,
'closuresWindowCovering': 258,
'hvacThermostat': 513,
'msTemperatureMeasurement': 1026,
}
class Endpoint {
constructor(ID, inputClusters, outputClusters, deviceIeeeAddress, binds=[], clusterValues={}, configuredReportings=[]) {
constructor(ID, inputClusters, outputClusters, deviceIeeeAddress, binds=[], clusterValues={}, configuredReportings=[], profileID=null, deviceID=null) {
this.deviceIeeeAddress = deviceIeeeAddress;
this.clusterValues = clusterValues;
this.ID = ID;
@@ -45,6 +47,8 @@ class Endpoint {
this.configureReporting = jest.fn();
this.meta = {};
this.binds = binds;
this.profileID = profileID;
this.deviceID = deviceID;
this.configuredReportings = configuredReportings;
this.getInputClusters = () => inputClusters.map((c) => {
return {ID: c, name: getKeyByValue(clusters, c)};
@@ -127,6 +131,7 @@ const bulb_color_2 = new Device('Router', '0x000b57fffec6a5b4', 401292, 4107, [n
const bulb_2 = new Device('Router', '0x000b57fffec6a5b7', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b7', [], {lightingColorCtrl: {colorCapabilities: 17}})], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm");
const TS0601_thermostat = new Device('EndDevice', '0x0017882104a44559', 6544,4151, [new Endpoint(1, [], [], '0x0017882104a44559')], true, "Mains (single phase)", 'kud7u2l');
const ZNCZ02LM = new Device('Router', '0x0017880104e45524', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45524')], true, "Mains (single phase)", "lumi.plug");
const GLEDOPTO_2ID = new Device('Router', '0x0017880104e45724', 6540,4151, [new Endpoint(11, [0,3,4,5,6,8,768], [], '0x0017880104e45724', [], {}, [], 49246, 528), new Endpoint(12, [0, 3, 4, 5, 6, 8, 768], [], '0x0017880104e45724', [], {}, [], 260, 258), new Endpoint(13, [4096], [4096], '0x0017880104e45724', [], {}, [], 49246, 57694), new Endpoint(15, [0, 3, 4, 5, 6, 8, 768], [], '0x0017880104e45724', [], {}, [], 49246, 256)], true, "Mains (single phase)", 'GL-C-007', false, 'GLEDOPTO');
const groups = {
'group_1': new Group(1, []),
@@ -135,6 +140,7 @@ const groups = {
'group_with_tradfri': new Group(11, [bulb_2.endpoints[0]]),
'thermostat_group': new Group(12, [TS0601_thermostat.endpoints[0]]),
'group_with_switch': new Group(14, [ZNCZ02LM.endpoints[0]]),
'gledopto_group': new Group(21, [GLEDOPTO_2ID.endpoints[3]]),
'default_bind_group': new Group(901, []),
}
@@ -149,7 +155,7 @@ const devices = {
'unsupported2': new Device('EndDevice', '0x0017880104e45529', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "notSupportedModelID"),
'interviewing': new Device('EndDevice', '0x0017880104e45530', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", undefined, true),
'notInSettings': new Device('EndDevice', '0x0017880104e45519', 6537, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "lumi.sensor_switch.aq2"),
'WXKG11LM': new Device('EndDevice', '0x0017880104e45520', 6537,4151, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "lumi.sensor_switch.aq2"),
'WXKG11LM': new Device('EndDevice', '0x0017880104e45520', 6537,4151, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x0017880104e45520', [], {}, [{cluster: {name: 'genOnOff'}, attribute: {name: undefined, ID: 1337}, minimumReportInterval: 1, maximumReportInterval: 10, reportableChange: 20}])], true, "Battery", "lumi.sensor_switch.aq2"),
'WXKG02LM_rev1': new Device('EndDevice', '0x0017880104e45521', 6538,4151, [new Endpoint(1, [0], []), new Endpoint(2, [0], [])], true, "Battery", "lumi.sensor_86sw2.es1"),
'WSDCGQ11LM': new Device('EndDevice', '0x0017880104e45522', 6539,4151, [new Endpoint(1, [0], [])], true, "Battery", "lumi.weather"),
'RTCGQ11LM': new Device('EndDevice', '0x0017880104e45523', 6540,4151, [new Endpoint(1, [0], [])], true, "Battery", "lumi.sensor_motion.aq2"),
@@ -159,7 +165,8 @@ const devices = {
'QBKG03LM':new Device('Router', '0x0017880104e45542', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45542'), new Endpoint(2, [0, 6], [], '0x0017880104e45542'), new Endpoint(3, [0, 6], [], '0x0017880104e45542')], true, "Mains (single phase)", 'lumi.ctrl_neutral2'),
'GLEDOPTO1112': new Device('Router', '0x0017880104e45543', 6540, 4151, [new Endpoint(11, [0], [], '0x0017880104e45543'), new Endpoint(13, [0], [], '0x0017880104e45543')], true, "Mains (single phase)", 'GL-C-008'),
'GLEDOPTO111213': new Device('Router', '0x0017880104e45544', 6540,4151, [new Endpoint(11, [0], []), new Endpoint(13, [0], []), new Endpoint(12, [0], [])], true, "Mains (single phase)", 'GL-C-008'),
'HGZB04D': new Device('Router', '0x0017880104e45545', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45545')], true, "Mains (single phase)", 'FB56+ZSC05HG1.0'),
'GLEDOPTO_2ID': GLEDOPTO_2ID,
'HGZB04D': new Device('Router', '0x0017880104e45545', 6540,4151, [new Endpoint(1, [0], [25], '0x0017880104e45545')], true, "Mains (single phase)", 'FB56+ZSC05HG1.0'),
'ZNCLDJ11LM': new Device('Router', '0x0017880104e45547', 6540,4151, [new Endpoint(1, [0], []), new Endpoint(2, [0], [])], true, "Mains (single phase)", 'lumi.curtain'),
'HAMPTON99432': new Device('Router', '0x0017880104e45548', 6540,4151, [new Endpoint(1, [0], []), new Endpoint(2, [0], [])], true, "Mains (single phase)", 'HDC52EastwindFan'),
'HS2WD': new Device('Router', '0x0017880104e45549', 6540,4151, [new Endpoint(1, [0], [])], true, "Mains (single phase)", 'WarningDevice'),
@@ -182,8 +189,11 @@ const devices = {
'3157100': new Device('Router', '0x0017880104e44559', 6542,4151, [new Endpoint(1, [], [], '0x0017880104e44559')], true, "Mains (single phase)", '3157100'),
'J1': new Device('Router', '0x0017880104a44559', 6543,4151, [new Endpoint(1, [], [], '0x0017880104a44559')], true, "Mains (single phase)", 'J1 (5502)'),
'TS0601_thermostat': TS0601_thermostat,
'external_converter_device': new Device('EndDevice', '0x0017880104e45511', 1114, 'external', [], false, null, 'external_converter_device' ),
'external_converter_device': new Device('EndDevice', '0x0017880104e45511', 1114, 'external', [new Endpoint(1, [], [], '0x0017880104e45511')], false, null, 'external_converter_device' ),
'QS_Zigbee_D02_TRIAC_2C_LN':new Device('Router', '0x0017882194e45543', 6549,4151, [new Endpoint(1, [0], [], '0x0017882194e45543'), new Endpoint(2, [0, 6], [], '0x0017882194e45543')], true, "Mains (single phase)", 'TS110F', false, '_TYZB01_v8gtiaed'),
'unknown': new Device('Router', '0x0017980134e45545', 6540,4151, [], true, "Mains (single phase)"),
'temperature_sensor': new Device('EndDevice', '0x0017880104e45561', 6544,4151, [new Endpoint(1, [0,3,4,1026], [])], true, "Battery", "temperature.sensor"),
'heating_actuator': new Device('Router', '0x0017880104e45562', 6545,4151, [new Endpoint(1, [0,3,4,513], [1026])], true, "Mains (single phase)", "heating.actuator"),
}
const mock = {
@@ -193,6 +203,7 @@ const mock = {
touchlinkScan: jest.fn(),
touchlinkIdentify: jest.fn(),
start: jest.fn(),
isStopping: jest.fn(),
permitJoin: jest.fn(),
getCoordinatorVersion: jest.fn().mockReturnValue({type: 'z-Stack', meta: {version: 1, revision: 20190425}}),
getNetworkParameters: jest.fn().mockReturnValue({panID: 0x162a, extendedPanID: [0, 11, 22], channel: 15}),
@@ -220,6 +231,7 @@ const mock = {
return Object.values(groups).find((d) => d.groupID === groupID);
}),
getPermitJoin: jest.fn().mockReturnValue(false),
getPermitJoinTimeout: jest.fn().mockReturnValue(undefined),
reset: jest.fn(),
createGroup: jest.fn().mockImplementation((groupID) => {
const group = new Group(groupID, []);
+7 -1
View File
@@ -46,7 +46,7 @@ describe('Utils', () => {
var dependency = 'zigbee-herdsman';
expect(await utils.getDependencyVersion(dependency)).toStrictEqual({"version": versionHerdsman});
})
it('To local iso string', async () => {
var date = new Date('August 19, 1975 23:15:30 UTC+00:00');
var getTimezoneOffset = Date.prototype.getTimezoneOffset;
@@ -61,4 +61,10 @@ describe('Utils', () => {
var date = new Date('August 19, 1975 23:15:30 UTC+00:00');
expect(() => utils.formatDate(date, 'invalid', 1)).toThrowError("Unsupported type 'invalid'")
})
it('Get key', async () => {
expect(utils.getKey({'1': '1'}, '1', 2, null)).toBe('1');
expect(utils.getKey({'1': '1'}, '2', 2, null)).toBe(2);
expect(utils.getKey({'1': '1'}, '1', null, () => '3')).toBe('3');
})
});