Compare commits

...
516 Commits
Author SHA1 Message Date
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
Koen Kanters 99274c72c2 1.17.1 2021-02-01 18:13:58 +01:00
Koen Kanters 8c88f370e6 Undev 2021-02-01 18:13:54 +01:00
Koen Kanters 7cf746aa6a Merge branch 'dev' 2021-02-01 18:13:27 +01:00
github-actions[bot]andKoenkk 504e1141f1 Update zigbee-herdsman-converters to 14.0.42 (#6029)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-02-01 18:12:43 +01:00
github-actions[bot]andnurikk 0f1aad6b98 Update zigbee2mqtt-frontend to 0.3.23 (#6017)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-02-01 11:29:46 +08:00
John Doe f1c29c9bfc Flag "readOnly": true some fields (#6016) 2021-02-01 11:03:28 +08:00
github-actions[bot]andKoenkk 3918b33e2d Update zigbee-herdsman-converters to 14.0.41 (#6006)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-31 19:50:57 +01:00
Lubomir Kaplan 9f5c68e9ec Pass logger to zigbee-herdsman controller (#5988)
* passing logger to zigbee-herdsman controller

* updated controller test for logger passing
2021-01-31 16:14:06 +01:00
github-actions[bot]andnurikk 1ce11b2ad6 Update zigbee2mqtt-frontend to 0.3.22 (#5997)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-31 19:33:26 +08:00
John Doe 236ba3951f Publish wheither device supports OTA or not (#5995) 2021-01-31 19:31:14 +08:00
github-actions[bot]andKoenkk 26450ff930 Update dependencies (#5992)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-31 12:10:12 +01:00
github-actions[bot]andKoenkk 3cfad2ac16 Update zigbee-herdsman-converters to 14.0.40 (#5987)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-30 17:31:09 +01:00
John Doe 0903f4c777 Dummy options (#5981)
* Split groups and devices schema into definition

* Dummy bridge/request/options

* Linter
2021-01-30 15:44:36 +01:00
github-actions[bot]andKoenkk 61b9ddcd60 Update zigbee-herdsman-converters to 14.0.39 (#5972)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-29 20:20:22 +01:00
Koen Kanters e769d17ea5 Improve tests 2021-01-29 20:14:08 +01:00
Koen Kanters 435a64a407 Create codeql-analysis.yml 2021-01-28 20:39:14 +01:00
Koen Kanters 2f107b6b27 Add CONTRIBUTING.md 2021-01-28 20:37:35 +01:00
Koen Kanters 833ab1a1c0 Add CODE_OF_CONDUCT.md 2021-01-28 20:37:05 +01:00
github-actions[bot]andKoenkk 45be18027c Update zigbee-herdsman-converters to 14.0.37 (#5955)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-28 20:12:09 +01:00
Koen Kanters 403de64e5e Copy LICENSE to Docker image 2021-01-28 20:09:36 +01:00
Koen Kanters de51d4c561 Docker: use node 14 2021-01-28 20:07:19 +01:00
github-actions[bot]andKoenkk 5772eb4413 Update zigbee-herdsman-converters to 14.0.36 (#5950)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-28 18:19:37 +01:00
github-actions[bot]andKoenkk 54fc4fa82b Update zigbee-herdsman-converters to 14.0.35 (#5942)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-27 20:55:36 +01:00
Koen Kanters 5cdd8eb7be Preps for new zigbee-herdsman-converters (build will fail for this commit) 2021-01-27 20:51:30 +01:00
github-actions[bot]andKoenkk 435963a8a4 Update zigbee-herdsman-converters to 14.0.34 (#5920)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-27 11:35:59 +01:00
github-actions[bot]andnurikk a038f7e334 Update zigbee2mqtt-frontend to 0.3.19 (#5915)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-26 16:18:43 +08:00
github-actions[bot]andnurikk 665a6c9bf4 Update zigbee2mqtt-frontend to 0.3.17 (#5912)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-26 13:54:32 +08:00
github-actions[bot]andKoenkk 051df6fb14 Update zigbee-herdsman-converters to 14.0.33 (#5905)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-25 20:02:42 +01:00
github-actions[bot]andKoenkk d95bbd9820 Update zigbee-herdsman to 0.13.59 (#5904)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-25 19:48:57 +01:00
github-actions[bot]andKoenkk fa33ac21f5 Update zigbee-herdsman-converters to 14.0.32 (#5903)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-25 18:16:05 +01:00
github-actions[bot]andKoenkk ee18da225b Update zigbee-herdsman-converters to 14.0.31 (#5891)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-24 20:03:03 +01:00
github-actions[bot]andKoenkk 104520b8ce Update dependencies (#5880)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-24 13:28:49 +01:00
fenduru acb0945927 Add test for using device friendly names in group config. (#5865) 2021-01-23 16:40:11 +01:00
github-actions[bot]andKoenkk 9ca483d18d Update zigbee-herdsman-converters to 14.0.30 (#5873)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-23 16:08:08 +01:00
github-actions[bot]andKoenkk 548030aa33 Update zigbee-herdsman-converters to 14.0.29 (#5870)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-23 14:11:50 +01:00
github-actions[bot]andKoenkk 8c5595894f Update zigbee-herdsman-converters to 14.0.28 (#5862)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-22 20:15:11 +01:00
Koen Kanters 6a33670296 Network scan stability improvement 2021-01-22 19:16:35 +01:00
Koen Kanters 1b32a46649 Setup reporting when binding and make report feature deprecated (#5529)
* Initial

* Updates

* Some tests

* tests

* Updates

* Move poll to bind

* Move report to legacy

* Updates

* Remove toPoll key. https://github.com/Koenkk/zigbee2mqtt/pull/5529#pullrequestreview-570285310

* Updates

* Updates

* Update bridge.test.js

* Fix tests
2021-01-22 18:56:44 +01:00
d80a84aca1 Update zigbee-herdsman-converters to 14.0.27 (#5851)
* Update zigbee-herdsman-converters to 14.0.27

* Update bridge.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-01-21 19:52:02 +01:00
Koen Kanters e65d660236 Remove duplicate E11-N1EA. https://github.com/Koenkk/zigbee2mqtt/issues/775#issuecomment-763999942 2021-01-21 17:39:53 +01:00
github-actions[bot]andKoenkk 4e2e04e3e7 Update zigbee-herdsman to 0.13.58 (#5837)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-20 19:33:00 +01:00
Koen Kanters 51c2a8fdb8 Make E11-N1EA pingable. https://github.com/Koenkk/zigbee2mqtt/issues/775#issuecomment-763755661 2021-01-20 19:31:29 +01:00
8fcad24adb Update zigbee-herdsman-converters to 14.0.25 (#5824)
* Update zigbee-herdsman-converters to 14.0.25

* 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-01-19 19:27:12 +01:00
Koen Kanters c67de5eb28 Fix nothing logged in frontend when log_level warn is set. https://github.com/Koenkk/zigbee2mqtt/issues/5812 2021-01-19 18:14:40 +01:00
Jorge Schrauwen 0d59b52479 Add missing devices options to settings.schema.json (#5823)
Looks like a few more were missing that just debounce: https://github.com/Koenkk/zigbee2mqtt.io/blob/05fc32d8e09bf4cc1bccfc4acb8f618ce7ab3a22/docs/information/configuration.md#device-specific-configuration
2021-01-19 17:39:46 +01:00
Koen Kanters d1a1035e43 Fix nothing logged when log_level set to warn. https://github.com/Koenkk/zigbee2mqtt/issues/5812 2021-01-18 18:31:47 +01:00
Koen Kanters 48767a7bcf Allow to generate panID by setting "pan_id: GENERATE". https://github.com/Koenkk/zigbee2mqtt/issues/5745 2021-01-18 18:01:10 +01:00
github-actions[bot]andnurikk 2d870bd7ea Update zigbee2mqtt-frontend to 0.3.16 (#5799)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-18 08:44:28 +08:00
github-actions[bot]andnurikk 2ef2c54702 Update zigbee2mqtt-frontend to 0.3.15 (#5788)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-17 18:57:34 +08:00
github-actions[bot]andnurikk 9a8476dc15 Update zigbee2mqtt-frontend to 0.3.14 (#5787)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-17 17:25:42 +08:00
John Doe dbf84e775e Update schema (#5786)
* Split groups and devices schema into definition

* Update settings schema
2021-01-17 17:20:50 +08:00
John Doe 0e6779db3a Republish info (#5785)
* Split groups and devices schema into definition

* Republish bridge info after device/group props change
2021-01-17 09:51:32 +01:00
github-actions[bot]andKoenkk 4a9bdf16f5 Update dependencies (#5782)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-17 09:29:41 +01:00
github-actions[bot]andKoenkk 6a24c609b6 Update zigbee-herdsman-converters to 14.0.24 (#5781)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-16 21:34:16 +01:00
github-actions[bot]andKoenkk a48a36aaf2 Update zigbee-herdsman to 0.13.57 (#5780)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-16 21:18:51 +01:00
github-actions[bot]andKoenkk e5bf3cdb34 Update zigbee-herdsman-converters to 14.0.23 (#5775)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-16 19:20:43 +01:00
xyzroe bbe4f9cd5f endpoint support for binary and numeric discovery entries (#5771)
* endpoint support for binary and numeric exposes

* endpoint support for binary and numeric exposes
2021-01-16 18:26:55 +01:00
github-actions[bot]andKoenkk 5050ca0dc0 Update zigbee-herdsman-converters to 14.0.22 (#5770)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-16 13:12:19 +01:00
John DoeandKoen Kanters d50d010b82 Publish settings json schema to bridge/info (#5754)
* Publish settings json schema to bridge/info

* Update frontend

* Please linter

* Rename configSchema -> config_schema

* Fix settings schema

* Update titles

* Updated descriptions

* Update frontend

* Update json schema titles and descriptions

* Update schema

* Update frontend

* Update npm-shrinkwrap.json

* Update package.json

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-01-16 12:32:15 +01:00
github-actions[bot]andKoenkk 4f777cae87 Update zigbee-herdsman to 0.13.55 (#5768)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-16 12:23:12 +01:00
github-actions[bot]andKoenkk 97827d4309 Update zigbee-herdsman-converters to 14.0.21 (#5764)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-15 20:19:46 +01:00
Koen Kanters 16cd176822 Fix group state not changing when members state changes under certain circumstances. https://github.com/Koenkk/zigbee2mqtt/issues/5725 2021-01-15 18:05:51 +01:00
github-actions[bot]andKoenkk a822165c26 Update zigbee-herdsman-converters to 14.0.20 (#5733)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-13 21:55:23 +01:00
github-actions[bot]andKoenkk bdc261ff9e Update zigbee-herdsman-converters to 14.0.19 (#5732)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-13 20:42:14 +01:00
github-actions[bot]andnurikk 9604c836fd Update zigbee2mqtt-frontend to 0.3.4 (#5731)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-14 01:05:23 +08:00
d752f0593d Update zigbee-herdsman-converters to 14.0.18 (#5723)
* Update zigbee-herdsman-converters to 14.0.18

* Update receive.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-01-12 21:57:05 +01:00
github-actions[bot]andKoenkk 197c80165b Update zigbee-herdsman to 0.13.54 (#5722)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-12 21:53:10 +01:00
github-actions[bot]andKoenkk 34c69c4309 Update zigbee-herdsman to 0.13.53 (#5719)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-12 21:25:48 +01:00
github-actions[bot]andKoenkk e6ec3c6c9b Update zigbee-herdsman to 0.13.52 (#5713)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-12 20:40:06 +01:00
github-actions[bot]andKoenkk f512e36b9e Update zigbee-herdsman-converters to 14.0.17 (#5702)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-11 20:10:16 +01:00
github-actions[bot]andKoenkk ca5cdd4f8b Update zigbee-herdsman to 0.13.51 (#5698)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-11 18:10:28 +01:00
github-actions[bot]andKoenkk 5943863c78 Update zigbee-herdsman-converters to 14.0.16 (#5697)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-11 18:01:58 +01:00
Koen Kanters ed76bbbdc5 Add ExtensionFrontend. https://github.com/Koenkk/zigbee2mqtt/issues/5688 2021-01-11 17:01:20 +01:00
github-actions[bot]andKoenkk 09d436fb9c Update zigbee-herdsman-converters to 14.0.15 (#5677)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-10 17:56:17 +01:00
github-actions[bot]andKoenkk 351cf41884 Update zigbee-herdsman to 0.13.50 (#5673)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-10 16:55:09 +01:00
Koen Kanters 19c54040bc Allow string values for permit_join call. https://github.com/Koenkk/zigbee2mqtt/issues/5644 2021-01-10 16:54:01 +01:00
github-actions[bot]andKoenkk 8026dde26a Update dependencies (#5663)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-10 15:14:59 +01:00
Koen Kanters 7014bb05f0 Fix tests 2021-01-09 15:17:09 +01:00
Koen Kanters 7b0eb87685 Don't allow friendly_name to start with. https://github.com/Koenkk/zigbee2mqtt/commit/d7c74ca3ad65dc8cc0366d321c5494c4f3b7a51b 2021-01-09 15:13:29 +01:00
github-actions[bot]andKoenkk 9189dd1d3d Update zigbee-herdsman-converters to 14.0.14 (#5650)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-09 15:07:23 +01:00
Koen Kanters 77a6ec09fb More friendly name checks. https://github.com/Koenkk/zigbee2mqtt/commit/d7c74ca3ad65dc8cc0366d321c5494c4f3b7a51b 2021-01-09 14:21:01 +01:00
f356be8253 Update zigbee-herdsman-converters to 14.0.13 (#5638)
* Update zigbee-herdsman-converters to 14.0.13

* 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-01-08 22:30:36 +01:00
Koen Kanters 4626d557d3 Fix log level in frontend not updating after setting log level. https://github.com/Koenkk/zigbee2mqtt/issues/5635 2021-01-08 21:47:05 +01:00
github-actions[bot]andKoenkk cab93d0c72 Update zigbee-herdsman to 0.13.49 (#5637)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-08 21:01:09 +01:00
Koen Kanters d7c74ca3ad Fix not allowed friendly_names being allowed. #5618 2021-01-08 20:46:04 +01:00
Koen Kanters b2cd519e0b Allow disabling discovery of device_automation. https://github.com/Koenkk/zigbee2mqtt/issues/5597 2021-01-08 20:23:25 +01:00
github-actions[bot]andnurikk 0a56e5c3d5 Update zigbee2mqtt-frontend to 0.3.2 (#5616)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-08 07:34:49 +08:00
Koen Kanters 4fc10f0cfb Attempt to fix external converters exposes not loaded into HA discovery. https://github.com/Koenkk/zigbee2mqtt/discussions/5605 2021-01-07 19:24:48 +01:00
Koen Kanters 2942ae9f4a Fix some properties not being published when debounce is used (#5509)
* initial

* More logging. https://github.com/Koenkk/zigbee-herdsman-converters/issues/1988

* More logging

* Ignore group_optimistic

* Cleanup

* Add logging again

* We got him?

* go

* linto
2021-01-07 19:17:13 +01:00
David 2524020963 Fix availability not working for Sengled E11-N1EA bulb (#5603)
Added availability support for Sengled E11-N1EA (RGB) bulb.  

https://www.zigbee2mqtt.io/devices/E11-N1EA.html#sengled-e11-n1ea
2021-01-07 19:07:36 +01:00
Koen Kanters 2cf9657bd4 Fix disabling discovery of specific Home Assistant sensor not working. #5597 2021-01-07 18:56:39 +01:00
github-actions[bot]andKoenkk 4c95265d5e Update zigbee-herdsman-converters to 14.0.12 (#5600)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-06 19:34:00 +01:00
github-actions[bot]andKoenkk cd5246865b Update zigbee-herdsman-converters to 14.0.11 (#5583)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-05 20:56:07 +01:00
Koen Kanters 4b8500603d Prepare for next zhc release 2021-01-05 20:36:04 +01:00
Koen Kanters adfb9f0c42 Fix setting last_seen from frontend not working. #5545 2021-01-05 19:15:08 +01:00
Koen Kanters acfd938e48 HA discovery: prefer XY over HS. 2021-01-05 18:40:26 +01:00
90abc2e846 Update zigbee-herdsman-converters to 14.0.8 (#5558)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-01-04 21:18:37 +01:00
Koen Kanters 1be15c67cf Prepare test for next zigbee-herdsman-converters version. 2021-01-04 21:16:49 +01:00
Igor ListopadandKoen Kanters 660796a1e4 Add specifying auth_token in a different file (#5537)
* Add specifying auth_token in a different file

* Update settings.js

* Update settings.js

* Update settings.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-01-04 19:32:03 +01:00
github-actions[bot]andKoenkk 5336ed9d9d Update zigbee-herdsman to 0.13.48 (#5550)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-04 18:03:31 +01:00
github-actions[bot]andKoenkk 7b3beafddc Update zigbee-herdsman-converters to 14.0.6 (#5527)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-03 15:25:59 +01:00
github-actions[bot]andKoenkk 1ffbf2ca71 Update zigbee-herdsman to 0.13.47 (#5523)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-03 11:14:59 +01:00
Koen Kanters d53b63d67e Allow joining for a specific amount of time. https://github.com/nurikk/z2m-frontend/issues/202 2021-01-03 11:08:33 +01:00
github-actions[bot]andKoenkk 1298769d4d Update dependencies (#5519)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-03 10:12:27 +01:00
914819799a Update zigbee-herdsman-converters to 14.0.5 (#5505)
* Update zigbee-herdsman-converters to 14.0.5

* Update bridge.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-01-02 11:05:43 +01:00
github-actions[bot]andnurikk cf7156707d Update zigbee2mqtt-frontend to 0.3.1 (#5490)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-01-02 16:42:42 +08:00
Pedro Lamas 2dc9016f9f More HA device classes in use (#5495) 2021-01-01 22:48:07 +01:00
Koen Kanters 3a9e6ad2c3 Promote to dev 2021-01-01 12:37:57 +01:00
Koen Kanters 07cdc9d549 1.17.0 2021-01-01 12:35:53 +01:00
Koen Kanters 03eaa17800 Undev 2021-01-01 12:30:22 +01:00
github-actions[bot]andKoenkk 3d3f754433 Update zigbee-herdsman-converters to 14.0.3 (#5488)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-01-01 12:14:35 +01:00
f999f58fa8 Update zigbee-herdsman-converters to 14.0.2 (#5474)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-31 10:37:00 +01:00
github-actions[bot]andnurikk 89272ce300 Update zigbee2mqtt-frontend to 0.2.129 (#5476)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-31 11:56:16 +08:00
github-actions[bot]andnurikk a203b1a0c0 Update zigbee2mqtt-frontend to 0.2.127 (#5475)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-31 09:51:44 +08:00
github-actions[bot]andnurikk 92f3caa1c9 Update zigbee2mqtt-frontend to 0.2.126 (#5469)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-30 22:55:07 +08:00
Koen Kanters 016137b82b Republish devices after configuring reporting. https://github.com/Koenkk/zigbee-herdsman-converters/commit/5f99e26eda41baad0d692eec397816168808ad5d 2020-12-30 12:20:38 +01:00
github-actions[bot]andnurikk 897f5a9324 Update zigbee2mqtt-frontend to 0.2.125 (#5467)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-30 09:32:15 +08:00
github-actions[bot]andnurikk 43d963ff5d Update zigbee2mqtt-frontend to 0.2.124 (#5466)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-30 07:47:29 +08:00
github-actions[bot]andKoenkk 2d1e99f89b Update zigbee-herdsman to 0.13.46 (#5461)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-29 20:45:47 +01:00
357b9c1e72 Update zigbee-herdsman-converters to 14.0.1 (#5460)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-29 20:14:32 +01:00
Koen Kanters 3558635797 Only reconfigure reporting for Ikea bulbs on device annouce when configuredReportings is empty, this is now done in https://github.com/Koenkk/zigbee-herdsman-converters/commit/5f99e26eda41baad0d692eec397816168808ad5d 2020-12-29 20:06:10 +01:00
Koen Kanters ff59480bce Expose model_id. 2020-12-29 18:50:57 +01:00
github-actions[bot]andnurikk 0c6406e7ff Update zigbee2mqtt-frontend to 0.2.123 (#5456)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-29 22:40:45 +08:00
github-actions[bot]andnurikk 451d612f70 Update zigbee2mqtt-frontend to 0.2.122 (#5453)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-29 22:10:23 +08:00
github-actions[bot]andKoenkk 41a0cc455b Update zigbee-herdsman to 0.13.45 (#5452)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-29 21:51:55 +08:00
John Doe db8a3d85a9 Revert "Update zigbee2mqtt-frontend to 0.2.118 (#5446)" (#5447)
This reverts commit 8575470ff2.
2020-12-29 19:19:31 +08:00
github-actions[bot]andnurikk 8575470ff2 Update zigbee2mqtt-frontend to 0.2.118 (#5446)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-29 19:03:51 +08:00
Koen Kanters 332ccee44a Log succesful configured reporting. 2020-12-29 10:56:46 +01:00
Koen Kanters 0e1d683c77 Republish devices after configure 2020-12-29 10:43:06 +01:00
Koen Kanters 09c5631b4d Expose configured reportings. 2020-12-28 23:12:40 +01:00
Koen Kanters c0fa732833 Add zigbee2mqtt/bridge/request/device/configure_reporting 2020-12-28 22:57:35 +01:00
github-actions[bot]andKoenkk 155e9119c0 Update zigbee-herdsman to 0.13.44 (#5440)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-28 22:39:14 +01:00
github-actions[bot]andKoenkk 03ef80c463 Update zigbee-herdsman-converters to 14.0.0 (#5438)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-28 21:54:40 +01:00
Koen Kanters 9345fe8c99 Pass logger to configure(). https://github.com/Koenkk/zigbee2mqtt/issues/5212 2020-12-28 21:48:07 +01:00
github-actions[bot]andKoenkk 59ab1e6c22 Update zigbee-herdsman to 0.13.43 (#5435)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-28 21:43:01 +01:00
d4a2f777fa Update zigbee-herdsman-converters to 13.0.38 (#5434)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-28 21:17:35 +01:00
Koen Kanters 6c4b345a58 Fix tests for zigbee-herdsman-converters 13.0.38 2020-12-28 21:15:45 +01:00
Koen Kanters 9b7ecff5d0 Clear outdated availability topics. #5414 2020-12-28 20:19:00 +01:00
Koen Kanters 2385cf62e5 Fix debug logging not logged on frontend. #5425 2020-12-28 18:53:22 +01:00
Koen Kanters 0bc86a97e7 Refactor homeassistant.js 2020-12-28 18:31:34 +01:00
github-actions[bot]andKoenkk 029afbe13c Update zigbee-herdsman-converters to 13.0.37 (#5433)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-28 17:25:28 +01:00
7c7df1426b Update zigbee-herdsman-converters to 13.0.36 (#5429)
* Update zigbee-herdsman-converters to 13.0.36

* Update homeassistant.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-28 14:05:58 +01:00
github-actions[bot]andKoenkk b53d9d18bc Update zigbee-herdsman to 0.13.42 (#5427)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-28 13:47:56 +01:00
Jakob NoheandKoen Kanters 0b8cd232af Do not fallback to string if array can be parsed (#5420)
* Do not fallback to string if array can be parsed

This in needed, otherwise ZIGBEE2MQTT_CONFIG_ADVANCED_NETWORK_KEY can never be set to a JSON object (array).

* Update settings.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-27 19:47:00 +01:00
Koen Kanters 20e40a278e Merge branch 'master' into dev 2020-12-27 19:29:27 +01:00
Koen Kanters 50053e34f8 Test case for https://github.com/Koenkk/zigbee2mqtt/issues/5413 2020-12-27 16:34:11 +01:00
github-actions[bot]andKoenkk 45e2cf75c6 Update zigbee-herdsman-converters to 13.0.35 (#5419)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-27 16:33:41 +01:00
93741bf731 Update zigbee-herdsman-converters to 13.0.34 (#5418)
* Update zigbee-herdsman-converters to 13.0.34

* Update bridge.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-27 14:44:36 +01:00
github-actions[bot]andKoenkk f33ce378de Update dependencies (#5417)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-27 13:45:51 +01:00
99c77d2e98 Update zigbee-herdsman-converters to 13.0.33 (#5403)
* Update zigbee-herdsman-converters to 13.0.33

* Update homeassistant.test.js

* Update bridge.test.js

* Update bridge.test.js

* Update homeassistant.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-25 13:13:09 +01:00
github-actions[bot]andKoenkk a1ae5ec9e0 Update zigbee-herdsman-converters to 13.0.32 (#5400)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-24 19:57:06 +01:00
Koen Kanters d1842ab69a Update new_device_support.md 2020-12-24 18:00:59 +01:00
github-actions[bot]andKoenkk 7045c1c669 Update zigbee-herdsman-converters to 13.0.31 (#5383)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-23 17:56:22 +01:00
github-actions[bot]andKoenkk fe2034f4d2 Update zigbee-herdsman-converters to 13.0.30 (#5375)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-22 19:11:20 +01:00
Koen Kanters 83221fd7dd Lint 2020-12-22 18:40:46 +01:00
github-actions[bot]andKoenkk 8e87cbd479 Update zigbee-herdsman-converters to 13.0.29 (#5364)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-21 22:34:28 +01:00
github-actions[bot]andnurikk be6b693198 Update zigbee2mqtt-frontend to 0.2.110 (#5361)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-21 18:54:42 +08:00
fb4b014940 Update zigbee-herdsman-converters to 13.0.28 (#5360)
* Update zigbee-herdsman-converters to 13.0.28

* Update otaUpdate.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-20 21:55:39 +01:00
Koen Kanters 5e226efe9d Add water to endpoint names. https://github.com/Koenkk/zigbee2mqtt/issues/5357 2020-12-20 21:40:23 +01:00
github-actions[bot]andKoenkk c174163e8e Update zigbee-herdsman-converters to 13.0.27 (#5354)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-20 10:36:29 +01:00
github-actions[bot]andKoenkk 8cc8e84afe Update dependencies (#5353)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-20 09:30:08 +01:00
github-actions[bot]andKoenkk 8e3dd2e666 Update zigbee-herdsman-converters to 13.0.25 (#5342)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-18 20:35:28 +01:00
github-actions[bot]andnurikk fdbd947451 Update zigbee2mqtt-frontend to 0.2.108 (#5337)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-18 11:56:01 +08:00
Rafael Xavier de Souza 90ee76c061 README: Update Zigbee clusters link (#5315) 2020-12-17 13:43:38 +01:00
github-actions[bot]andKoenkk eba8849d49 Update zigbee-herdsman-converters to 13.0.24 (#5317)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-16 08:43:35 +01:00
github-actions[bot]andKoenkk 38aaa9a5ab Update zigbee-herdsman-converters to 13.0.23 (#5310)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-15 19:15:25 +01:00
github-actions[bot]andKoenkk 619846002e Update zigbee-herdsman-converters to 13.0.22 (#5304)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-14 20:34:42 +01:00
github-actions[bot]andKoenkk 2468d61414 Update zigbee-herdsman-converters to 13.0.21 (#5294)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-13 21:14:23 +01:00
6b50f4074d Update zigbee-herdsman-converters to 13.0.20 (#5290)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-13 18:35:00 +01:00
Koen Kanters 9abcaddaba Fix tests 2020-12-13 18:29:08 +01:00
Koen Kanters 026f1bfd1c Fix no devices logged when publishing empty message to zigbee2mqtt/bridge/config/devices. #5289 2020-12-13 18:12:11 +01:00
github-actions[bot]andKoenkk bc4c74d5b6 Update dependencies (#5282)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-13 17:44:33 +01:00
dd68d1d2be Update zigbee-herdsman to 0.13.41 (#5270)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-11 20:20:16 +01:00
github-actions[bot]andKoenkk 2b64a476c9 Update zigbee-herdsman-converters to 13.0.19 (#5269)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-11 20:19:37 +01:00
e0fa6814a5 Update zigbee-herdsman-converters to 13.0.18 (#5253)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-11 08:31:50 +01:00
github-actions[bot]andnurikk 1f679213b5 Update zigbee2mqtt-frontend to 0.2.107 (#5260)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-11 09:10:14 +08:00
Hacker-CB d0132a4494 zStackEraseAllNvMem.js: fix osal/not osal items for ZStack 3.x (#5246) 2020-12-10 15:43:44 +01:00
github-actions[bot]andnurikk 4078f2da02 Update zigbee2mqtt-frontend to 0.2.106 (#5245)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-10 15:26:17 +08:00
github-actions[bot]andKoenkk b6c4c2d73c Update zigbee-herdsman-converters to 13.0.17 (#5242)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-09 19:51:41 +01:00
Koen Kanters ab3fd769d5 Allow to use IKEA TRADFRI OTA test server. https://github.com/Koenkk/zigbee2mqtt/issues/2921 2020-12-09 19:51:16 +01:00
github-actions[bot]andKoenkk c29ffcf39e Update zigbee-herdsman-converters to 13.0.16 (#5240)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-09 19:08:22 +01:00
github-actions[bot]andKoenkk ae4ea1120e Update zigbee-herdsman to 0.13.40 (#5239)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-09 18:35:30 +01:00
github-actions[bot]andnurikk 7ca8535714 Update zigbee2mqtt-frontend to 0.2.105 (#5235)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-09 20:27:54 +08:00
github-actions[bot]andnurikk 293c32d465 Update zigbee2mqtt-frontend to 0.2.104 (#5232)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-09 16:47:47 +08:00
github-actions[bot]andKoenkk 9b1e20f100 Update zigbee-herdsman-converters to 13.0.15 (#5228)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-08 20:24:26 +01:00
Koen Kanters 05142044cb Update Github discussions link 2020-12-08 20:13:03 +01:00
Koen Kanters fd7d39ca77 Add Github discussions 2020-12-08 20:12:14 +01:00
Koen Kanters 2134966f6e Home Assistant: separate discovered name by space instead of _ when friendly_name has no _. #5226 2020-12-08 19:41:34 +01:00
Koen Kanters c78206b9ef Support Home Assistant discovery for composite. https://github.com/Koenkk/zigbee-herdsman-converters/pull/1873 2020-12-08 19:01:59 +01:00
John Doe 743c701ac5 Fix disabling HA discovery after device was discovered (#5200) 2020-12-08 16:45:31 +01:00
github-actions[bot]andKoenkk 3f2eec85b2 Update zigbee-herdsman-converters to 13.0.14 (#5219)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-07 19:34:06 +01:00
github-actions[bot]andKoenkk 92b942e4f2 Update zigbee-herdsman-converters to 13.0.13 (#5209)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-06 20:49:03 +01:00
github-actions[bot]andKoenkk 94c76b591d Update dependencies (#5205)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-06 19:43:45 +01:00
47d26c8887 Update zigbee-herdsman-converters to 13.0.12 (#5197)
* Update zigbee-herdsman-converters to 13.0.12

* Update homeassistant.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-12-04 16:58:33 +01:00
Koen Kanters 81704f1807 Fix setting brightness via "brightness_[ENDPOINT]" not working. #5186 2020-12-04 16:41:06 +01:00
github-actions[bot]andKoenkk 4bcdd22e28 Update zigbee-herdsman-converters to 13.0.11 (#5187)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-03 21:56:40 +01:00
github-actions[bot]andKoenkk be61249d04 Update zigbee-herdsman to 0.13.39 (#5184)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-03 20:51:17 +01:00
github-actions[bot]andKoenkk a5505f7fe2 Update zigbee-herdsman to 0.13.38 (#5174)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-02 22:23:34 +01:00
github-actions[bot]andKoenkk ee2032f3aa Update zigbee-herdsman-converters to 13.0.10 (#5173)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-02 22:18:34 +01:00
Koen Kanters 9ff232fbb2 Enable new api by default. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-12-02 20:38:48 +01:00
github-actions[bot]andKoenkk 1c634dde60 Update zigbee-herdsman-converters to 13.0.9 (#5171)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-02 19:50:46 +01:00
John Doe dba9b4d931 Fix network map request failing when device has no exposes. (#5161)
* Fix map request if external converter doesn't have exposes attribute

* Remove dead code
2020-12-02 19:25:56 +01:00
github-actions[bot]andKoenkk 0e648dcebd Update zigbee-herdsman-converters to 13.0.8 (#5157)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-12-01 22:57:02 +01:00
Koen Kanters 9da405a6e5 Promote to dev 2020-12-01 19:00:16 +01:00
Koen Kanters 04c15f7fe8 1.16.2 2020-12-01 18:57:53 +01:00
Koen Kanters 4ac8d0df95 Undev 2020-12-01 18:57:33 +01:00
github-actions[bot]andnurikk e7bb0c3def Update zigbee2mqtt-frontend to 0.2.100 (#5145)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-12-01 06:47:48 +08:00
github-actions[bot]andKoenkk 15d04eda7c Update zigbee-herdsman-converters to 13.0.7 (#5143)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-30 19:46:01 +01:00
Pedro Lamas 8b508971db Poll lamps with Philips, Atmel, Gledopto, Mueller-Licht manufacturer codes (#5139)
* Poll lamps with Philips or Atmel manufacturer code

* Fixes incorrect test

Following up on https://github.com/Koenkk/zigbee2mqtt/pull/2122#discussion_r532618336

* Adds GLEDOPTO_CO_LTD and MUELLER_LICHT_INT to lamp polling
2020-11-30 19:21:17 +01:00
github-actions[bot]andKoenkk b1b134fbf0 Update zigbee-herdsman to 0.13.37 (#5134)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-30 12:21:30 +01:00
github-actions[bot]andnurikk 3cbed1dcd2 Update zigbee2mqtt-frontend to 0.2.98 (#5129)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-30 13:38:39 +08:00
github-actions[bot]andKoenkk bda3548cd2 Update zigbee-herdsman-converters to 13.0.6 (#5126)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-29 17:38:13 +01:00
github-actions[bot]andKoenkk 200b099de1 Update dependencies (#5118)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-29 17:10:49 +01:00
github-actions[bot]andnurikk b8f5e67bee Update zigbee2mqtt-frontend to 0.2.97 (#5124)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-29 21:57:25 +08:00
github-actions[bot]andnurikk 9238748978 Update zigbee2mqtt-frontend to 0.2.96 (#5122)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-29 19:29:38 +08:00
github-actions[bot]andnurikk 9e0ea50da2 Update zigbee2mqtt-frontend to 0.2.95 (#5112)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-28 22:04:46 +08:00
github-actions[bot]andKoenkk e0c2d69244 Update zigbee-herdsman to 0.13.36 (#5111)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-28 14:53:01 +01:00
github-actions[bot]andKoenkk 26841f53aa Update zigbee-herdsman-converters to 13.0.5 (#5099)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-27 17:51:23 +01:00
Koen Kanters 6548250eb4 Allow read-only devices/groups files by allowing to specify devices/groups in multiple files. #2970 2020-11-27 17:32:32 +01:00
ab8035d6bb Update zigbee-herdsman-converters to 13.0.4 (#5095)
* Update zigbee-herdsman-converters to 13.0.4

* Update homeassistant.js

* Update homeassistant.js

* Update homeassistant.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-26 20:12:09 +01:00
github-actions[bot]andKoenkk 99207358d5 Update zigbee-herdsman to 0.13.35 (#5094)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-26 19:55:34 +01:00
github-actions[bot]andnurikk 7b98a00a24 Update zigbee2mqtt-frontend to 0.2.91 (#5088)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-26 19:54:56 +01:00
Koen Kanters 749e6789b8 Merge remote-tracking branch 'origin/master' into dev 2020-11-26 16:35:57 +01:00
John Doe 68f243221f Add auth to frontend and remove development server (#5079)
* Add frontend authentification

* Delete auth_token from bridge/info
2020-11-25 16:28:02 +01:00
github-actions[bot]andnurikk 4b1b1a4c3b Update zigbee2mqtt-frontend to 0.2.94 (#5080)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-25 13:36:36 +08:00
github-actions[bot]andKoenkk 9ead1a248e Update zigbee-herdsman-converters to 13.0.3 (#5078)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-24 19:34:44 +01:00
github-actions[bot]andKoenkk b5f62f15c7 Update zigbee-herdsman to 0.13.34 (#5075)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-24 16:34:47 +01:00
Marius cb9699e12a Automatic edge build trigger for zigbee2mqtt org (#5068)
Automatic edge build trigger for zigbee2mqtt/hassio-zigbee2mqtt
2020-11-24 09:59:36 +01:00
github-actions[bot]andKoenkk 9b79620bde Update zigbee-herdsman-converters to 13.0.2 (#5063)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-23 19:17:39 +01:00
481cdefafc Add host bind option for frontend (#5014)
* Added IP address bind to HTTP frontend listener

* Changed ip to host in configuration and code

Co-authored-by: romanius <me@romanius.me>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-23 19:09:47 +01:00
Koen Kanters 9ee35f6933 Home Assistant: don't include object_id in name if same as type (e.g. light.bulb_light -> light.bulb). #5054 2020-11-23 19:01:16 +01:00
a5c2c483ad Update zigbee-herdsman-converters to 13.0.1 (#5058)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-22 21:12:09 +01:00
github-actions[bot]andKoenkk 2fa70fea4d Update zigbee-herdsman to 0.13.31 (#5057)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-22 21:08:53 +01:00
github-actions[bot]andKoenkk 554f44e361 Update zigbee-herdsman-converters to 13.0.0 (#5052)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-22 13:00:31 +01:00
github-actions[bot]andnurikk 33a0a6a651 Update zigbee2mqtt-frontend to 0.2.93 (#5049)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-22 12:55:18 +01:00
Koen Kanters 982e918a50 Don't use supports anymore and prepare for next zigbee-herdsman-converters. 2020-11-22 12:54:19 +01:00
github-actions[bot]andKoenkk ebad3d673c Update dependencies (#5047)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-22 12:38:49 +01:00
github-actions[bot]andnurikk aa6554c1b7 Update zigbee2mqtt-frontend to 0.2.92 (#5046)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2020-11-22 07:16:26 +08:00
Koen Kanters fc19f2e903 Don't pass messages from coordinator to extensions. https://github.com/Koenkk/zigbee2mqtt/issues/5028 2020-11-21 16:20:48 +01:00
John Doe c1f7ce7389 Implement cli version of z2m (#5034) 2020-11-21 16:02:49 +01:00
John Doe 5f7553f624 Delete settings.json (#5033) 2020-11-21 11:42:38 +08:00
github-actions[bot]andKoenkk 7a774ef3ed Update zigbee-herdsman to 0.13.30 (#5030)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2020-11-20 17:57:56 +01:00
John Doe 153b82ca77 Fix loading external convertersThis fixes Error: Cannot find module 'zigbee-herdsman-converters'Caused by requiring converter from folder which is located outside z2m sources folder (ZIGBEE2MQTT_DATA) (#5027) 2020-11-20 22:22:08 +08:00
John Doe 4978d09eb1 Update peter-evans/create-pull-request to v3 (#5025) 2020-11-20 06:15:26 +08:00
John Doe 350ec1776e Update update_frontend.yml (#5021) 2020-11-20 06:01:19 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 8d01a20d6f Update zigbee2mqtt-frontend to 0.2.88 (#5020)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-20 05:44:43 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 1e9553fadc Update zigbee-herdsman-converters to 12.0.235 (#5008)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-18 19:09:00 +01:00
Koen Kanters 49f37acd18 Add adapter_delay option. https://github.com/Koenkk/zigbee2mqtt/issues/4884 2020-11-18 18:46:13 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> bce2877641 Update zigbee2mqtt-frontend to 0.2.85 (#5001)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-17 09:09:17 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
02a945d84b Update zigbee-herdsman-converters to 12.0.234 (#4998)
* Update zigbee-herdsman-converters to 12.0.234

* Update bridge.test.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-16 19:00:55 +01:00
Hacker-CB 1eaf14a861 Added ZStack NVMEM eraser (Erase all NVMEM items) (#4974)
* Added ZStack NVMMEM eraser script

* typo fix

* Compact switch/case

* Added process.exit(0)
2020-11-16 18:56:34 +01:00
Chris Nesbitt-SmithandKoen Kanters aabb88fbf4 Add force_disable_retain option (#4948)
* support configurable retain on mqtt settings since some servers throw errors and drop the connections if you try (e.g. aws iot)

* Revert "support configurable retain on mqtt settings since some servers throw errors and drop the connections if you try (e.g. aws iot)"

This reverts commit 40d3a9c0bc84c83d2b167e2e3c3a06ca6df80f47.

* support configurable retain on mqtt settings

* support subscribing to the whole base prefix

* Update mqtt.js

* Fixes

* Fixes

* Fix tests

* Updates

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-16 17:27:49 +01:00
Koen Kanters 9c8323326d Greatly reduce amount of subscribed topics. #4948 2020-11-16 17:03:22 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> da3b3b565f Update zigbee2mqtt-frontend to 0.2.84 (#4995)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-16 13:36:03 +08:00
Koen Kanters c1e5c75473 Fix bridge/state online not send after reconnect. https://github.com/Koenkk/zigbee2mqtt/issues/4854 2020-11-15 13:22:29 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6c22f285b4 Update dependencies (#4986)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-15 13:08:53 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 4c9dd749b9 Update zigbee2mqtt-frontend to 0.2.83 (#4988)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-15 11:59:09 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 918064feb3 Update zigbee2mqtt-frontend to 0.2.81 (#4987)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-15 10:01:03 +08:00
William SuttonandKoen Kanters 1c3ad9da72 Log zigbee-herdsman version on startup (#4930)
* add getZigbeeHerdsmanVersion Function

Allow current zigbee-herdsman version to be exposed to controller for logging and to startup output.

* Update utils.test.js

Add tests for getZigbeeHerdsmanVersion() function

* Update utils.js

Added getZigbeeHerdsman to module exports

* Update utils.js

Fixed path because I'm dumb.

* Update utils.test.js

Apparently version numbers are important when they're different.

* Update zigbee.js

Added actual display of herdsman version output.

* Update utils.js

Remove git checking in getZigbeeHerdsmanVersion()

* Update utils.test.js

Remove commitHash from getZigbeeHerdsmanVersion test

* Update utils.js

Remove commitHash from getZigbeeHerdsmanVersion

* Update utils.js

* Update utils.test.js

* Update utils.js

Try removing less code this time.

* Update utils.test.js

* Update zigbee.js

* Update utils.js

* Update utils.js

* Update utils.test.js

* Update utils.js

* Update utils.test.js

* Update utils.js

* Update utils.js

* Update zigbee.js

* Update utils.js

Generalize getZigbeeHerdsmanVersion() to getDependVersion(), so any node_module dependency can be checked

* Update zigbee.js

* Update utils.test.js

* Update utils.js

* Update zigbee.js

* Update utils.test.js

* Update zigbee.js

* Update zigbee.js

* Update zigbee.js

* Update zigbee.js

* Update zigbee.js

* Update utils.js

* Update zigbee.js

* Update utils.test.js

* Update zigbee.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-14 17:25:22 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 033863925a Update zigbee2mqtt-frontend to 0.2.80 (#4981)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-14 22:40:16 +08:00
Koen Kanters b5436d4b93 Fix devices marked unavailable in Home Assistant after restart. https://github.com/Koenkk/zigbee2mqtt/issues/4912 2020-11-13 19:45:27 +01:00
Koen Kanters cbf353e387 Fix linkquality N/A when refreshing frontend. https://github.com/Koenkk/zigbee2mqtt/issues/4942 2020-11-13 17:29:23 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> ca33e228e6 Update zigbee2mqtt-frontend to 0.2.79 (#4966)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-13 05:08:22 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 97215c9072 Update zigbee2mqtt-frontend to 0.2.78 (#4962)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-12 21:24:56 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> dd582a3f7e Update zigbee2mqtt-frontend to 0.2.77 (#4961)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-12 20:47:32 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 8034ea272c Update zigbee-herdsman to 0.13.29 (#4955)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-12 11:11:49 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> e6124c2631 Update zigbee2mqtt-frontend to 0.2.76 (#4957)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-12 16:38:40 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 35813ff951 Update zigbee2mqtt-frontend to 0.2.75 (#4953)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-12 12:20:47 +08:00
Nonsense112andKoen Kanters 1ff4be4600 Fix state.json not pretty printed (#4943)
* state.json format

Since 9774031 the state.json file is not formatted anymore, it's just 1 line.

* Update state.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-11 18:36:15 +01:00
Drzony 7305960d53 Display manufacturer name for unsupported devices (#4938)
* Display manufacturer name for unsupported devices

* fixup! Display manufacturer name for unsupported devices
2020-11-11 18:27:20 +01:00
Kamil Trzciński d5d2b68e67 Expose position for compatible thermostats (#4936)
This exposes an absolute position.
2020-11-11 18:22:37 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 25b080a0f1 Update zigbee-herdsman to 0.13.28 (#4946)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-11 18:12:17 +01:00
Koen Kanters f8943011d4 Fix unbind for default_bind_group (901) not working from frontend. https://github.com/Koenkk/zigbee2mqtt/issues/4901 2020-11-11 18:11:18 +01:00
Koen Kanters 21b8e01149 Fix set/get with property including endpoint name having _ not working. 2020-11-11 17:11:54 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> ee50fb40f2 Update zigbee2mqtt-frontend to 0.2.74 (#4935)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-11 18:44:28 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 3c218305fa Update zigbee2mqtt-frontend to 0.2.72 (#4932)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-11 16:54:15 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> f31961982d Update zigbee2mqtt-frontend to 0.2.71 (#4928)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-11 13:08:21 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
8c20fa5d83 Update zigbee-herdsman-converters to 12.0.233 (#4924)
* Update zigbee-herdsman-converters to 12.0.233

* Update publish.test.js

* Update publish.test.js

* Update publish.test.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-10 21:40:15 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6ef08e855c Update zigbee-herdsman to 0.13.27 (#4923)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-10 21:26:51 +01:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
431c7f7327 Update zigbee-herdsman-converters to 12.0.232 (#4913)
* Update zigbee-herdsman-converters to 12.0.232

* Update homeassistant.test.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-09 17:46:02 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> ab7d507806 Update zigbee2mqtt-frontend to 0.2.69 (#4906)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-08 21:59:06 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
b1f129c6e8 Update zigbee-herdsman-converters to 12.0.231 (#4905)
* Update zigbee-herdsman-converters to 12.0.231

* Update homeassistant.js

* Update bridge.test.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-08 14:45:56 +01:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
5224f0f372 Update zigbee-herdsman-converters to 12.0.230 (#4896)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-08 13:24:11 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 30cd101adf Update dependencies (#4900)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-08 13:19:04 +01:00
Koen Kanters f99257ae83 Home Assistant: use device_class presence for presence binary sensor. 2020-11-07 19:16:16 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 0c6d4a0f05 Update zigbee-herdsman-converters to 12.0.229 (#4885)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-06 19:28:42 +01:00
Koen Kanters 752bb6c4d1 Merge branch 'master' into dev 2020-11-05 18:07:57 +01:00
Koen Kanters 6b32f30654 1.16.1 2020-11-05 18:02:13 +01:00
Koen Kanters ddddd88ef6 Downgrade mqtt version to 4.2.1. https://github.com/danielwelch/hassio-zigbee2mqtt/issues/460 2020-11-05 18:00:14 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 40619a5a53 Update zigbee-herdsman-converters to 12.0.228 (#4877)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-04 22:37:12 +01:00
Koen Kanters c27e66b618 Changes for exposes. 2020-11-04 22:33:00 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 3c2eab00d0 Update zigbee-herdsman-converters to 12.0.227 (#4875)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-04 18:18:47 +01:00
Koen Kanters dc89b92e2a Mark node 15 as supported. https://github.com/Koenkk/zigbee2mqtt/issues/4867 2020-11-04 17:39:09 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 828b4b3e4e Update zigbee-herdsman-converters to 12.0.226 (#4863)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-03 18:19:34 +01:00
Koen Kanters bd7d0be07a Clear device state when device leaves network. https://github.com/Koenkk/zigbee2mqtt/issues/4485 2020-11-02 18:27:06 +01:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
2ee3b5f12b Update zigbee-herdsman-converters to 12.0.225 (#4850)
* Update zigbee-herdsman-converters to 12.0.225

* Update homeassistant.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-02 18:00:58 +01:00
Jan 195d56d4ef Enhanced MQTT logging when connecting (#4843)
* enhanced logging

* reduced loglevel
2020-11-02 18:00:02 +01:00
Koen Kanters ca53013661 Dev 2020-11-01 12:59:06 +01:00
70 changed files with 15407 additions and 4823 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
+4 -1
View File
@@ -1,10 +1,13 @@
blank_issues_enabled: false
contact_links:
- name: Questions/discussion
url: https://github.com/Koenkk/zigbee2mqtt/discussions/new
about: Ask questions, discuss about devices or show things you made
- name: Frontend issues
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
+4 -1
View File
@@ -8,7 +8,10 @@ assignees: ''
---
<!--
IMPORTANT: Before submitting a request, first follow: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html
VERY IMPORTANT: Before submitting a request, first (try to) follow: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html
Dumping just the info about this device without actually attemping adding support yourself will likely lead into this issue being ignored.
Clearly indicate what you tried and where you got stuck.
-->
## Information about the device + link
+8 -5
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,19 +64,19 @@ 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
- name: Trigger zigbee2mqtt/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}}' \
--data '{"definition": {"id": 9}}' \
-u ${{ secrets.HASSIO_TRIGGER_SECRET }} \
"https://dev.azure.com/danielwelch2101/hassio-zigbee2mqtt/_apis/build/builds?api-version=5.1"
"https://dev.azure.com/zigbee2mqtt/Zigbee2mqtt%20Add-on/_apis/build/builds?api-version=5.1"
+67
View File
@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master, dev ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '28 12 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- run: npm install
- run: npm ls --depth 0 --prod --parseable | tail -n +2 | grep -v "zigbee" | xargs -I '{}' basename "{}" | xargs -I '{}' npm install '{}'@latest
- run: rm -rf node_modules npm-shrinkwrap.json && npm install && npm shrinkwrap --dev
- uses: peter-evans/create-pull-request@v2
- uses: peter-evans/create-pull-request@v3
with:
commit-message: Update dependencies
branch: "update_deps"
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
with:
node-version: 12
- run: npm install zigbee2mqtt-frontend@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v2
- uses: peter-evans/create-pull-request@v3
with:
commit-message: Update zigbee2mqtt-frontend to ${{ github.event.client_payload.version }}
branch: "update_frontend"
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
with:
node-version: 12
- run: npm install zigbee-herdsman@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v2
- uses: peter-evans/create-pull-request@v3
with:
commit-message: Update zigbee-herdsman to ${{ github.event.client_payload.version }}
branch: "update_zh"
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
with:
node-version: 12
- run: npm install zigbee-herdsman-converters@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v2
- uses: peter-evans/create-pull-request@v3
with:
commit-message: Update zigbee-herdsman-converters to ${{ github.event.client_payload.version }}
branch: "update_zhc"
-5
View File
@@ -1,5 +0,0 @@
{
"files.associations": {
"*.yaml": "home-assistant"
}
}
+132
View File
@@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+8
View File
@@ -0,0 +1,8 @@
# Contributing to Zigbee2MQTT
Everybody is invited and welcome to contribute to Zigbee2MQTT. Zigbee2MQTT is writen in JavaScript and is based upon [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) and [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters). Zigbee-herdsman-converters contains all device definition, zigbee-herdsman is responsible for handling all communication with the adapter.
- Pull requests are always created against the [**dev**](https://github.com/Koenkk/zigbee2mqtt/tree/dev) branch.
- Easiest way to start developing Zigbee2MQTT is by setting up a development environment (aka bare-metal installation). You can follow this [guide](https://www.zigbee2mqtt.io/getting_started/running_zigbee2mqtt.html#running-zigbee2mqtt) to do this.
- You can run the tests locally by executing `npm test`. Zigbee2MQTT enforces 100% code coverage, in case you add new code check if you code is covered by running `npm run test-with-coverage`. The coverage report can be found under `coverage/lcov-report/index.html`. Linting is also enforced and can be run with `npm run eslint`.
- When you want to add support for a new devices no changes to Zigbee2MQTT have to be made, only to zigbee-herdsman-converters. You can find a guide for it [here](https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html).
+2 -2
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">
@@ -67,7 +67,7 @@ Zigbee2MQTT integrates well with (almost) every home automation solution because
![Architecture](images/architecture.png)
### Internal Architecture
Zigbee2MQTT is made up of three modules, each developed in its own Github project. Starting from the hardware (adapter) and moving up; [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) connects to your Zigbee adapter an makes an API available to the higher levels of the stack. For e.g. Texas Instruments hardware, zigbee-herdsman uses the [TI zStack monitoring and test API](https://github.com/koenkk/zigbee-herdsman/raw/master/docs/Z-Stack%20Monitor%20and%20Test%20API.pdf) to communicate with the adapter. Zigbee-herdsman handles the core Zigbee communication. The module [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters) handles the mapping from individual device models to the Zigbee clusters they support. [Zigbee clusters](https://github.com/Koenkk/zigbee-herdsman/raw/master/docs/07-5123-06-zigbee-cluster-library-specification.pdf) are the layers of the Zigbee protocol on top of the base protocol that define things like how lights, sensors and switches talk to each other over the Zigbee network. Finally, the Zigbee2MQTT module drives zigbee-herdsman and maps the zigbee messages to MQTT messages. Zigbee2MQTT also keeps track of the state of the system. It uses a `database.db` file to store this state; a text file with a JSON database of connected devices and their capabilities.
Zigbee2MQTT is made up of three modules, each developed in its own Github project. Starting from the hardware (adapter) and moving up; [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) connects to your Zigbee adapter an makes an API available to the higher levels of the stack. For e.g. Texas Instruments hardware, zigbee-herdsman uses the [TI zStack monitoring and test API](https://github.com/koenkk/zigbee-herdsman/raw/master/docs/Z-Stack%20Monitor%20and%20Test%20API.pdf) to communicate with the adapter. Zigbee-herdsman handles the core Zigbee communication. The module [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters) handles the mapping from individual device models to the Zigbee clusters they support. [Zigbee clusters](https://github.com/Koenkk/zigbee-herdsman/raw/master/docs/Zigbee%20Cluster%20Library%20Specification%20v7.pdf) are the layers of the Zigbee protocol on top of the base protocol that define things like how lights, sensors and switches talk to each other over the Zigbee network. Finally, the Zigbee2MQTT module drives zigbee-herdsman and maps the zigbee messages to MQTT messages. Zigbee2MQTT also keeps track of the state of the system. It uses a `database.db` file to store this state; a text file with a JSON database of connected devices and their capabilities.
## Supported devices
See [Supported devices](https://www.zigbee2mqtt.io/information/supported_devices.html) to check whether your device is supported. There is quite an extensive list, including devices from vendors like Xiaomi, Ikea, Philips, OSRAM and more.
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env node
const path = require('path');
process.env['ZIGBEE2MQTT_DATA'] = process.env['ZIGBEE2MQTT_DATA'] || path.join(process.env['HOME'], '.z2m');
require('./index');
+2 -2
View File
@@ -1,4 +1,4 @@
FROM node:12-alpine3.12 as base
FROM node:14-alpine3.12 as base
WORKDIR /app
RUN apk add --no-cache tzdata eudev
@@ -19,7 +19,7 @@ FROM base as release
COPY --from=dependencies /app/node_modules ./node_modules
COPY lib ./lib
COPY index.js data/configuration.yaml ./
COPY LICENSE index.js data/configuration.yaml ./
COPY docker/docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+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();
+35 -31
View File
@@ -7,9 +7,6 @@ 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
@@ -26,32 +23,37 @@ const ExtensionBridge = require('./extension/bridge');
const ExtensionGroups = require('./extension/groups');
const ExtensionAvailability = require('./extension/availability');
const ExtensionBind = require('./extension/bind');
const ExtensionReport = require('./extension/report');
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,
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, this.restartCallback),
new ExtensionPublish(...args),
new ExtensionReceive(...args),
new ExtensionDeviceGroupMembership(...args),
@@ -64,10 +66,6 @@ class Controller {
new ExtensionReport(...args),
];
if (settings.get().experimental.new_api) {
this.extensions.push(new ExtensionBridge(...args, this.enableDisableExtension));
}
if (settings.get().frontend) {
this.extensions.push(new ExtensionFrontend(...args));
}
@@ -76,6 +74,10 @@ class Controller {
this.extensions.push(new ExtensionBridgeLegacy(...args));
}
if (settings.get().external_converters.length) {
this.extensions.push(new ExtensionExternalConverters(...args));
}
if (settings.get().homeassistant) {
this.extensions.push(new ExtensionHomeAssistant(...args));
}
@@ -88,18 +90,7 @@ class Controller {
if (settings.get().advanced.availability_timeout) {
this.extensions.push(new ExtensionAvailability(...args));
}
if (settings.get().external_converters.length) {
this.extensions.push(new ExtensionExternalConverters(...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() {
@@ -117,9 +108,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
@@ -149,7 +142,6 @@ class Controller {
// MQTT
this.mqtt.on('message', this.onMQTTMessage.bind(this));
await this.mqtt.connect();
this.mqtt.publish('bridge/state', 'online', {retain: true, qos: 0});
// Send all cached states.
if (settings.get().advanced.cache_state_send_on_startup && settings.get().advanced.cache_state) {
@@ -188,7 +180,11 @@ class Controller {
}
}
async stop() {
addExtension(extension) {
this.extensions.push(extension);
}
async stop(reason=null) {
// Call extensions
await this.callExtensionMethod('stop', []);
@@ -198,10 +194,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);
}
}
@@ -212,7 +209,12 @@ class Controller {
async onZigbeeEvent(type, data) {
const resolvedEntity = this.zigbee.resolveEntity(data.device || data.ieeeAddr);
if (data.device && !settings.getDevice(data.device.ieeeAddr) && data.device.type !== 'Coordinator') {
if (data.device && data.device.type === 'Coordinator') {
logger.debug('Ignoring message from coordinator');
return;
}
if (data.device && !settings.getDevice(data.device.ieeeAddr)) {
// Only deviceLeave doesn't have a device (not interesting to add to settings)
resolvedEntity.settings = {...settings.get().device_options, ...settings.addDevice(data.device.ieeeAddr)};
}
@@ -236,7 +238,8 @@ class Controller {
logger.info(`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`);
} else {
logger.warn(
`Device '${name}' with Zigbee model '${data.device.modelID}' is NOT supported, ` +
`Device '${name}' with Zigbee model '${data.device.modelID}' and manufacturer name ` +
`'${data.device.manufacturerName}' is NOT supported, ` +
`please follow https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`,
);
}
@@ -254,6 +257,7 @@ class Controller {
/* istanbul ignore else */
if (type === 'deviceLeave') {
logger.warn(`Device '${name || data.ieeeAddr}' left the network`);
this.state.remove(data.ieeeAddr);
}
}
@@ -341,7 +345,7 @@ class Controller {
}
}
this.eventBus.emit('publishEntityState', {payload: messagePayload, entity: resolvedEntity, stateChangeReason});
this.eventBus.emit('publishEntityState', {messagePayload, entity: resolvedEntity, stateChangeReason, payload});
}
async iteratePayloadAttributeOutput(topicRoot, payload, options) {
+24 -1
View File
@@ -3,15 +3,24 @@ const settings = require('../util/settings');
const utils = require('../util/utils');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const Extension = require('./extension');
const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/(.*)/availability`);
// Pingable end devices, some end devices should be pinged
// e.g. E11-G13 https://github.com/Koenkk/zigbee2mqtt/issues/775#issuecomment-453683846
const pingableEndDevices = [
zigbeeHerdsmanConverters.devices.find((d) => d.model === 'E11-G13'),
zigbeeHerdsmanConverters.devices.find((d) => d.model === 'E11-N1EA'),
zigbeeHerdsmanConverters.devices.find((d) => d.model === '53170161'),
];
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.
@@ -25,6 +34,7 @@ class Availability extends Extension {
this.state = {};
this.eventBus.on('deviceRemoved', (data) => this.onDeviceRemoved(data.resolvedEntity), this.constructor.name);
this.eventBus.on('deviceRenamed', (data) => this.onDeviceRenamed(data), this.constructor.name);
this.blocklist = settings.get().advanced.availability_blocklist
.concat(settings.get().advanced.availability_blacklist)
@@ -35,6 +45,10 @@ class Availability extends Extension {
.map((e) => settings.getEntity(e).ID);
}
onDeviceRenamed(data) {
this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 0});
}
onDeviceRemoved(resolvedEntity) {
this.mqtt.publish(`${resolvedEntity.name}/availability`, null, {retain: true, qos: 0});
delete this.state[resolvedEntity.device.ieeeAddr];
@@ -86,6 +100,14 @@ class Availability extends Extension {
}
}
async onMQTTMessage(topic, message) {
// Clear topics for non-existing devices
const match = topic.match(topicRegex);
if (match && (!this.zigbee.resolveEntity(match[1]) || this.zigbee.resolveEntity(match[1]).name !== match[1])) {
this.mqtt.publish(`${match[1]}/availability`, null, {retain: true, qos: 0});
}
}
async handleIntervalPingable(device) {
// When a device is already unavailable, log the ping failed on 'debug' instead of 'error'.
const resolvedEntity = this.zigbee.resolveEntity(device.ieeeAddr);
@@ -122,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() {
+263 -20
View File
@@ -5,30 +5,120 @@ const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/(
const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/(bind|unbind)`);
const Extension = require('./extension');
const stringify = require('json-stable-stringify-without-jsonify');
const debounce = require('debounce');
const zigbeeHersdman = require('zigbee-herdsman');
const clusterCandidates = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering'];
// See zigbee-herdsman-converters devices.js
const defaultBindGroup = {type: 'group_number', ID: 901};
const defaultReportConfiguration = {
minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1,
};
const getColorCapabilities = async (endpoint) => {
if (endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities') == null) {
await endpoint.read('lightingColorCtrl', ['colorCapabilities']);
}
const value = endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities');
return {
colorTemperature: (value & 1<<4) > 0,
colorXY: (value & 1<<3) > 0,
};
};
const reportClusters = {
'genOnOff': [
{attribute: 'onOff', ...defaultReportConfiguration, minimumReportInterval: 0, reportableChange: 0},
],
'genLevelCtrl': [
{attribute: 'currentLevel', ...defaultReportConfiguration},
],
'lightingColorCtrl': [
{
attribute: 'colorTemperature', ...defaultReportConfiguration,
condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorTemperature,
},
{
attribute: 'currentX', ...defaultReportConfiguration,
condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorXY,
},
{
attribute: 'currentY', ...defaultReportConfiguration,
condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorXY,
},
],
'closuresWindowCovering': [
{attribute: 'currentPositionLiftPercentage', ...defaultReportConfiguration},
{attribute: 'currentPositionTiltPercentage', ...defaultReportConfiguration},
],
};
const pollOnMessage = [
{
// On messages that have the cluster and type of below
cluster: {
manuSpecificPhilips: [
{type: 'commandHueNotification', data: {button: 2}},
{type: 'commandHueNotification', data: {button: 3}},
],
genLevelCtrl: [
{type: 'commandStep', data: {}},
{type: 'commandStepWithOnOff', data: {}},
{type: 'commandStop', data: {}},
{type: 'commandMoveWithOnOff', data: {}},
{type: 'commandStopWithOnOff', data: {}},
{type: 'commandMove', data: {}},
{type: 'commandMoveToLevelWithOnOff', data: {}},
],
},
// Read the following attributes
read: {cluster: 'genLevelCtrl', attributes: ['currentLevel']},
// When the bound devices/members of group have the following manufacturerIDs
manufacturerIDs: [
zigbeeHersdman.Zcl.ManufacturerCode.Philips,
zigbeeHersdman.Zcl.ManufacturerCode.ATMEL,
zigbeeHersdman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHersdman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
],
},
{
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}},
{type: 'commandHueNotification', data: {button: 4}},
],
},
read: {cluster: 'genOnOff', attributes: ['onOff']},
manufacturerIDs: [
zigbeeHersdman.Zcl.ManufacturerCode.Philips,
zigbeeHersdman.Zcl.ManufacturerCode.ATMEL,
zigbeeHersdman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHersdman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
],
},
];
class Bind extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.legacyApi = settings.get().advanced.legacy_api;
}
onMQTTConnected() {
/* istanbul ignore else */
if (this.legacyApi) {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/bind/#`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/unbind/#`);
}
/* istanbul ignore else */
if (settings.get().experimental.new_api) {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/bind`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/unbind`);
}
this.eventBus.on(`groupMembersChanged`, (d) => this.groupMembersChanged(d));
this.pollDebouncers = {};
}
parseMQTTMessage(topic, message) {
@@ -42,7 +132,7 @@ class Bind extends Extension {
type = topic.split('/')[0];
sourceKey = topic.replace(`${type}/`, '');
targetKey = message;
} else if (settings.get().experimental.new_api && topic.match(topicRegex)) {
} else if (topic.match(topicRegex)) {
type = topic.endsWith('unbind') ? 'unbind' : 'bind';
message = JSON.parse(message);
sourceKey = message.from;
@@ -56,6 +146,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);
@@ -73,6 +164,11 @@ class Bind extends Extension {
const failedClusters = [];
const attemptedClusters = [];
let bindTarget = null;
if (target.type === 'group') bindTarget = target.group;
else if (target.type === 'group_number') bindTarget = target.ID;
else bindTarget = target.endpoint;
// Find which clusters are supported by both the source and target.
// Groups are assumed to support all clusters.
for (const cluster of clusterCandidates) {
@@ -86,11 +182,6 @@ class Bind extends Extension {
attemptedClusters.push(cluster);
try {
let bindTarget = null;
if (target.type === 'group') bindTarget = target.group;
else if (target.type === 'group_number') bindTarget = target.ID;
else bindTarget = target.endpoint;
if (type === 'bind') {
await source.endpoint.bind(cluster, bindTarget);
} else {
@@ -147,6 +238,15 @@ class Bind extends Extension {
responseData[`clusters`] = successfulClusters;
responseData[`failed`] = failedClusters;
if (successfulClusters.length !== 0) {
if (type === 'bind') {
await this.setupReporting(source.endpoint.binds.filter((b) =>
successfulClusters.includes(b.cluster.name) && b.target === bindTarget));
} else if (target.type !== 'group_number') {
await this.disableUnnecessaryReportings(bindTarget);
}
}
}
const triggeredViaLegacyApi = topic.match(legacyTopicRegex);
@@ -161,6 +261,149 @@ class Bind extends Extension {
this.eventBus.emit(`devicesChanged`);
}
}
async groupMembersChanged(data) {
const bindsToGroup = this.zigbee.getClients().map((c) => c.endpoints).flat().map((e) => e.binds)
.flat().filter((b) => b.target === data.group.group);
if (data.action === 'add') {
await this.setupReporting(bindsToGroup);
} else { // action === remove/remove_all
await this.disableUnnecessaryReportings(data.endpoint);
}
}
getSetupReportingEndpoints(bind, coordinatorEp) {
const endpoints = bind.target.constructor.name === 'Group' ? bind.target.members : [bind.target];
return endpoints.filter((e) => {
const supportsInputCluster = e.supportsInputCluster(bind.cluster.name);
const hasConfiguredReporting = !!e.configuredReportings.find((c) => c.cluster.name === bind.cluster.name);
const hasBind = !!e.binds.find((b) => b.cluster.name === bind.cluster.name && b.target === coordinatorEp);
return supportsInputCluster && !(hasBind && hasConfiguredReporting);
});
}
async setupReporting(binds) {
const coordinator = this.zigbee.getDevicesByType('Coordinator')[0];
const coordinatorEndpoint = coordinator.getEndpoint(1);
for (const bind of binds.filter((b) => b.cluster.name in reportClusters)) {
for (const endpoint of this.getSetupReportingEndpoints(bind, coordinatorEndpoint)) {
const entity = `${this.zigbee.resolveEntity(endpoint.getDevice()).name}/${endpoint.ID}`;
try {
await endpoint.bind(bind.cluster.name, coordinatorEndpoint);
const items = await reportClusters[bind.cluster.name]
.filter(async (a) => !a.condition || await a.condition(endpoint))
.map((a) => {
const result = {...a};
delete result.condition;
return result;
});
await endpoint.configureReporting(bind.cluster.name, items);
logger.info(`Succesfully setup reporting for '${entity}' cluster '${bind.cluster.name}'`);
} catch (error) {
logger.warn(`Failed to setup reporting for '${entity}' cluster '${bind.cluster.name}'`);
}
}
}
this.eventBus.emit(`devicesChanged`);
}
async disableUnnecessaryReportings(target) {
const coordinator = this.zigbee.getDevicesByType('Coordinator')[0];
const coordinatorEndpoint = coordinator.getEndpoint(1);
const endpoints = target.constructor.name === 'Group' ? target.members : [target];
for (const endpoint of endpoints) {
const entity = `${this.zigbee.resolveEntity(endpoint.getDevice()).name}/${endpoint.ID}`;
const boundClusters = endpoint.binds.filter((b) => b.target === coordinatorEndpoint)
.map((b) => b.cluster.name);
const requiredClusters = this.zigbee.getClients().map((c) => c.endpoints).flat().map((e) => e.binds)
.flat().filter((bind) => {
if (bind.target.constructor.name === 'Group') {
return bind.target.members.includes(endpoint);
} else {
return bind.target === endpoint;
}
}).map((b) => b.cluster.name).filter((v, i, a) => a.indexOf(v) === i);
for (const cluster of boundClusters.filter((c) => !requiredClusters.includes(c) && c in reportClusters)) {
try {
await endpoint.unbind(cluster, coordinatorEndpoint);
const items = await reportClusters[cluster]
.filter(async (a) => !a.condition || await a.condition(endpoint))
.map((a) => {
const result = {...a, maximumReportInterval: 0xFFFF};
delete result.condition;
return result;
});
await endpoint.configureReporting(cluster, items);
logger.info(`Succesfully disabled reporting for '${entity}' cluster '${cluster}'`);
} catch (error) {
logger.warn(`Failed to disable reporting for '${entity}' cluster '${cluster}'`);
}
}
this.eventBus.emit('reportingDisabled', {device: endpoint.getDevice()});
}
}
async onZigbeeEvent(type, data, resolvedEntity) {
if (type === 'message') {
this.poll(data);
}
}
poll(message) {
/**
* This method poll bound endpoints and group members for state changes.
*
* A use case is e.g. a Hue Dimmer switch bound to a Hue bulb.
* Hue bulbs only report their on/off state.
* When dimming the bulb via the dimmer switch the state is therefore not reported.
* When we receive a message from a Hue dimmer we read the brightness from the bulb (if bound).
*/
const polls = pollOnMessage.filter((p) =>
p.cluster[message.cluster] && p.cluster[message.cluster].find((c) => c.type === message.type &&
utils.equalsPartial(message.data, c.data)),
);
if (polls.length) {
let toPoll = [];
// Add bound devices
toPoll = toPoll.concat([].concat(...message.device.endpoints.map((e) =>
e.binds.map((e) => e).filter((e) => e.target))));
toPoll = toPoll.filter((e) => e.target.constructor.name === 'Endpoint');
toPoll = toPoll.filter((e) => e.target.getDevice().type !== 'Coordinator');
toPoll = toPoll.map((e) => e.target);
// If message is published to a group, add members of the group
const group = message.groupID !== 0 ? this.zigbee.getGroupByID(message.groupID) : null;
if (group) {
toPoll = toPoll.concat(group.members);
}
toPoll = new Set(toPoll);
for (const endpoint of toPoll) {
for (const poll of polls) {
if (!poll.manufacturerIDs.includes(endpoint.getDevice().manufacturerID)) {
continue;
}
const key = `${endpoint.deviceIeeeAddress}_${endpoint.ID}_${pollOnMessage.indexOf(poll)}`;
if (!this.pollDebouncers[key]) {
this.pollDebouncers[key] = debounce(async () => {
await endpoint.read(poll.read.cluster, poll.read.attributes);
}, 1000);
}
this.pollDebouncers[key]();
}
}
}
}
}
module.exports = Bind;
+161 -30
View File
@@ -6,18 +6,22 @@ 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),
'device/configure_reporting': this.deviceConfigureReporting.bind(this),
'device/remove': this.deviceRemove.bind(this),
'device/rename': this.deviceRename.bind(this),
'group/add': this.groupAdd.bind(this),
@@ -25,14 +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),
};
}
@@ -41,8 +48,17 @@ class Bridge extends Extension {
this.coordinatorVersion = await this.zigbee.getCoordinatorVersion();
this.eventBus.on(`groupMembersChanged`, () => this.publishGroups());
this.eventBus.on(`devicesChanged`, () => this.publishDevices());
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/#`);
this.eventBus.on(`devicesChanged`, () => {
this.publishDevices();
this.publishInfo();
});
this.eventBus.on(`deviceRenamed`, () => {
this.publishInfo();
});
this.eventBus.on(`groupRenamed`, () => {
this.publishInfo();
});
this.zigbee.on('permitJoinChanged', (data) => this.permitJoinChanged(data));
await this.publishInfo();
await this.publishDevices();
await this.publishGroups();
@@ -58,7 +74,13 @@ class Bridge extends Extension {
}
}
logger.add(new EventTransport());
logger.addTransport(new EventTransport());
}
permitJoinChanged(data) {
if (!this.zigbee.isStopping()) {
this.publishInfo();
}
}
async onMQTTMessage(topic, message) {
@@ -126,6 +148,49 @@ class Bridge extends Extension {
return this.changeEntityOptions('group', message);
}
async bridgeOptions(message) {
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) {
return this.removeEntity('device', message);
}
@@ -159,15 +224,24 @@ 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');
}
let value;
let time;
let resolvedEntity;
if (typeof message === 'object') {
value = message.value;
time = message.time;
if (message.device) {
resolvedEntity = this.zigbee.resolveEntity(message.device);
if (!resolvedEntity || resolvedEntity.type !== 'device') {
@@ -178,13 +252,18 @@ class Bridge extends Extension {
value = message;
}
await this.zigbee.permitJoin(value, resolvedEntity);
await this.publishInfo();
return utils.getResponse(
message, resolvedEntity ? {value: value, device: message.device} : {value: value}, null,
);
if (typeof value === 'string') {
value = value.toLowerCase() === 'true';
}
await this.zigbee.permitJoin(value, resolvedEntity, time);
const response = {value};
if (resolvedEntity) response.device = message.device;
if (time) response.time = message.time;
return utils.getResponse(message, response, null);
}
// Deprecated
configLastSeen(message) {
const allowed = ['disable', 'ISO_8601', 'epoch', 'ISO_8601_local'];
const value = this.getValue(message);
@@ -192,11 +271,12 @@ class Bridge extends Extension {
throw new Error(`'${value}' is not an allowed value, allowed: ${allowed}`);
}
settings.set(['advanced', 'last_seen'], message);
settings.set(['advanced', 'last_seen'], value);
this.publishInfo();
return utils.getResponse(message, {value}, null);
}
// Deprecated
configHomeAssistant(message) {
const allowed = [true, false];
const value = this.getValue(message);
@@ -210,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);
@@ -222,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);
@@ -294,7 +376,7 @@ class Bridge extends Extension {
}
}
changeEntityOptions(entityType, message) {
async changeEntityOptions(entityType, message) {
if (typeof message !== 'object' || !message.hasOwnProperty('id') || !message.hasOwnProperty('options')) {
throw new Error(`Invalid payload`);
}
@@ -308,9 +390,43 @@ 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);
}
async deviceConfigureReporting(message) {
if (typeof message !== 'object' || !message.hasOwnProperty('id') || !message.hasOwnProperty('cluster') ||
!message.hasOwnProperty('maximum_report_interval') || !message.hasOwnProperty('minimum_report_interval') ||
!message.hasOwnProperty('reportable_change') || !message.hasOwnProperty('attribute')) {
throw new Error(`Invalid payload`);
}
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,
}]);
this.publishDevices();
logger.info(`Configured reporting for '${message.id}', '${message.cluster}.${message.attribute}'`);
return utils.getResponse(message, {
id: message.id, cluster: message.cluster, maximum_report_interval: message.maximum_report_interval,
minimum_report_interval: message.minimum_report_interval, reportable_change: message.reportable_change,
attribute: message.attribute,
}, null);
}
renameEntity(entityType, message) {
const deviceAndHasLast = entityType === 'device' && typeof message === 'object' && message.last === true;
if (typeof message !== 'object' || (!message.hasOwnProperty('from') && !deviceAndHasLast) ||
@@ -322,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') ?
@@ -338,12 +449,13 @@ class Bridge extends Extension {
// Clear retained messages
this.mqtt.publish(entity.name, '', {retain: true});
const oldFriendlyName = entity.settings.friendlyName;
if (entity.type === 'device') {
this.publishDevices();
this.eventBus.emit(`deviceRenamed`, {device: entity.device, homeAssisantRename});
this.eventBus.emit(`deviceRenamed`, {device: entity.device, homeAssisantRename, from: oldFriendlyName, to});
} else {
this.publishGroups();
this.eventBus.emit(`groupRenamed`, {group: entity.group, homeAssisantRename});
this.eventBus.emit(`groupRenamed`, {group: entity.group, homeAssisantRename, from: oldFriendlyName, to});
}
// Repulish entity state
@@ -351,7 +463,7 @@ class Bridge extends Extension {
return utils.getResponse(
message,
{from: entity.settings.friendlyName, to, homeassistant_rename: homeAssisantRename},
{from: oldFriendlyName, to, homeassistant_rename: homeAssisantRename},
null,
);
}
@@ -440,17 +552,22 @@ class Bridge extends Extension {
const config = objectAssignDeep.noMutate({}, settings.get());
delete config.advanced.network_key;
delete config.mqtt.password;
config.frontend && delete config.frontend.auth_token;
const payload = {
version: this.zigbee2mqttVersion.version,
commit: this.zigbee2mqttVersion.commitHash,
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() {
@@ -461,6 +578,7 @@ class Bridge extends Extension {
for (const endpoint of device.endpoints) {
const data = {
bindings: [],
configured_reportings: [],
clusters: {
input: endpoint.getInputClusters().map((c) => c.name),
output: endpoint.getOutputClusters().map((c) => c.name),
@@ -481,6 +599,16 @@ class Bridge extends Extension {
data.bindings.push({cluster: bind.cluster.name, target});
}
for (const configuredReporting of endpoint.configuredReportings) {
data.configured_reportings.push({
cluster: configuredReporting.cluster.name,
attribute: configuredReporting.attribute.name || configuredReporting.attribute.ID,
minimum_report_interval: configuredReporting.minimumReportInterval,
maximum_report_interval: configuredReporting.maximumReportInterval,
reportable_change: configuredReporting.reportableChange,
});
}
endpoints[endpoint.ID] = data;
}
@@ -494,13 +622,15 @@ class Bridge extends Extension {
power_source: device.powerSource,
software_build_id: device.softwareBuildID,
date_code: device.dateCode,
model_id: device.modelID,
interviewing: device.interviewing,
interview_completed: device.interviewCompleted,
endpoints,
};
});
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() {
@@ -508,7 +638,7 @@ class Bridge extends Extension {
const resolved = this.zigbee.resolveEntity(group);
return {
id: group.groupID,
friendly_name: resolved.name,
friendly_name: group.groupID === 901 ? 'default_bind_group' : resolved.name,
members: group.members.map((m) => {
return {
ieee_address: m.deviceIeeeAddress,
@@ -518,7 +648,8 @@ 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) {
@@ -527,8 +658,8 @@ class Bridge extends Extension {
model: definition.model,
vendor: definition.vendor,
description: definition.description,
[definition.hasOwnProperty('exposes') ? 'exposes' : 'supports']:
definition.hasOwnProperty('exposes') ? definition.exposes : definition.supports,
exposes: definition.exposes,
supports_ota: !!definition.ota,
};
} else {
return null;
+2 -13
View File
@@ -53,18 +53,6 @@ class Configure extends Extension {
return true;
}
onMQTTConnected() {
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
this.mqtt.subscribe(this.legacyTopic);
}
/* istanbul ignore else */
if (settings.get().experimental.new_api) {
this.mqtt.subscribe(this.topic);
}
}
async onMQTTMessage(topic, message) {
if (topic === this.legacyTopic) {
const resolvedEntity = this.zigbee.resolveEntity(message);
@@ -140,10 +128,11 @@ class Configure extends Extension {
logger.info(`Configuring '${resolvedEntity.name}'`);
try {
await resolvedEntity.definition.configure(device, this.coordinatorEndpoint);
await resolvedEntity.definition.configure(device, this.coordinatorEndpoint, logger);
logger.info(`Successfully configured '${resolvedEntity.name}'`);
device.meta.configured = resolvedEntity.definition.meta.configureKey;
device.save();
this.eventBus.emit(`devicesChanged`);
} catch (error) {
this.attempts[device.ieeeAddr]++;
const attempt = this.attempts[device.ieeeAddr];
+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;
+30 -37
View File
@@ -1,5 +1,4 @@
const http = require('http');
const httpProxy = require('http-proxy');
const serveStatic = require('serve-static');
const finalhandler = require('finalhandler');
const Extension = require('./extension');
@@ -25,38 +24,29 @@ class Frontend extends Extension {
this.onWebSocketConnection = this.onWebSocketConnection.bind(this);
this.server = http.createServer(this.onRequest);
this.server.on('upgrade', this.onUpgrade);
this.developmentServer = settings.get().frontend.development_server;
this.development = !!this.developmentServer;
this.host = settings.get().frontend.host || '0.0.0.0';
this.port = settings.get().frontend.port || 8080;
this.authToken = settings.get().frontend.auth_token || false;
this.retainedMessages = new Map();
if (this.development) {
this.proxy = httpProxy.createProxyServer({ws: true});
} else {
/* istanbul ignore next */
const options = {setHeaders: (res, path) => {
if (path.endsWith('index.html')) {
res.setHeader('Cache-Control', 'no-store');
}
}};
this.fileServer = serveStatic(frontend.getPath(), options);
}
/* istanbul ignore next */
const options = {setHeaders: (res, path) => {
if (path.endsWith('index.html')) {
res.setHeader('Cache-Control', 'no-store');
}
}};
this.fileServer = serveStatic(frontend.getPath(), options);
this.wss = new WebSocket.Server({noServer: true});
this.wss.on('connection', this.onWebSocketConnection);
}
onZigbeeStarted() {
if (this.development) {
logger.info(`Running frontend in development mode (${this.developmentServer})`);
}
this.server.listen(this.port);
logger.info(`Started frontend on port ${this.port}`);
this.server.listen(this.port, this.host);
logger.info(`Started frontend on port ${this.host}:${this.port}`);
}
async stop() {
for (const client of this.wss.clients) {
client.send(stringify({topic: 'bridge/state', payload: 'offline'}));
client.close();
}
@@ -64,25 +54,24 @@ class Frontend extends Extension {
this.server.close(resolve);
});
}
onRequest(request, response) {
if (this.development) {
this.proxy.web(request, response, {target: `http://${this.developmentServer}`});
} else {
this.fileServer(request, response, finalhandler(request, response));
}
this.fileServer(request, response, finalhandler(request, response));
}
authenticate(request, cb) {
const {query} = url.parse(request.url, true);
cb(!this.authToken || this.authToken === query.token);
}
onUpgrade(request, socket, head) {
const pathname = url.parse(request.url).pathname;
if (pathname === '/api') {
const wss = this.wss;
wss.handleUpgrade(request, socket, head, (ws) => wss.emit('connection', ws, request));
} else if (this.development && pathname === '/sockjs-node') {
this.proxy.ws(request, socket, head, {target: `ws://${this.developmentServer}`});
} else {
socket.destroy();
}
this.wss.handleUpgrade(request, socket, head, (ws) => {
this.authenticate(request, (isAuthentificated) => {
if (isAuthentificated) {
this.wss.emit('connection', ws, request);
} else {
ws.close(4401, 'Unauthorized');
}
});
});
}
onWebSocketConnection(ws) {
@@ -109,6 +98,10 @@ class Frontend extends Extension {
payload.last_seen = utils.formatDate(resolvedEntity.device.lastSeen, lastSeen);
}
if (resolvedEntity.device.linkquality !== undefined) {
payload.linkquality = resolvedEntity.device.linkquality;
}
ws.send(stringify({topic: resolvedEntity.name, payload}));
}
}
+9 -25
View File
@@ -19,27 +19,6 @@ class Groups extends Extension {
this.lastOptimisticState = {};
}
onMQTTConnected() {
/* istanbul ignore else */
if (this.legacyApi) {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/group/remove_all`);
for (let step = 1; step < 20; step++) {
const topic = `${settings.get().mqtt.base_topic}/bridge/group/${'+/'.repeat(step)}`;
this.mqtt.subscribe(`${topic}remove`);
this.mqtt.subscribe(`${topic}add`);
this.mqtt.subscribe(`${topic}remove_all`);
}
}
/* istanbul ignore else */
if (settings.get().experimental.new_api) {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/group/members/add`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/group/members/remove`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/group/members/remove_all`);
}
}
async onZigbeeStarted() {
this.eventBus.on('stateChange', this.onStateChange);
await this.syncGroupsWithSettings();
@@ -103,12 +82,12 @@ 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];
if (data.changed.hasOwnProperty(prop)) {
payload[prop] = data.changed[prop];
}
});
@@ -130,6 +109,9 @@ class Groups extends Extension {
}
}
} else {
// Invalidate the last optimistic group state when group state is changed directly.
delete this.lastOptimisticState[resolvedEntity.group.groupID];
const groupIDsToPublish = new Set();
for (const member of resolvedEntity.group.members) {
await this.publishEntityState(member.getDevice().ieeeAddr, payload, reason);
@@ -255,6 +237,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) {
@@ -339,7 +322,8 @@ class Groups extends Extension {
if (error) {
logger.error(error);
} else {
this.eventBus.emit('groupMembersChanged', {group: resolvedEntityGroup});
this.eventBus.emit('groupMembersChanged',
{group: resolvedEntityGroup, action: type, endpoint: resolvedEntityDevice.endpoint});
}
}
}
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -177,7 +177,9 @@ class BridgeLegacy extends Extension {
});
if (topic.split('/').pop() == 'get') {
this.mqtt.publish(`bridge/config/devices`, stringify(devices), {});
this.mqtt.publish(
`bridge/config/devices`, stringify(devices), {}, settings.get().mqtt.base_topic, false, false,
);
} else {
this.mqtt.publish('bridge/log', stringify({type: 'devices', message: devices}));
}
@@ -229,7 +231,7 @@ class BridgeLegacy extends Extension {
settings.changeFriendlyName(from, to);
logger.info(`Successfully renamed - ${from} to ${to} `);
const entity = this.zigbee.resolveEntity(to);
const eventData = isGroup ? {group: entity.group} : {device: entity.device};
const eventData = isGroup ? {group: entity.group} : {device: entity.device, from, to};
eventData.homeAssisantRename = false;
this.eventBus.emit(`${isGroup ? 'group' : 'device'}Renamed`, eventData);
@@ -351,8 +353,6 @@ class BridgeLegacy extends Extension {
}
async onMQTTConnected() {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/config/+`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/config/+/+`);
await this.publish();
}
@@ -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,13 +6,6 @@ const Extension = require('../extension');
const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/device/(.+)/get_group_membership$`);
class DeviceGroupMembership extends Extension {
onMQTTConnected() {
for (let step = 1; step < 20; step++) {
const topic = `${settings.get().mqtt.base_topic}/bridge/device/${'+/'.repeat(step)}get_group_membership`;
this.mqtt.subscribe(topic);
}
}
async onMQTTMessage(topic, message) {
const match = topic.match(topicRegex);
if (!match) {
@@ -1,11 +1,9 @@
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const logger = require('../util/logger');
const settings = require('../util/settings');
const logger = require('../../util/logger');
const settings = require('../../util/settings');
const ZNLDP12LM = zigbeeHerdsmanConverters.devices.find((d) => d.model === 'ZNLDP12LM');
const utils = require('../util/utils');
const Extension = require('./extension');
const debounce = require('debounce');
const ZigbeeHerdsman = require('zigbee-herdsman');
const utils = require('../../util/utils');
const Extension = require('../extension');
const defaultConfiguration = {
minimumReportInterval: 3, maximumReportInterval: 300, reportableChange: 1,
@@ -60,54 +58,11 @@ const clusters = {
],
};
const pollOnMessage = [
{
// Key is used this.pollDebouncers uniqueness
key: 1,
// On messages that have the cluster and type of below
cluster: {
manuSpecificPhilips: [
{type: 'commandHueNotification', data: {button: 2}},
{type: 'commandHueNotification', data: {button: 3}},
],
genLevelCtrl: [
{type: 'commandStep', data: {}},
{type: 'commandStepWithOnOff', data: {}},
{type: 'commandStop', data: {}},
{type: 'commandMoveWithOnOff', data: {}},
{type: 'commandStopWithOnOff', data: {}},
{type: 'commandMove', data: {}},
],
},
// Read the following attributes
read: {cluster: 'genLevelCtrl', attributes: ['currentLevel']},
// When the bound devices/members of group have the following manufacturerID
manufacturerID: ZigbeeHerdsman.Zcl.ManufacturerCode.Philips,
},
{
key: 2,
cluster: {
genOnOff: [
{type: 'commandOn', data: {}},
{type: 'commandOff', data: {}},
{type: 'commandOffWithEffect', data: {}},
],
manuSpecificPhilips: [
{type: 'commandHueNotification', data: {button: 1}},
{type: 'commandHueNotification', data: {button: 4}},
],
},
read: {cluster: 'genOnOff', attributes: ['onOff']},
manufacturerID: ZigbeeHerdsman.Zcl.ManufacturerCode.Philips,
},
];
class Report extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.queue = new Set();
this.failed = new Set();
this.pollDebouncers = {};
this.enabled = settings.get().advanced.report;
}
@@ -164,6 +119,8 @@ class Report extends Extension {
delete device.meta.reporting;
this.eventBus.emit('reportingDisabled', {device});
}
this.eventBus.emit(`devicesChanged`);
} catch (error) {
logger.error(
`Failed to ${term1.toLowerCase()} reporting for '${device.ieeeAddr}' - ${error.stack}`,
@@ -185,8 +142,14 @@ class Report extends Extension {
// This message is typically send when a device comes online after being powered off
// Ikea TRADFRI tend to forget their reporting after powered off.
// Re-setup reporting.
// Only resetup reporting if configuredReportings was not populated yet,
// else reconfigure is done in zigbee-herdsman-converters ikea.js/bulbOnEvent
// configuredReportings are saved since Zigbee2MQTT 1.17.0
// https://github.com/Koenkk/zigbee2mqtt/issues/966
if (this.enabled && messageType === 'deviceAnnounce' && utils.isIkeaTradfriDevice(device)) return true;
if (this.enabled && messageType === 'deviceAnnounce' && utils.isIkeaTradfriDevice(device) &&
device.endpoints.filter((e) => e.configuredReportings.length === 0).length === device.endpoints.length) {
return true;
}
// These do not support reproting.
// https://github.com/Koenkk/zigbee-herdsman/issues/110
@@ -224,61 +187,6 @@ class Report extends Extension {
if (this.shouldSetupReporting(resolvedEntity, type)) {
await this.setupReporting(resolvedEntity);
}
if (type === 'message') {
this.poll(data);
}
}
poll(message) {
/**
* This method poll bound endpoints and group members for state changes.
*
* A use case is e.g. a Hue Dimmer switch bound to a Hue bulb.
* Hue bulbs only report their on/off state.
* When dimming the bulb via the dimmer switch the state is therefore not reported.
* When we receive a message from a Hue dimmer we read the brightness from the bulb (if bound).
*/
const polls = pollOnMessage.filter((p) =>
p.cluster[message.cluster] && p.cluster[message.cluster].find((c) => c.type === message.type &&
utils.equalsPartial(message.data, c.data)),
);
if (polls.length) {
let toPoll = [];
// Add bound devices
toPoll = toPoll.concat([].concat(...message.device.endpoints.map((e) =>
e.binds.map((e) => e).filter((e) => e.target))));
toPoll = toPoll.filter((e) => e.target.constructor.name === 'Endpoint');
toPoll = toPoll.filter((e) => e.target.getDevice().type !== 'Coordinator');
toPoll = toPoll.map((e) => e.target);
// If message is published to a group, add members of the group
const group = message.groupID !== 0 ? this.zigbee.getGroupByID(message.groupID) : null;
if (group) {
toPoll = toPoll.concat(group.members);
}
toPoll = new Set(toPoll);
for (const endpoint of toPoll) {
for (const poll of polls) {
if (poll.manufacturerID !== endpoint.getDevice().manufacturerID) {
continue;
}
const key = `${endpoint.deviceIeeeAddress}_${endpoint.ID}_${poll.key}`;
if (!this.pollDebouncers[key]) {
this.pollDebouncers[key] = debounce(async () => {
await endpoint.read(poll.read.cluster, poll.read.attributes);
}, 1000);
}
this.pollDebouncers[key]();
}
}
}
}
}
+7 -14
View File
@@ -29,18 +29,6 @@ class NetworkMap extends Extension {
};
}
onMQTTConnected() {
/* istanbul ignore else */
if (this.legacyApi) {
this.mqtt.subscribe(this.legacyTopic);
this.mqtt.subscribe(this.legacyTopicRoutes);
}
if (settings.get().experimental.new_api) {
this.mqtt.subscribe(this.topic);
}
}
async onMQTTMessage(topic, message) {
/* istanbul ignore else */
if (this.legacyApi) {
@@ -54,7 +42,7 @@ class NetworkMap extends Extension {
}
}
if (settings.get().experimental.new_api && topic === this.topic) {
if (topic === this.topic) {
try {
message = utils.parseJSON(message, message);
const type = typeof message === 'object' ? message.type : message;
@@ -224,6 +212,7 @@ class NetworkMap extends Extension {
for (const device of devices.filter((d) => d.type != 'EndDevice')) {
failed.set(device, []);
await utils.sleep(1); // sleep 1 second between each scan to reduce stress on network.
const resolvedEntity = this.zigbee.resolveEntity(device);
try {
const result = await device.lqi();
@@ -256,7 +245,11 @@ class NetworkMap extends Extension {
model: resolvedEntity.definition.model,
vendor: resolvedEntity.definition.vendor,
description: resolvedEntity.definition.description,
supports: resolvedEntity.definition.supports,
supports: Array.from(new Set((resolvedEntity.definition.exposes).map((e) => {
return e.hasOwnProperty('name') ?
e.name :
`${e.type} (${e.features.map((f) => f.name).join(', ')})`;
}))).join(', '),
} : null;
networkMap.nodes.push({
+14 -25
View File
@@ -3,11 +3,11 @@ const logger = require('../util/logger');
const stringify = require('json-stable-stringify-without-jsonify');
const utils = require('../util/utils');
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/ota_update/.+$`);
const tradfriOTA = require('zigbee-herdsman-converters/lib/ota/tradfri');
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) {
@@ -15,21 +15,13 @@ class OTAUpdate extends Extension {
this.inProgress = new Set();
this.lastChecked = {};
this.legacyApi = settings.get().advanced.legacy_api;
if (settings.get().advanced.ikea_ota_use_test_url) {
tradfriOTA.useTestURL();
}
}
onMQTTConnected() {
/* istanbul ignore else */
if (this.legacyApi) {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/ota_update/check`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/ota_update/update`);
}
/* istanbul ignore else */
if (settings.get().experimental.new_api) {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/check`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/update`);
}
for (const device of this.zigbee.getClients()) {
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state.
// remove them.
@@ -43,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();
@@ -102,20 +96,15 @@ class OTAUpdate extends Extension {
}
getEntityPublishPayload(state, progress=null, remaining=null) {
const payload = {};
const payload = {update: {state}};
if (progress !== null) payload.update.progress = progress;
if (remaining !== null) payload.update.remaining = Math.round(remaining);
/* istanbul ignore else */
if (this.legacyApi) {
payload.update_available = state === 'available';
}
/* istanbul ignore else */
if (settings.get().experimental.new_api) {
payload.update = {state};
if (progress !== null) payload.update.progress = progress;
if (remaining !== null) payload.update.remaining = Math.round(remaining);
}
return payload;
}
@@ -211,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);
+18 -19
View File
@@ -27,18 +27,6 @@ const groupConverters = [
];
class EntityPublish extends Extension {
onMQTTConnected() {
// Subscribe to topics.
const baseTopic = settings.get().mqtt.base_topic;
for (let step = 1; step < 20; step++) {
const topic = `${baseTopic}/${'+/'.repeat(step)}`;
this.mqtt.subscribe(`${topic}set`);
this.mqtt.subscribe(`${topic}set/+`);
this.mqtt.subscribe(`${topic}get`);
this.mqtt.subscribe(`${topic}get/+`);
}
}
parseTopic(topic) {
const match = topic.match(topicRegex);
if (!match) {
@@ -178,14 +166,13 @@ class EntityPublish extends Extension {
// When the key has a endpointName included (e.g. state_right), this will override the target.
if (resolvedEntity.type === 'device' && key.includes('_')) {
const underscoreIndex = key.lastIndexOf('_');
const possibleEndpointName = key.substring(underscoreIndex + 1, key.length);
if (utils.getEndpointNames().includes(possibleEndpointName)) {
endpointName = possibleEndpointName;
key = key.substring(0, underscoreIndex);
const endpointNameMatch = utils.getEndpointNames().find((n) => key.endsWith(`_${n}`));
if (endpointNameMatch) {
endpointName = endpointNameMatch;
key = key.replace(`_${endpointNameMatch}`, '');
const device = target.getDevice();
actualTarget = device.getEndpoint(definition.endpoint(device)[endpointName]);
if (!actualTarget) {
logger.error(`Device '${resolvedEntity.name}' has no endpoint '${endpointName}'`);
continue;
@@ -212,7 +199,7 @@ class EntityPublish extends Extension {
const meta = {
endpoint_name: endpointName,
options,
message: json,
message: {...json},
logger,
device,
state: entityState,
@@ -220,6 +207,17 @@ class EntityPublish extends Extension {
mapped: definition,
};
// Strip endpoint name from meta.message properties.
if (meta.endpoint_name) {
for (const [key, value] of Object.entries(meta.message)) {
if (key.endsWith(meta.endpoint_name)) {
delete meta.message[key];
const keyWithoutEndpoint = key.substring(0, key.length - meta.endpoint_name.length - 1);
meta.message[keyWithoutEndpoint] = value;
}
}
}
try {
if (topic.type === 'set' && converter.convertSet) {
logger.debug(`Publishing '${topic.type}' '${key}' to '${resolvedEntity.name}'`);
@@ -255,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
+4 -7
View File
@@ -74,16 +74,13 @@ class Receive extends Extension {
return false;
}
if (data.device.ieeeAddr === this.coordinator.ieeeAddr) {
logger.debug('Ignoring message from coordinator');
return false;
}
if (!resolvedEntity.definition) {
if (data.device.interviewing) {
logger.debug(`Skipping message, definition is undefined and still interviewing`);
} else {
logger.warn(`Received message from unsupported device with Zigbee model '${data.device.modelID}'`);
logger.warn(
`Received message from unsupported device with Zigbee model '${data.device.modelID}' ` +
`and manufacturer name '${data.device.manufacturerName}'`);
logger.warn(`Please see: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html.`);
}
@@ -122,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)}'`,
+23 -4
View File
@@ -8,6 +8,7 @@ class MQTT extends events.EventEmitter {
constructor() {
super();
this.onMessage = this.onMessage.bind(this);
this.publishedTopics = new Set();
}
async connect() {
@@ -18,7 +19,7 @@ class MQTT extends events.EventEmitter {
will: {
topic: `${settings.get().mqtt.base_topic}/bridge/state`,
payload: 'offline',
retain: true,
retain: settings.get().mqtt.force_disable_retain ? false : true,
},
};
@@ -44,8 +45,11 @@ class MQTT extends events.EventEmitter {
}
if (mqttSettings.user && mqttSettings.password) {
logger.debug(`Using MQTT login with username: ${mqttSettings.user}`);
options.username = mqttSettings.user;
options.password = mqttSettings.password;
} else {
logger.debug(`Using MQTT anonymous login`);
}
if (mqttSettings.client_id) {
@@ -69,8 +73,11 @@ class MQTT extends events.EventEmitter {
return new Promise((resolve) => {
this.client = mqtt.connect(mqttSettings.server, options);
const self = this;
this.client.on('connect', () => {
logger.info('Connected to MQTT server');
self.subscribe(`${settings.get().mqtt.base_topic}/#`);
self.publish('bridge/state', 'online', {retain: true, qos: 0});
resolve();
});
@@ -91,17 +98,24 @@ class MQTT extends events.EventEmitter {
}
onMessage(topic, message) {
this.emit('message', {topic, message: message + ''});
// Since we subscribe to zigbee2mqtt/# we also receive the message we send ourselves, skip these.
if (!this.publishedTopics.has(topic)) {
this.emit('message', {topic, message: message + ''});
}
}
isConnected() {
return this.client && !this.client.reconnecting;
}
async publish(topic, payload, options, base=settings.get().mqtt.base_topic, skipLog=false) {
async publish(topic, payload, options, base=settings.get().mqtt.base_topic, skipLog=false, skipReceive=true) {
topic = `${base}/${topic}`;
options = {qos: 0, retain: false, ...options};
if (skipReceive) {
this.publishedTopics.add(topic);
}
if (!this.isConnected()) {
if (!skipLog) {
logger.error(`Not connected to MQTT server!`);
@@ -114,8 +128,13 @@ class MQTT extends events.EventEmitter {
logger.info(`MQTT publish: topic '${topic}', payload '${payload}'`);
}
const actualOptions = {...options};
if (settings.get().mqtt.force_disable_retain) {
actualOptions.retain = false;
}
return new Promise((resolve) => {
this.client.publish(topic, payload, options, () => {
this.client.publish(topic, payload, actualOptions, () => {
this.emit('publishedMessage', {topic, payload, options});
resolve();
});
+8 -6
View File
@@ -3,7 +3,6 @@ const data = require('./util/data');
const settings = require('./util/settings');
const fs = require('fs');
const objectAssignDeep = require('object-assign-deep');
const stringify = require('json-stable-stringify-without-jsonify');
const saveInterval = 1000 * 60 * 5; // 5 minutes
@@ -57,7 +56,7 @@ class State {
save() {
if (settings.get().advanced.cache_state_persistent) {
logger.debug(`Saving state to file ${this.file}`);
const json = stringify(this.state, null, 4);
const json = JSON.stringify(this.state, null, 4);
try {
fs.writeFileSync(this.file, json, 'utf8');
} catch (e) {
@@ -78,18 +77,21 @@ class State {
set(ID, state, reason=null) {
const toState = objectAssignDeep.noMutate(state);
const fromState = this.state[ID];
const changed = {};
for (const property of Object.keys(toState)) {
if (dontCacheProperties.find((p) => property.match(p))) {
delete toState[property];
}
if (!fromState || toState[property] !== fromState[property]) {
changed[property] = toState[property];
}
}
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: state, reason, changed});
}
removeKey(ID, path) {
+14 -5
View File
@@ -21,10 +21,14 @@ if (output.includes('file')) {
fx.mkdirSync(directory);
}
const z2mToWinstonLevel = (level) => level === 'warn' ? 'warning' : level;
const winstonToZ2mLevel = (level) => level === 'warning' ? 'warn' : level;
// Determine the log level.
const level = settings.get().advanced.log_level;
let level = settings.get().advanced.log_level;
const validLevels = ['info', 'error', 'warn', 'debug'];
assert(validLevels.includes(level), `'${level}' is not a valid log_level, use one of '${validLevels.join(', ')}'`);
level = z2mToWinstonLevel(level);
const levelWithCompensatedLength = {
'info': 'info ',
@@ -45,7 +49,7 @@ const transportsToUse = [
winston.format.timestamp({format: timestampFormat}),
winston.format.printf(/* istanbul ignore next */(info) => {
let {timestamp, level, message} = info;
level = level === 'warning' ? 'warn' : level;
level = winstonToZ2mLevel(level);
const prefix = colorizer.colorize(level, `Zigbee2MQTT:${levelWithCompensatedLength[level]}`);
return `${prefix} ${timestamp.split('.')[0]}: ${message}`;
}),
@@ -63,7 +67,7 @@ const transportFileOptions = {
winston.format.timestamp({format: timestampFormat}),
winston.format.printf(/* istanbul ignore next */(info) => {
let {timestamp, level, message} = info;
level = level === 'warning' ? 'warn' : level;
level = winstonToZ2mLevel(level);
return `${levelWithCompensatedLength[level]} ${timestamp.split('.')[0]}: ${message}`;
}),
),
@@ -129,11 +133,16 @@ function logOutput() {
}
}
logger.addTransport = (transport) => {
transport.level = transportsToUse[0].level;
logger.add(transport);
};
logger.cleanup = cleanup;
logger.logOutput = logOutput;
logger.getLevel = () => transportsToUse[0].level;
logger.getLevel = () => winstonToZ2mLevel(transportsToUse[0].level);
logger.setLevel = (level) => {
transportsToUse.forEach((transport) => transport.level = level);
level = z2mToWinstonLevel(level);
logger.transports.forEach((transport) => transport.level = level);
};
// winston.config.syslog.levels doesnt have warn, but is required for syslog.
+103 -198
View File
@@ -6,7 +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: [],
@@ -17,6 +20,11 @@ const defaults = {
permit_join: false,
mqtt: {
include_device_information: false,
/**
* Configurable force disable retain flag on mqtt publish.
* https://github.com/Koenkk/zigbee2mqtt/pull/4948
*/
force_disable_retain: false,
},
serial: {
disable_led: false,
@@ -45,7 +53,6 @@ const defaults = {
experimental: {
// json or attribute or attribute_and_json
output: 'json',
new_api: false,
},
advanced: {
legacy_api: true,
@@ -60,6 +67,7 @@ const defaults = {
ext_pan_id: [0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD],
channel: 11,
adapter_concurrent: null,
adapter_delay: null,
// Availability timeout in seconds, disabled by default.
availability_timeout: 0,
@@ -129,173 +137,20 @@ 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: [],
};
const schema = {
type: 'object',
properties: {
device_options: {type: 'object'},
homeassistant: {type: 'boolean'},
permit_join: {type: 'boolean'},
mqtt: {
type: 'object',
properties: {
base_topic: {type: 'string'},
server: {type: 'string'},
keepalive: {type: 'number'},
ca: {type: 'string'},
key: {type: 'string'},
cert: {type: 'string'},
user: {type: 'string'},
password: {type: 'string'},
client_id: {type: 'string'},
reject_unauthorized: {type: 'boolean'},
include_device_information: {type: 'boolean'},
version: {type: 'number'},
},
required: ['base_topic', 'server'],
},
serial: {
type: 'object',
properties: {
port: {type: ['string', 'null']},
disable_led: {type: 'boolean'},
adapter: {type: 'string', enum: ['deconz', 'zstack', 'zigate']},
},
},
blocklist: {type: 'array', items: {type: 'string'}},
passlist: {type: 'array', items: {type: 'string'}},
// Deprecated: use block/passlist
whitelist: {type: 'array', items: {type: 'string'}},
ban: {type: 'array', items: {type: 'string'}},
experimental: {
type: 'object',
properties: {
transmit_power: {type: 'number'},
output: {type: 'string', enum: ['attribute_and_json', 'attribute', 'json']},
},
},
advanced: {
type: 'object',
properties: {
legacy_api: {type: 'boolean'},
pan_id: {type: 'number'},
ext_pan_id: {type: 'array', items: {type: 'number'}},
channel: {type: 'number', minimum: 11, maximum: 26},
cache_state: {type: 'boolean'},
cache_state_persistent: {type: 'boolean'},
cache_state_send_on_startup: {type: 'boolean'},
log_rotation: {type: 'boolean'},
log_level: {type: 'string', enum: ['info', 'warn', 'error', 'debug']},
log_output: {type: 'array', items: {type: 'string'}},
log_directory: {type: 'string'},
log_file: {type: 'string'},
baudrate: {type: 'number'},
rtscts: {type: 'boolean'},
soft_reset_timeout: {type: 'number', minimum: 0},
network_key: {type: ['array', 'string'], items: {type: 'number'}},
last_seen: {type: 'string', enum: ['disable', 'ISO_8601', 'ISO_8601_local', 'epoch']},
elapsed: {type: 'boolean'},
availability_timeout: {type: 'number', minimum: 0},
availability_blocklist: {type: 'array', items: {type: 'string'}},
availability_passlist: {type: 'array', items: {type: 'string'}},
// Deprecated, use block/passlist
availability_blacklist: {type: 'array', items: {type: 'string'}},
availability_whitelist: {type: 'array', items: {type: 'string'}},
report: {type: 'boolean'},
homeassistant_discovery_topic: {type: 'string'},
homeassistant_status_topic: {type: 'string'},
timestamp_format: {type: 'string'},
adapter_concurrent: {type: 'number'},
},
},
map_options: {
type: 'object',
properties: {
graphviz: {
type: 'object',
properties: {
colors: {
type: 'object',
properties: {
fill: {
type: 'object',
properties: {
enddevice: {type: 'string'},
coordinator: {type: 'string'},
router: {type: 'string'},
},
},
font: {
type: 'object',
properties: {
enddevice: {type: 'string'},
coordinator: {type: 'string'},
router: {type: 'string'},
},
},
line: {
type: 'object',
properties: {
active: {type: 'string'},
inactive: {type: 'string'},
},
},
},
},
},
},
},
},
devices: {
type: 'object',
propertyNames: {
pattern: '^0x[\\d\\w]{16}$',
},
patternProperties: {
'^.*$': {
type: 'object',
properties: {
friendly_name: {type: 'string'},
retain: {type: 'boolean'},
retention: {type: 'number'},
qos: {type: 'number'},
filtered_attributes: {type: 'array', items: {type: 'string'}},
},
required: ['friendly_name'],
},
},
},
groups: {
type: 'object',
propertyNames: {
pattern: '^[\\w].*$',
},
patternProperties: {
'^.*$': {
type: 'object',
properties: {
friendly_name: {type: 'string'},
retain: {type: 'boolean'},
devices: {type: 'array', items: {type: 'string'}},
optimistic: {type: 'boolean'},
qos: {type: 'number'},
filtered_attributes: {type: 'array', items: {type: 'string'}},
},
required: ['friendly_name'],
},
},
},
},
external_converters: {
type: 'array',
items: {type: 'string'},
},
required: ['homeassistant', 'permit_join', 'mqtt'],
};
let _settings;
let _settingsWithDefaults;
@@ -307,7 +162,12 @@ function write() {
const actual = yaml.read(file);
// In case the setting is defined in a separte file (e.g. !secret network_key) update it there.
for (const path of [['mqtt', 'user'], ['mqtt', 'password'], ['advanced', 'network_key']]) {
for (const path of [
['mqtt', 'user'],
['mqtt', 'password'],
['advanced', 'network_key'],
['frontend', 'auth_token'],
]) {
if (actual[path[0]] && actual[path[0]][path[1]]) {
const match = /!(.*) (.*)/g.exec(actual[path[0]][path[1]]);
if (match) {
@@ -317,15 +177,27 @@ function write() {
}
}
if (typeof actual.devices === 'string') {
yaml.writeIfChanged(data.joinPath(actual.devices), settings.devices);
toWrite.devices = actual.devices;
}
// Write devices/groups to separate file if required.
const writeDevicesOrGroups = (type) => {
if (typeof actual[type] === 'string' || Array.isArray(actual[type])) {
const fileToWrite = Array.isArray(actual[type]) ? actual[type][0] : actual[type];
const content = objectAssignDeep.noMutate(settings[type]);
if (typeof actual.groups === 'string') {
yaml.writeIfChanged(data.joinPath(actual.groups), settings.groups);
toWrite.groups = actual.groups;
}
// If an array, only write to first file and only devices which are not in the other files.
if (Array.isArray(actual[type])) {
actual[type].filter((f, i) => i !== 0)
.map((f) => yaml.readIfExists(data.joinPath(f), {}))
.map((c) => Object.keys(c))
.forEach((k) => delete content[k]);
}
yaml.writeIfChanged(data.joinPath(fileToWrite), content);
toWrite[type] = actual[type];
}
};
writeDevicesOrGroups('devices');
writeDevicesOrGroups('groups');
yaml.writeIfChanged(file, toWrite);
@@ -347,10 +219,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 = [];
@@ -359,6 +229,11 @@ function validate() {
errors.push(`advanced.network_key: should be array or 'GENERATE' (is '${_settings.advanced.network_key}')`);
}
if (_settings.advanced && _settings.advanced.pan_id && typeof _settings.advanced.pan_id === 'string' &&
_settings.advanced.pan_id !== 'GENERATE') {
errors.push(`advanced.pan_id: should be number or 'GENERATE' (is '${_settings.advanced.pan_id}')`);
}
// Verify that all friendly names are unique
const names = [];
const check = (name) => {
@@ -420,16 +295,24 @@ function read() {
s.advanced.network_key = interpetValue(s.advanced.network_key);
}
// Read devices/groups configuration from separate file.
if (typeof s.devices === 'string') {
const file = data.joinPath(s.devices);
s.devices = yaml.readIfExists(file) || {};
if (s.frontend && s.frontend.auth_token) {
s.frontend.auth_token = interpetValue(s.frontend.auth_token);
}
if (typeof s.groups === 'string') {
const file = data.joinPath(s.groups);
s.groups = yaml.readIfExists(file) || {};
}
// Read devices/groups configuration from separate file if specified.
const readDevicesOrGroups = (type) => {
if (typeof s[type] === 'string' || Array.isArray(s[type])) {
const files = Array.isArray(s[type]) ? s[type] : [s[type]];
s[type] = {};
for (const file of files) {
const content = yaml.readIfExists(data.joinPath(file), {});
s[type] = objectAssignDeep.noMutate(s[type], content);
}
}
};
readDevicesOrGroups('devices');
readDevicesOrGroups('groups');
return s;
}
@@ -450,15 +333,15 @@ function applyEnvironmentVariables(settings) {
if (type.indexOf('object') >= 0 || type.indexOf('array') >= 0) {
setting[key] = JSON.parse(process.env[envVariableName]);
}
if (type.indexOf('number') >= 0) {
} else if (type.indexOf('number') >= 0) {
setting[key] = process.env[envVariableName] * 1;
}
if (type.indexOf('boolean') >= 0) {
} else if (type.indexOf('boolean') >= 0) {
setting[key] = process.env[envVariableName].toLowerCase() === 'true';
}
if (type.indexOf('string') >= 0) {
setting[key] = process.env[envVariableName];
} else {
/* istanbul ignore else */
if (type.indexOf('string') >= 0) {
setting[key] = process.env[envVariableName];
}
}
}
}
@@ -520,6 +403,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];
@@ -650,6 +551,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`);
}
@@ -743,6 +645,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`);
}
@@ -763,6 +666,7 @@ module.exports = {
validate,
get: getWithDefaults,
set,
apply,
getDevice,
getGroup,
getGroups,
@@ -778,15 +682,16 @@ module.exports = {
removeDeviceFromGroup,
changeEntityOptions,
changeFriendlyName,
// For tests only
_write: write,
_reRead: () => {
schema,
reRead: () => {
_settings = null;
get();
_settingsWithDefaults = null;
getWithDefaults();
},
// For tests only
_write: write,
_clear: () => {
_settings = null;
_settingsWithDefaults = null;
+740
View File
@@ -0,0 +1,740 @@
{
"type": "object",
"properties": {
"device_options": {
"type": "object"
},
"homeassistant": {
"title": "Home Assistant integration",
"type": "boolean",
"description": "Home Assistant integration (MQTT discovery)",
"default": false
},
"permit_join": {
"type": "boolean",
"default": false,
"title": "Permit join",
"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"
},
"examples": ["DIYRuZ_FreePad.js"]
},
"mqtt": {
"type": "object",
"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",
"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
},
"include_device_information": {
"type": "boolean",
"title": "Include device information",
"description": "Include device information to mqtt messages",
"default": false
},
"version": {
"type": ["number", "null"],
"title": "Version",
"requiresRestart": true,
"description": "MQTT protocol version",
"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
}
},
"required": ["base_topic", "server"]
},
"serial": {
"type": "object",
"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", "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": {
"type": "string"
}
},
"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": {
"type": "string"
}
},
"whitelist": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Whitelist (deprecated, use passlist)",
"items": {
"type": "string"
}
},
"ban": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Ban (deprecated, use blocklist)",
"items": {
"type": "string"
}
},
"experimental": {
"type": "object",
"title": "Experimental",
"properties": {
"transmit_power": {
"type": ["number", "null"],
"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",
"enum": ["attribute_and_json", "attribute", "json"],
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)"
}
}
},
"advanced": {
"type": "object",
"title": "Advanced",
"properties": {
"legacy_api": {
"type": "boolean",
"title": "Legacy API",
"requiresRestart": true,
"description": "Disables the legacy api (false = disable)",
"default": true
},
"pan_id": {
"oneOf": [{
"type": "string",
"title": "Pan ID (string)"
},
{
"type": "number",
"title": "Pan ID (number)"
}
],
"title": "Pan ID",
"requiresRestart": true,
"description": "ZigBee pan ID, changing requires repairing all devices!"
},
"ext_pan_id": {
"type": "array",
"items": {
"type": "number"
},
"title": "Ext Pan ID",
"requiresRestart": true,
"description": "Zigbee extended pan ID, changing requires repairing all devices!"
},
"channel": {
"type": "number",
"minimum": 11,
"maximum": 26,
"default": 11,
"title": "ZigBee channel",
"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",
"title": "Cache state",
"description": "MQTT message payload will contain all attributes, not only changed ones. Has to be true when integrating via Home Assistant",
"default": true
},
"cache_state_persistent": {
"type": "boolean",
"title": "Persist cache state",
"description": "Persist cached state, only used when cache_state: true",
"default": true
},
"cache_state_send_on_startup": {
"type": "boolean",
"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_level": {
"type": "string",
"enum": ["info", "warn", "error", "debug"],
"title": "Log level",
"description": "Logging level",
"default": "info"
},
"log_output": {
"type": "array",
"requiresRestart": true,
"items": {
"type": "string",
"enum": ["console", "file", "syslog"]
},
"title": "Log output",
"description": "Output location of the log, leave empty to supress logging"
},
"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"
},
"baudrate": {
"type": "number",
"title": "Baudrate",
"requiresRestart": true,
"description": "Baudrate for serial port, default: 115200 for Z-Stack, 38400 for Deconz",
"examples": [38400, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS",
"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
},
"network_key": {
"oneOf": [{
"type": "string",
"title": "Network key(string)"
},
{
"type": "array",
"items": {
"type": "number"
},
"title": "Network key(array)"
}
],
"title": "Network key",
"requiresRestart": true,
"description": "Network encryption key, changing requires repairing all devices!"
},
"last_seen": {
"type": "string",
"enum": ["disable", "ISO_8601", "ISO_8601_local", "epoch"],
"title": "Last seen",
"description": "Add a last_seen attribute to MQTT messages, contains date/time of last Zigbee message",
"default": "disable"
},
"elapsed": {
"type": "boolean",
"title": "Elapsed",
"description": "Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg",
"default": false
},
"availability_timeout": {
"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"
},
"availability_blocklist": {
"type": "array",
"items": {
"type": "string"
},
"requiresRestart": true,
"title": "Availability Blocklist",
"description": "Prevent devices from being checked for availability"
},
"availability_passlist": {
"type": "array",
"items": {
"type": "string"
},
"requiresRestart": true,
"title": "Availability passlist",
"description": "Only enable availability check for certain devices"
},
"availability_blacklist": {
"type": "array",
"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, use passlist)"
},
"report": {
"type": "boolean",
"title": "Reporting",
"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"],
"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",
"properties": {
"graphviz": {
"type": "object",
"properties": {
"colors": {
"type": "object",
"properties": {
"fill": {
"type": "object",
"properties": {
"enddevice": {
"type": "string"
},
"coordinator": {
"type": "string"
},
"router": {
"type": "string"
}
}
},
"font": {
"type": "object",
"properties": {
"enddevice": {
"type": "string"
},
"coordinator": {
"type": "string"
},
"router": {
"type": "string"
}
}
},
"line": {
"type": "object",
"properties": {
"active": {
"type": "string"
},
"inactive": {
"type": "string"
}
}
}
}
}
}
}
}
},
"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": {
"pattern": "^0x[\\d\\w]{16}$"
},
"patternProperties": {
"^.*$": {
"$ref": "#/definitions/device"
}
}
},
"groups": {
"type": "object",
"propertyNames": {
"pattern": "^[\\w].*$"
},
"patternProperties": {
"^.*$": {
"$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"],
"definitions": {
"device": {
"type": "object",
"properties": {
"friendly_name": {
"type": "string",
"title": "Friendly name",
"description": "Used in the MQTT topic of a device. By default this is the device ID",
"readOnly": true
},
"retain": {
"type": "boolean",
"title": "Retain",
"description": "Retain MQTT messages of this device"
},
"retention": {
"type": "number",
"title": "Retention",
"description": "Sets the MQTT Message Expiry in seconds, Make sure to set mqtt.version to 5"
},
"qos": {
"type": "number",
"title": "QoS",
"descritption": "QoS level for MQTT messages of this device"
},
"debounce": {
"type": "number",
"title": "Debounce",
"description": "Debounces messages of this device"
},
"debounce_ignore": {
"type": "array",
"items": {
"type": "string"
},
"examples": ["action"],
"title": "Ignore debounce",
"description": "Protects unique payload values of specified payload properties from overriding within debounce time"
},
"optimistic": {
"type": "boolean",
"title": "Optimistic",
"description": "Publish optimistic state after set (default true)"
},
"filtered_attributes": {
"type": "array",
"items": {
"type": "string"
},
"examples": ["temperature", "battery", "action"],
"title": "Filtered attributes",
"description": "Allows to prevent certain attributes from being published"
}
},
"required": ["friendly_name"]
},
"group": {
"type": "object",
"properties": {
"friendly_name": {
"type": "string"
},
"retain": {
"type": "boolean"
},
"devices": {
"type": "array",
"items": {
"type": "string"
}
},
"optimistic": {
"type": "boolean"
},
"qos": {
"type": "number"
},
"filtered_attributes": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["friendly_name"]
}
}
}
+62 -5
View File
@@ -1,6 +1,9 @@
const equals = require('fast-deep-equal/es6');
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];
@@ -33,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',
'heat', 'cool', 'water', 'meter',
];
function capitalize(s) {
@@ -69,6 +73,14 @@ async function getZigbee2mqttVersion() {
});
}
async function getDependencyVersion(depend) {
return new Promise((resolve, reject) => {
const packageJSON = require('../../node_modules/'+depend+'/package.json');
const version = packageJSON.version;
resolve({version});
});
}
function formatDate(date, type) {
let result;
@@ -134,17 +146,40 @@ 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;
for (const moduleName of externalConverters) {
let converterModule = moduleName;
let converter;
if (moduleName.endsWith('.js')) {
converterModule = data.joinPath(moduleName.split('.')[0]);
converter = loadModuleFromFile(data.joinPath(moduleName));
} else {
converter = require(moduleName);
}
const converter = require(converterModule);
if (Array.isArray(converter)) {
for (const item of converter) {
yield item;
@@ -157,6 +192,7 @@ function* getExternalConvertersDefinitions(settings) {
function toSnakeCase(value) {
if (typeof value === 'object') {
value = {...value};
for (const key of Object.keys(value)) {
const keySnakeCase = toSnakeCase(key);
if (key !== keySnakeCase) {
@@ -170,22 +206,41 @@ function toSnakeCase(value) {
}
}
function validateFriendlyName(name) {
function validateFriendlyName(name, throwFirstError=false) {
const errors = [];
for (const endpointName of endpointNames) {
if (name.toLowerCase().endsWith('/' + endpointName)) {
errors.push(`friendly_name is not allowed to end with: '/${endpointName}'`);
}
}
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}'`);
if (name.match(/.*\/\d*$/)) errors.push(`Friendly name cannot end with a "/DIGIT" ('${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));
}
module.exports = {
millisecondsToSeconds: (milliseconds) => milliseconds / 1000,
secondsToMilliseconds: (seconds) => seconds * 1000,
getZigbee2mqttVersion,
getDependencyVersion,
objectHasProperties,
toSnakeCase,
sleep,
getObjectProperty,
getEndpointNames: () => endpointNames,
isXiaomiDevice: (device) => {
@@ -200,4 +255,6 @@ module.exports = {
parseJSON,
getExternalConvertersDefinitions,
validateFriendlyName,
loadModuleFromFile,
loadModuleFromText,
};
+2 -2
View File
@@ -14,8 +14,8 @@ function read(file) {
}
}
function readIfExists(file) {
return fs.existsSync(file) ? read(file) : null;
function readIfExists(file, default_=null) {
return fs.existsSync(file) ? read(file) : default_;
}
function writeIfChanged(file, content) {
+23 -6
View File
@@ -19,7 +19,8 @@ class Zigbee extends events.EventEmitter {
}
async start() {
logger.info(`Starting zigbee-herdsman...`);
const infoHerdsman = await utils.getDependencyVersion('zigbee-herdsman');
logger.info(`Starting zigbee-herdsman (${infoHerdsman.version})`);
const herdsmanSettings = {
network: {
panID: settings.get().advanced.pan_id,
@@ -38,6 +39,7 @@ class Zigbee extends events.EventEmitter {
},
adapter: {
concurrent: settings.get().advanced.adapter_concurrent,
delay: settings.get().advanced.adapter_delay,
},
};
@@ -51,9 +53,15 @@ class Zigbee extends events.EventEmitter {
herdsmanSettings.network.networkKey = newKey;
}
if (herdsmanSettings.network.panID === 'GENERATE') {
const newPanID = Math.floor(Math.random() * (0xFFFF - 2)) + 1;
settings.set(['advanced', 'pan_id'], newPanID);
herdsmanSettings.network.panID = newPanID;
}
try {
herdsmanSettings.acceptJoiningDeviceHandler = this.acceptJoiningDeviceHandler;
this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings);
this.herdsman = new ZigbeeHerdsman.Controller(herdsmanSettings, logger);
await this.herdsman.start();
} catch (error) {
logger.error(`Error while starting zigbee-herdsman`);
@@ -66,6 +74,7 @@ class Zigbee extends events.EventEmitter {
this.herdsman.on('deviceJoined', (data) => this.emit('event', 'deviceJoined', data));
this.herdsman.on('deviceLeave', (data) => this.emit('event', 'deviceLeave', data));
this.herdsman.on('message', (data) => this.emit('event', 'message', data));
this.herdsman.on('permitJoinChanged', (data) => this.emit('permitJoinChanged', data));
logger.info('zigbee-herdsman started');
logger.info(`Coordinator firmware version: '${stringify(await this.getCoordinatorVersion())}'`);
@@ -103,6 +112,10 @@ class Zigbee extends events.EventEmitter {
return this.herdsman.getCoordinatorVersion();
}
isStopping() {
return this.herdsman.isStopping();
}
async getNetworkParameters() {
return this.herdsman.getNetworkParameters();
}
@@ -116,22 +129,26 @@ class Zigbee extends events.EventEmitter {
logger.info('zigbee-herdsman stopped');
}
async permitJoin(permit, resolvedEntity) {
async permitJoin(permit, resolvedEntity, time=undefined) {
permit ?
logger.info(`Zigbee: allowing new devices to join${resolvedEntity ? ` via ${resolvedEntity.name}` : ''}.`) :
logger.info('Zigbee: disabling joining new devices.');
if (resolvedEntity && permit) {
await this.herdsman.permitJoin(permit, resolvedEntity.device);
await this.herdsman.permitJoin(permit, resolvedEntity.device, time);
} else {
await this.herdsman.permitJoin(permit);
await this.herdsman.permitJoin(permit, undefined, time);
}
}
async getPermitJoin() {
getPermitJoin() {
return this.herdsman.getPermitJoin();
}
getPermitJoinTimeout() {
return this.herdsman.getPermitJoinTimeout();
}
getClients() {
return this.herdsman.getDevices().filter((device) => device.type !== 'Coordinator');
}
+11630 -3212
View File
File diff suppressed because it is too large Load Diff
+11 -8
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "1.16.0",
"version": "1.18.2",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"repository": {
@@ -8,7 +8,7 @@
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
},
"engines": {
"node": "^10 || ^12 || ^14"
"node": "^10 || ^12 || ^14 || ^15"
},
"keywords": [
"xiaomi",
@@ -35,16 +35,16 @@
"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",
"http-proxy": "^1.18.1",
"humanize-duration": "^3.23.1",
"js-yaml": "^3.14.0",
"json-stable-stringify-without-jsonify": "=1.0.1",
"mkdir-recursive": "^0.4.0",
"moment": "^2.27.0",
"mqtt": "^4.2.1",
"mqtt": "4.2.1",
"object-assign-deep": "^0.4.0",
"rimraf": "^3.0.2",
"semver": "^7.3.2",
@@ -52,9 +52,9 @@
"winston": "^3.3.3",
"winston-syslog": "^2.4.4",
"ws": "^7.3.1",
"zigbee-herdsman": "0.13.26",
"zigbee-herdsman-converters": "12.0.224",
"zigbee2mqtt-frontend": "0.2.68"
"zigbee-herdsman": "0.13.88",
"zigbee-herdsman-converters": "14.0.102",
"zigbee2mqtt-frontend": "0.3.114"
},
"devDependencies": {
"eslint": "*",
@@ -73,7 +73,10 @@
}
},
"collectCoverageFrom": [
"lib/**"
"lib/**/*.js"
]
},
"bin": {
"zigbee2mqtt": "cli.js"
}
}
+89
View File
@@ -0,0 +1,89 @@
const {ZnpCommandStatus, NvSystemIds} = require('zigbee-herdsman/dist/adapter/z-stack/constants/common');
const {ZnpVersion} = require('zigbee-herdsman/dist/adapter/z-stack/adapter/tstype');
const {Subsystem} = require('zigbee-herdsman/dist/adapter/z-stack/unpi/constants');
const {Znp} = require('zigbee-herdsman/dist/adapter/z-stack/znp');
class ZStackNvMemEraser {
constructor(device) {
this.znp = new Znp(device, 115200, false);
}
async start() {
await this.znp.open();
const attempts = 3;
for (let i = 0; i < attempts; i++) {
try {
await this.znp.request(Subsystem.SYS, 'ping', {capabilities: 1});
break;
} catch (e) {
if (attempts - 1 === i) {
throw new Error(`Failed to connect to the adapter (${e})`);
}
}
}
// Old firmware did not support version, assume it's Z-Stack 1.2 for now.
try {
this.version = (await this.znp.request(Subsystem.SYS, 'version', {})).payload;
} catch (e) {
console.log(`Failed to get zStack version, assuming 1.2`);
this.version = {'transportrev': 2, 'product': 0, 'majorrel': 2,
'minorrel': 0, 'maintrel': 0, 'revision': ''};
}
console.log(`Detected znp version '${ZnpVersion[this.version.product]}' (${JSON.stringify(this.version)})`);
await this.clearAllNvMemItems();
process.exit(0);
}
async clearAllNvMemItems() {
let maxNvMemId;
switch (this.version.product) {
case ZnpVersion.zStack12: maxNvMemId = 0x0302; break;
case ZnpVersion.zStack30x: maxNvMemId = 0x033F; break;
case ZnpVersion.zStack3x0: maxNvMemId = 0x032F; break;
}
let deletedCount = 0;
console.log(`Clearing all NVMEM items, from 0 to ${maxNvMemId}`);
for (let id=0; id<=maxNvMemId; id++) {
let len;
const needOsal = !(this.version.product == ZnpVersion.zStack3x0 && id <= 7);
if (needOsal) {
const lengthRes = await this.znp.request(Subsystem.SYS, 'osalNvLength', {id: id});
len = lengthRes.payload['length'];
} else {
const lengthRes = await this.znp.request(Subsystem.SYS, 'nvLength',
{sysid: NvSystemIds.ZSTACK, itemid: id, subid: 0});
len = lengthRes.payload['len'];
}
if (len != 0) {
console.log(`NVMEM item #${id} - deleting, size: ${len}`);
if (needOsal) {
await this.znp.request(Subsystem.SYS, 'osalNvDelete',
{id: id, len: len},
null, [ZnpCommandStatus.SUCCESS, ZnpCommandStatus.NV_ITEM_INITIALIZED]);
} else {
await this.znp.request(Subsystem.SYS, 'nvDelete',
{sysid: NvSystemIds.ZSTACK, itemid: id, subid: 0},
null, [ZnpCommandStatus.SUCCESS, ZnpCommandStatus.NV_ITEM_INITIALIZED]);
}
deletedCount++;
}
}
console.log(`Clearing all NVMEM items finished, deleted ${deletedCount} items`);
}
}
const processArgs = process.argv.slice(2);
if (processArgs.length != 1) {
console.log('ZStack NVMEM eraser.');
console.log('Usage:');
console.log(' node zStackEraseAllNvMem.js <SERIAL_PORT>');
process.exit(1);
}
const eraser = new ZStackNvMemEraser(processArgs[0]);
eraser.start();
+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,10 +11,23 @@ const homeassistantSwitch = {
const mockDevices = [{
mock: 1,
model: 'external_converters_device',
model: 'external_converters_device_1',
homeassistant: [homeassistantSwitch],
zigbeeModel: ['external_converter_device_1'],
vendor: 'external_1',
description: 'external_1',
fromZigbee: [],
toZigbee: [],
exposes: [],
}, {
mock: 2
mock: 2,
model: 'external_converters_device_2',
zigbeeModel: ['external_converter_device_2'],
vendor: 'external_2',
description: 'external_2',
fromZigbee: [],
toZigbee: [],
exposes: [],
}];
module.exports = mockDevices;
+8 -1
View File
@@ -1,5 +1,12 @@
const mockDevice = {
mock: true
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
model: 'external_converter_device',
description: 'external',
fromZigbee: [],
toZigbee: [],
exposes: [],
};
module.exports = mockDevice;
+44 -16
View File
@@ -1,5 +1,6 @@
const data = require('./stub/data');
const logger = require('./stub/logger');
const stringify = require('json-stable-stringify-without-jsonify');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b3');
zigbeeHerdsman.returnDevices.push('0x00124b00120144ae');
@@ -24,12 +25,11 @@ describe('Availability', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
jest.useFakeTimers();
settings.set(['advanced', 'availability_timeout'], 10);
settings.set(['experimental', 'new_api'], true);
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
mocksClear.forEach((m) => m.mockClear());
await controller.start();
await flushPromises();
@@ -172,16 +172,14 @@ 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 () => {
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.bulb_color;
const device = zigbeeHerdsman.devices.E11_G13;
const endpoint = device.getEndpoint(1);
endpoint.read.mockClear();
await zigbeeHerdsman.events.deviceJoined({device});
@@ -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);
@@ -343,7 +341,7 @@ describe('Availability', () => {
});
it('Should publish availability when end device joins', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const payload = {device};
MQTT.publish.mockClear();
await zigbeeHerdsman.events.deviceJoined(payload);
@@ -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();
@@ -409,4 +407,34 @@ describe('Availability', () => {
{retain: true, qos: 0}, expect.any(Function)
);
});
it('Should clear retained availability topic when device is renamed', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "bulb_color", "to": "bulb_color_new_name"}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color/availability',
null,
{retain: true, qos: 0}, expect.any(Function)
);
});
it('Should clear retained availability topic when device does not exist', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/not_existing_hahaha/availability', 'offline');
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/not_existing_hahaha/availability', null, {retain: true, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/0x000b57fffec6a5b3/availability', 'offline');
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/0x000b57fffec6a5b3/availability', null, {retain: true, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bulb_color/availability', 'offline');
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(0);
});
});
+125 -20
View File
@@ -6,6 +6,8 @@ const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const flushPromises = () => new Promise(setImmediate);
const stringify = require('json-stable-stringify-without-jsonify');
jest.mock('debounce', () => jest.fn(fn => fn));
const debounce = require('debounce');
describe('Bind', () => {
let controller;
@@ -23,37 +25,49 @@ describe('Bind', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
settings.set(['experimental', 'new_api'], true);
controller = new Controller();
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(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
this.coordinatorEndoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
MQTT.publish.mockClear();
});
it('Should subscribe to topics', async () => {
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/bind');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/unbind');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/bind/#');
});
it('Should bind', async () => {
it('Should bind to device and configure reporting', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
// Setup
const originalDeviceOutputClusters = device.getEndpoint(1).outputClusters;
device.getEndpoint(1).outputClusters = [...device.getEndpoint(1).outputClusters, 768];
const originalTargetBinds = target.binds;
target.binds = [{cluster: {name: 'genLevelCtrl'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)}];
target.getClusterAttributeValue.mockImplementationOnce((cluster, value) => undefined);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'}));
target.configureReporting.mockImplementationOnce(() => {throw new Error("timeout")});
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({transaction: "1234", from: 'remote', to: 'bulb_color'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(target.read).toHaveBeenCalledWith('lightingColorCtrl', [ 'colorCapabilities' ]);
expect(endpoint.bind).toHaveBeenCalledTimes(4);
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(5);
expect(endpoint.bind).toHaveBeenCalledWith("lightingColorCtrl", target);
expect(target.configureReporting).toHaveBeenCalledTimes(3);
expect(target.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 3600, "minimumReportInterval": 0, "reportableChange": 0}]);
expect(target.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 3600, "minimumReportInterval": 5, "reportableChange": 1}]);
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"],"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)
);
@@ -63,6 +77,10 @@ describe('Bind', () => {
{ retain: true, qos: 0 },
expect.any(Function)
);
// Teardown
target.binds = originalTargetBinds;
device.getEndpoint(1).outputClusters = originalDeviceOutputClusters;
});
it('Should bind only specifief clusters', async () => {
@@ -99,19 +117,44 @@ describe('Bind', () => {
it('Should unbind', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
// setup
target.configureReporting.mockImplementationOnce(() => {throw new Error("timeout")});
const originalRemoteBinds = device.getEndpoint(1).binds;
device.getEndpoint(1).binds = [];
const originalTargetBinds = target.binds;
target.binds = [
{cluster: {name: 'genOnOff'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)},
{cluster: {name: 'genLevelCtrl'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)},
{cluster: {name: 'lightingColorCtrl'}, target: zigbeeHerdsman.devices.coordinator.getEndpoint(1)}
];
const endpoint = device.getEndpoint(1);
mockClear(device);
delete zigbeeHerdsman.devices.bulb_color.meta.configured;
expect(zigbeeHerdsman.devices.bulb_color.meta.configured).toBe(undefined);
MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: 'bulb_color'}));
await flushPromises();
expect(endpoint.unbind).toHaveBeenCalledTimes(3);
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
// Disable reporting
expect(target.configureReporting).toHaveBeenCalledTimes(3);
expect(target.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 0, "reportableChange": 0}]);
expect(target.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 5, "reportableChange": 1}]);
expect(target.configureReporting).toHaveBeenCalledWith("lightingColorCtrl",[{"attribute":"colorTemperature","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1},{"attribute":"currentX","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1},{"attribute":"currentY","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1}]);
expect(zigbeeHerdsman.devices.bulb_color.meta.configured).toBe(2);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
// Teardown
target.binds = originalTargetBinds;
device.getEndpoint(1).binds = originalRemoteBinds;
});
it('Should unbind coordinator', async () => {
@@ -136,7 +179,10 @@ describe('Bind', () => {
it('Should bind to groups', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.groups.group_1;
const target1Member = zigbeeHerdsman.devices.bulb.getEndpoint(1);
const endpoint = device.getEndpoint(1);
target.members.push(target1Member);
target1Member.configureReporting.mockClear();
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'group_1'}));
await flushPromises();
@@ -144,11 +190,43 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(target1Member.configureReporting).toHaveBeenCalledTimes(2);
expect(target1Member.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 3600, "minimumReportInterval": 0, "reportableChange": 0}]);
expect(target1Member.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 3600, "minimumReportInterval": 5, "reportableChange": 1}]);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
// Should configure reproting for device added to group
target1Member.configureReporting.mockClear();
await MQTT.events.message('zigbee2mqtt/bridge/group/group_1/add', 'bulb');
await flushPromises();
expect(target1Member.configureReporting).toHaveBeenCalledTimes(2);
expect(target1Member.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 3600, "minimumReportInterval": 0, "reportableChange": 0}]);
expect(target1Member.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 3600, "minimumReportInterval": 5, "reportableChange": 1}]);
});
it('Should unbind from group', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.groups.group_1;
const target1Member = zigbeeHerdsman.devices.bulb.getEndpoint(1);
const endpoint = device.getEndpoint(1);
target.members.push(target1Member);
target1Member.configureReporting.mockClear();
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: 'group_1'}));
await flushPromises();
expect(endpoint.unbind).toHaveBeenCalledTimes(3);
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Should bind to group by number', async () => {
@@ -274,7 +352,6 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_bind', message: {from: 'remote', to: 'bulb_color', cluster: 'genScenes'}});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log');
@@ -305,7 +382,6 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'bulb_color', cluster: 'genScenes'}});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log');
@@ -326,7 +402,6 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'Coordinator', cluster: 'genScenes'}});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log');
@@ -346,7 +421,6 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_bind', message: {from: 'remote', to: 'group_1', cluster: 'genScenes'}});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log');
@@ -366,7 +440,6 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_bind', message: {from: 'remote', to: 'group_1', cluster: 'genScenes'}});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log');
@@ -420,7 +493,6 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", 901);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", 901);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", 901);
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'default_bind_group', cluster: 'genScenes'}});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/log');
@@ -428,4 +500,37 @@ describe('Bind', () => {
expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('zigbee2mqtt/bridge/log');
expect(JSON.parse(MQTT.publish.mock.calls[2][1])).toStrictEqual({type: 'device_unbind', message: {from: 'remote', to: 'default_bind_group', cluster: 'genLevelCtrl'}});
});
it('Should poll bounded Hue bulb when receiving message from Hue dimmer', async () => {
const remote = zigbeeHerdsman.devices.remote;
const data = {"button":3,"unknown1":3145728,"type":2,"unknown2":0,"time":1};
const payload = {data, cluster: 'manuSpecificPhilips', device: remote, endpoint: remote.getEndpoint(2), type: 'commandHueNotification', linkquality: 10, groupID: 0};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(debounce).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.devices.bulb_color.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]);
});
it('Should poll grouped Hue bulb when receiving message from TRADFRI remote', async () => {
zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read.mockClear();
zigbeeHerdsman.devices.bulb_2.getEndpoint(1).read.mockClear();
const remote = zigbeeHerdsman.devices.tradfri_remote;
const data = {"stepmode":0,"stepsize":43,"transtime":5};
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(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(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);
});
});
+242 -14
View File
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -44,9 +44,9 @@ 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();
@@ -137,11 +137,11 @@ describe('Configure', () => {
});
it('Fail to configure via MQTT when device has no configure', async () => {
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "bulb", transaction: 20}));
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "bulb_enddevice", transaction: 20}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
stringify({"data":{"id": "bulb"},"status":"error","error": "Device 'bulb' cannot be configured","transaction":20}),
stringify({"data":{"id": "bulb_enddevice"},"status":"error","error": "Device 'bulb_enddevice' cannot be configured","transaction":20}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -161,9 +161,9 @@ describe('Configure', () => {
});
it('Legacy api: Should skip reconfigure when device does not require this', async () => {
await MQTT.events.message('zigbee2mqtt/bridge/configure', 'bulb');
await MQTT.events.message('zigbee2mqtt/bridge/configure', '0x0017880104e45553');
await flushPromises();
expect(logger.warn).toHaveBeenCalledWith(`Skipping configure of 'bulb', device does not require this.`)
expect(logger.warn).toHaveBeenCalledWith(`Skipping configure of 'bulb_enddevice', device does not require this.`)
});
it('Should not configure when interviewing', async () => {
+42 -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,24 +18,26 @@ 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();
});
it('Start controller', async () => {
await controller.start();
expect(zigbeeHerdsman.constructor).toHaveBeenCalledWith({"network":{"panID":6754,"extendedPanID":[221,221,221,221,221,221,221,221],"channelList":[11],"networkKey":[1,3,5,7,9,11,13,15,0,2,4,6,8,10,12,13]},"databasePath":path.join(data.mockDir, "database.db"), "databaseBackupPath":path.join(data.mockDir, "database.db.backup"),"backupPath":path.join(data.mockDir, "coordinator_backup.json"),"acceptJoiningDeviceHandler": expect.any(Function),adapter: {concurrent: null}, "serialPort":{"baudRate":undefined,"rtscts":undefined,"path":"/dev/dummy"}});
expect(zigbeeHerdsman.constructor).toHaveBeenCalledWith({"network":{"panID":6754,"extendedPanID":[221,221,221,221,221,221,221,221],"channelList":[11],"networkKey":[1,3,5,7,9,11,13,15,0,2,4,6,8,10,12,13]},"databasePath":path.join(data.mockDir, "database.db"), "databaseBackupPath":path.join(data.mockDir, "database.db.backup"),"backupPath":path.join(data.mockDir, "coordinator_backup.json"),"acceptJoiningDeviceHandler": expect.any(Function),adapter: {concurrent: null, delay: null}, "serialPort":{"baudRate":undefined,"rtscts":undefined,"path":"/dev/dummy"}}, logger);
expect(zigbeeHerdsman.start).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.setLED).toHaveBeenCalledTimes(0);
expect(zigbeeHerdsman.setTransmitPower).toHaveBeenCalledTimes(0);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined);
expect(logger.info).toHaveBeenCalledWith(`Currently ${Object.values(zigbeeHerdsman.devices).length - 1} devices are joined:`)
expect(logger.info).toHaveBeenCalledWith('bulb (0x000b57fffec6a5b2): LED1545G12 - IKEA TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (Router)');
expect(logger.info).toHaveBeenCalledWith('remote (0x0017880104e45517): 324131092621 - Philips Hue dimmer switch (EndDevice)');
@@ -93,12 +94,15 @@ describe('Controller', () => {
expect(MQTT.connect).toHaveBeenCalledWith("mqtt://localhost", expected);
});
it('Should generate network_key when set to GENERATE', async () => {
it('Should generate network_key and pan_id when set to GENERATE', async () => {
settings.set(['advanced', 'network_key'], 'GENERATE');
settings.set(['advanced', 'pan_id'], 'GENERATE');
await controller.start();
await flushPromises();
expect(zigbeeHerdsman.constructor.mock.calls[0][0].network.networkKey.length).toStrictEqual(16);
expect(zigbeeHerdsman.constructor.mock.calls[0][0].network.panID).toStrictEqual(expect.any(Number));
expect(data.read().advanced.network_key.length).toStrictEqual(16);
expect(data.read().advanced.pan_id).toStrictEqual(expect.any(Number));
});
it('Start controller should publish cached states', async () => {
@@ -202,7 +206,7 @@ describe('Controller', () => {
settings.set(['permit_join'], false);
await controller.start();
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined);
});
it('Start controller with disable_led', async () => {
@@ -225,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 () => {
@@ -235,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 () => {
@@ -246,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 () => {
@@ -256,6 +260,17 @@ describe('Controller', () => {
expect(logger.debug).toHaveBeenCalledWith("Received MQTT message on 'dummytopic' with data 'dummymessage'")
});
it('Skip MQTT messages on topic we published to', async () => {
await controller.start();
logger.debug.mockClear();
await MQTT.events.message('zigbee2mqtt/skip-this-topic', 'skipped');
expect(logger.debug).toHaveBeenCalledWith("Received MQTT message on 'zigbee2mqtt/skip-this-topic' with data 'skipped'")
logger.debug.mockClear();
await controller.mqtt.publish('skip-this-topic', '', {});
await MQTT.events.message('zigbee2mqtt/skip-this-topic', 'skipped');
expect(logger.debug).toHaveBeenCalledTimes(0);
});
it('On zigbee event message', async () => {
await controller.start();
const device = zigbeeHerdsman.devices.bulb;
@@ -588,14 +603,24 @@ 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();
it('Start controller with force_disable_retain', async () => {
settings.set(['mqtt', 'force_disable_retain'], true);
await controller.start();
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', { retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.connect).toHaveBeenCalledTimes(1);
const expected = {
"will": { "payload": "offline", "retain": false, "topic": "zigbee2mqtt/bridge/state" },
}
expect(MQTT.connect).toHaveBeenCalledWith("mqtt://localhost", expected);
});
it('Should prevent any message being published with retain flag when force_disable_retain is set', async () => {
settings.set(['mqtt', 'force_disable_retain'], true);
await controller.mqtt.connect()
MQTT.publish.mockClear();
await controller.mqtt.publish('fo', 'bar', { retain: true })
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/fo', 'bar', { retain: false, qos: 0 }, expect.any(Function));
});
});
+29 -9
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,12 +62,19 @@ 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);
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledWith({
mock: true
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
model: 'external_converter_device',
description: 'external',
fromZigbee: [],
toZigbee: [],
exposes: []
});
});
@@ -75,22 +82,35 @@ 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);
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(1, {
mock: 1,
model: 'external_converters_device',
model: 'external_converters_device_1',
zigbeeModel: ['external_converter_device_1'],
vendor: 'external_1',
description: 'external_1',
fromZigbee: [],
toZigbee: [],
exposes: []
});
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(2, {
mock: 2
mock: 2,
model: 'external_converters_device_2',
zigbeeModel: ['external_converter_device_2'],
vendor: 'external_2',
description: 'external_2',
fromZigbee: [],
toZigbee: [],
exposes: []
});
});
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);
@@ -101,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)
);
});
});
+50 -43
View File
@@ -6,6 +6,7 @@ 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 zigbeeHerdsman = require('./stub/zigbeeHerdsman');
jest.spyOn(process, 'exit').mockImplementation(() => {});
const mockHTTP = {
@@ -18,20 +19,17 @@ const mockHTTP = {
events: {},
};
const mockHTTPProxy = {
implementation: {
web: jest.fn(),
ws: jest.fn(),
},
variables: {},
events: {},
const mockWSocket = {
close: jest.fn(),
};
const mockWS = {
implementation: {
clients: [],
on: (event, handler) => {mockWS.events[event] = handler},
handleUpgrade: jest.fn(),
handleUpgrade: jest.fn().mockImplementation((request, socket, head, cb) => {
cb(mockWSocket)
}),
emit: jest.fn(),
},
variables: {},
@@ -51,13 +49,6 @@ jest.mock('http', () => ({
}),
}));
jest.mock('http-proxy', () => ({
createProxyServer: jest.fn().mockImplementation((initParameter) => {
mockHTTPProxy.variables.initParameter = initParameter;
return mockHTTPProxy.implementation;
}),
}));
jest.mock("serve-static", () =>
jest.fn().mockImplementation((path) => {
mockNodeStatic.variables.path = path
@@ -83,21 +74,26 @@ describe('Frontend', () => {
mockWS.implementation.clients = [];
data.writeDefaultConfiguration();
data.writeDefaultState();
settings._reRead();
settings.set(['experimental'], {new_api: true});
settings.set(['frontend'], {port: 8081});
settings.reRead();
settings.set(['frontend'], {port: 8081, host: "127.0.0.1"});
settings.set(['homeassistant'], true);
zigbeeHerdsman.devices.bulb.linkquality = 10;
});
afterEach(async() => {
delete zigbeeHerdsman.devices.bulb.linkquality;
});
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);
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
const mockWSClient = {
implementation: {
close: jest.fn(),
send: jest.fn(),
},
events: {},
};
@@ -108,7 +104,7 @@ describe('Frontend', () => {
});
it('Websocket interaction', async () => {
controller = new Controller();
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
// Connect
@@ -122,10 +118,9 @@ describe('Frontend', () => {
};
mockWS.implementation.clients.push(mockWSClient.implementation);
await mockWS.events.connection(mockWSClient.implementation);
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(48);
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();
@@ -160,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,32 +171,44 @@ describe('Frontend', () => {
mockWS.implementation.handleUpgrade.mock.calls[0][3](99);
expect(mockWS.implementation.emit).toHaveBeenCalledWith('connection', 99, {"url": "http://localhost:8080/api"});
mockWS.implementation.handleUpgrade.mockClear();
mockHTTP.events.upgrade({url: 'http://localhost:8080/unkown'}, mockSocket, 3);
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(0);
expect(mockSocket.destroy).toHaveBeenCalledTimes(1);
mockHTTP.variables.onRequest(1, 2);
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
expect(mockNodeStatic.implementation).toHaveBeenCalledWith(1, 2, expect.any(Function));
});
it('Development server', async () => {
settings.set(['frontend'], {development_server: 'localhost:3001'});
controller = new Controller();
it('Static server', async () => {
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
expect(mockHTTPProxy.variables.initParameter).toStrictEqual({ws: true});
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8080);
mockHTTP.variables.onRequest(1, 2);
expect(mockHTTPProxy.implementation.web).toHaveBeenCalledTimes(1);
expect(mockHTTPProxy.implementation.web).toHaveBeenCalledWith(1, 2, {"target": "http://localhost:3001"});
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
});
it('Authentification', async () => {
const authToken = 'sample-secure-token'
settings.set(['frontend'], {auth_token: authToken});
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
const mockSocket = {destroy: jest.fn()};
mockHTTPProxy.implementation.ws.mockClear();
mockHTTP.events.upgrade({url: 'http://localhost:8080/sockjs-node'}, mockSocket, 3);
expect(mockHTTPProxy.implementation.ws).toHaveBeenCalledTimes(1);
mockWS.implementation.handleUpgrade.mockClear();
mockHTTP.events.upgrade({url: '/api'}, mockSocket, mockWSocket);
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(1);
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
expect(mockHTTPProxy.implementation.ws).toHaveBeenCalledWith({"url": "http://localhost:8080/sockjs-node"}, mockSocket, 3, {"target": "ws://localhost:3001"});
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledWith({"url": "/api"}, mockSocket, mockWSocket, expect.any(Function));
expect(mockWSocket.close).toHaveBeenCalledWith(4401, "Unauthorized");
mockWSocket.close.mockClear();
mockWS.implementation.emit.mockClear();
const url = `/api?token=${authToken}`;
mockWS.implementation.handleUpgrade.mockClear();
mockHTTP.events.upgrade({url: url}, mockSocket, 3);
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(1);
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledWith({url}, mockSocket, 3, expect.any(Function));
expect(mockWSocket.close).toHaveBeenCalledTimes(0);
mockWS.implementation.handleUpgrade.mock.calls[0][3](mockWSocket);
expect(mockWS.implementation.emit).toHaveBeenCalledWith('connection', mockWSocket, {url});
});
});
+75 -11
View File
@@ -20,11 +20,10 @@ describe('Groups', () => {
beforeEach(() => {
data.writeEmptyState();
settings.set(['experimental', 'new_api'], true);
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();
zigbeeHerdsmanConverters.toZigbeeConverters.__clearStore__();
})
@@ -99,6 +98,17 @@ describe('Groups', () => {
expect(logger.error).toHaveBeenCalledWith("Cannot find 'not_existing_bla' of group 'group_1'");
});
it('Should resolve device friendly names', async () => {
settings.set(['devices', zigbeeHerdsman.devices.bulb.ieeeAddr, 'friendly_name'], 'bulb_friendly_name');
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: ['bulb_friendly_name', 'bulb_color']}});
await controller.start();
await flushPromises();
expect(zigbeeHerdsman.groups.group_1.members).toStrictEqual([
zigbeeHerdsman.devices.bulb.getEndpoint(1),
zigbeeHerdsman.devices.bulb_color.getEndpoint(1)
]);
});
it('Legacy api: Add to group via MQTT', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
@@ -127,12 +137,6 @@ describe('Groups', () => {
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group/with/slashes').devices).toStrictEqual([`${device.ieeeAddr}/1`]);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group/with/slashes"}}), {"retain": false, qos: 0}, expect.any(Function));
// Test if subscribed to topics with slashes
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/group/+/remove');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/group/+/+/remove');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/group/+/+/+/+/+/remove');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/group/+/+/+/+/+/add');
});
it('Legacy api: Add to group via MQTT with postfix', async () => {
@@ -335,6 +339,38 @@ describe('Groups', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should publish state change for group when members state change', async () => {
// Created for https://github.com/Koenkk/zigbee2mqtt/issues/5725
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [device.ieeeAddr]}});
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'OFF'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
});
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);
@@ -469,6 +505,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;
@@ -505,14 +569,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)
);
});
+215 -66
View File
@@ -10,31 +10,20 @@ const fs = require('fs');
const path = require('path');
const HomeAssistant = require('../lib/extension/homeassistant');
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
describe('HomeAssistant extension', () => {
beforeEach(async () => {
this.version = await require('../lib/util/utils').getZigbee2mqttVersion();
this.version = `Zigbee2MQTT ${this.version.version}`;
jest.useRealTimers();
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
MQTT.publish.mockClear();
settings.set(['homeassistant'], true);
});
it('Should have mapping for all devices supported by zigbee-herdsman-converters', () => {
const missing = [];
const ha = new HomeAssistant(null, null, null, null, {on: () => {}});
require('zigbee-herdsman-converters').definitions.forEach((d) => {
if (!d.hasOwnProperty('exposes') && !ha._getMapping()[d.model]) {
missing.push(d.model);
}
});
expect(missing).toHaveLength(0);
});
it('Should not have duplicate type/object_ids in a mapping', () => {
const duplicated = [];
const ha = new HomeAssistant(null, null, null, null, {on: () => {}});
@@ -77,7 +66,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -102,7 +91,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -127,7 +116,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -152,7 +141,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -177,7 +166,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -186,6 +175,116 @@ describe('HomeAssistant extension', () => {
{ retain: true, qos: 0 },
expect.any(Function),
);
payload = {
"availability":[
{
"topic":"zigbee2mqtt/bridge/state"
}
],
"command_topic":"zigbee2mqtt/wall_switch_double/left/set",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017880104e45542"
],
"manufacturer":"Xiaomi",
"model":"Aqara double key wired wall switch without neutral wire. Doesn't work as a router and doesn't support power meter (QBKG03LM)",
"name":"wall_switch_double",
"sw_version":this.version
},
"json_attributes_topic":"zigbee2mqtt/wall_switch_double",
"name":"wall_switch_double_left",
"payload_off":"OFF",
"payload_on":"ON",
"state_topic":"zigbee2mqtt/wall_switch_double",
"unique_id":"0x0017880104e45542_switch_left_zigbee2mqtt",
"value_template":"{{ value_json.state_left }}"
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/switch/0x0017880104e45542/switch_left/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
payload = {
"availability":[
{
"topic":"zigbee2mqtt/bridge/state"
}
],
"command_topic":"zigbee2mqtt/wall_switch_double/right/set",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017880104e45542"
],
"manufacturer":"Xiaomi",
"model":"Aqara double key wired wall switch without neutral wire. Doesn't work as a router and doesn't support power meter (QBKG03LM)",
"name":"wall_switch_double",
"sw_version":this.version
},
"json_attributes_topic":"zigbee2mqtt/wall_switch_double",
"name":"wall_switch_double_right",
"payload_off":"OFF",
"payload_on":"ON",
"state_topic":"zigbee2mqtt/wall_switch_double",
"unique_id":"0x0017880104e45542_switch_right_zigbee2mqtt",
"value_template":"{{ value_json.state_right }}"
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/switch/0x0017880104e45542/switch_right/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
payload = {
"availability":[
{
"topic":"zigbee2mqtt/bridge/state"
}
],
"brightness":true,
"brightness_scale":254,
"color_temp":true,
"min_mireds": 250,
"max_mireds": 454,
"command_topic":"zigbee2mqtt/bulb/set",
"device":{
"identifiers":[
"zigbee2mqtt_0x000b57fffec6a5b2"
],
"manufacturer":"IKEA",
"model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)",
"name":"bulb",
"sw_version":this.version,
},
"effect":true,
"effect_list":[
"blink",
"breathe",
"okay",
"channel_change",
"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(
'homeassistant/light/0x000b57fffec6a5b2/light/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Should discover devices with precision', async () => {
@@ -218,7 +317,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -243,7 +342,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -268,7 +367,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -324,7 +423,7 @@ describe('HomeAssistant extension', () => {
'model': 'custom model',
'manufacturer': 'From Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'expire_after': 90,
'icon': 'mdi:test',
};
@@ -350,7 +449,7 @@ describe('HomeAssistant extension', () => {
'model': 'custom model',
'manufacturer': 'Not from Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'expire_after': 30,
'icon': 'mdi:test',
};
@@ -386,7 +485,7 @@ describe('HomeAssistant extension', () => {
await flushPromises();
payload = {
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/my_switch/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
"command_topic": "zigbee2mqtt/my_switch/set",
"device": {
"identifiers": [
@@ -432,6 +531,24 @@ describe('HomeAssistant extension', () => {
expect(topics).not.toContain('homeassistant/sensor/0x0017880104e45522/temperature/config')
});
it('Shouldnt discover sensor when set to null', async () => {
logger.error.mockClear();
settings.set(['devices', '0x0017880104e45522'], {
homeassistant: {humidity: null},
friendly_name: 'weather_sensor',
retain: false,
})
controller = new Controller(false);
await controller.start();
await flushPromises();
const topics = MQTT.publish.mock.calls.map((c) => c[0]);
expect(topics).not.toContain('homeassistant/sensor/0x0017880104e45522/humidity/config')
expect(topics).toContain('homeassistant/sensor/0x0017880104e45522/temperature/config')
});
it('Should discover devices with fan', async () => {
controller = new Controller(false);
await controller.start();
@@ -456,7 +573,7 @@ describe('HomeAssistant extension', () => {
"smart"
],
"json_attributes_topic":"zigbee2mqtt/fan",
"name":"fan_fan",
"name":"fan",
"unique_id":"0x0017880104e45548_fan_zigbee2mqtt",
"device":{
"identifiers":[
@@ -467,7 +584,7 @@ describe('HomeAssistant extension', () => {
"model":"Universal wink enabled white ceiling fan premier remote control (99432)",
"manufacturer":"Hampton Bay"
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/fan/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -488,7 +605,7 @@ describe('HomeAssistant extension', () => {
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'}, {topic: 'zigbee2mqtt/TS0601_thermostat/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
"away_mode_command_topic":"zigbee2mqtt/TS0601_thermostat/set/away_mode",
"away_mode_state_template":"{{ value_json.away_mode }}",
"away_mode_state_topic":"zigbee2mqtt/TS0601_thermostat",
@@ -515,15 +632,15 @@ describe('HomeAssistant extension', () => {
"hold_state_template":"{{ value_json.preset }}",
"hold_state_topic":"zigbee2mqtt/TS0601_thermostat",
"json_attributes_topic":"zigbee2mqtt/TS0601_thermostat",
"max_temp":"30",
"max_temp":"35",
"min_temp":"5",
"mode_command_topic":"zigbee2mqtt/TS0601_thermostat/set/system_mode",
"mode_state_template":"{{ value_json.system_mode }}",
"mode_state_topic":"zigbee2mqtt/TS0601_thermostat",
"modes":[
"auto"
"heat", "auto", "off"
],
"name":"TS0601_thermostat_climate",
"name":"TS0601_thermostat",
"temp_step":0.5,
"temperature_command_topic":"zigbee2mqtt/TS0601_thermostat/set/current_heating_setpoint",
"temperature_state_template":"{{ value_json.current_heating_setpoint }}",
@@ -552,9 +669,9 @@ 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 cover',
name: 'smart vent',
unique_id: '0x0017880104e45551_cover_zigbee2mqtt',
device:
{
@@ -564,7 +681,7 @@ describe('HomeAssistant extension', () => {
model: 'Smart vent (SV01)',
manufacturer: 'Keen Home'
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/smart vent/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -598,7 +715,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -663,7 +780,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -675,7 +792,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 = {};
@@ -684,14 +801,12 @@ describe('HomeAssistant extension', () => {
MQTT.publish.mockClear();
await zigbeeHerdsman.events.deviceLeave(payload);
await flushPromises();
// 1 publish is from device_removed
expect(MQTT.publish).toHaveBeenCalledTimes(1);
});
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();
@@ -702,13 +817,13 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update_available":false}),
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update":{"state":"idle"},"update_available":false}),
{ retain: true, qos: 0 },
expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/remote',
stringify({"brightness":255,"update_available":false}),
stringify({"brightness":255,"update_available":false,"update":{"state":"idle"}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -717,7 +832,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');
@@ -728,13 +843,13 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update_available":false}),
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update_available":false,"update":{"state":"idle"}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/remote',
stringify({"brightness":255,"update_available":false}),
stringify({"brightness":255,"update_available":false,"update":{"state":"idle"}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -743,7 +858,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();
@@ -757,7 +872,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();
@@ -853,7 +968,6 @@ describe('HomeAssistant extension', () => {
});
it('Should refresh discovery when device is renamed', async () => {
settings.set(['experimental', 'new_api'], true);
controller = new Controller(false);
await controller.start();
await flushPromises();
@@ -878,7 +992,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor_renamed/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -919,7 +1033,6 @@ describe('HomeAssistant extension', () => {
});
it('Shouldnt refresh discovery when device is renamed and homeassistant_rename is false', async () => {
settings.set(['experimental', 'new_api'], true);
controller = new Controller(false);
await controller.start();
await flushPromises();
@@ -949,7 +1062,7 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor_renamed/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -970,7 +1083,7 @@ describe('HomeAssistant extension', () => {
"value_template":"{{ value_json.update_available}}",
"state_topic":"zigbee2mqtt/bulb",
"json_attributes_topic":"zigbee2mqtt/bulb",
"name":"bulb_update_available",
"name":"bulb update available",
"unique_id":"0x000b57fffec6a5b2_update_available_zigbee2mqtt",
"device":{
"identifiers":[
@@ -981,7 +1094,7 @@ describe('HomeAssistant extension', () => {
"model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)",
"manufacturer":"IKEA"
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/bulb/availability'}],
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -998,7 +1111,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');
@@ -1145,6 +1258,28 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith('homeassistant/device_automation/0x0017880104e45520/action_double/config', expect.any(String), expect.any(Object), expect.any(Function));
});
it('Should not discover device_automtation when disabled', async () => {
settings.set(['device_options'], {
homeassistant: {device_automation: null},
})
controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.WXKG11LM;
const payload1 = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload1);
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/action_single/config',
expect.any(String),
expect.any(Object),
expect.any(Function),
);
});
it('Should not discover sensor_click when legacy: false is set', async () => {
settings.set(['devices', '0x0017880104e45520'], {
legacy: false,
@@ -1156,7 +1291,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');
@@ -1169,7 +1304,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');
@@ -1271,14 +1406,11 @@ 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'));
const beforeCount = Object.entries((new HomeAssistant(null, null, null, null, {on: () => {}}))._getMapping()).length;
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();
const afterCount = Object.entries(ha._getMapping()).length;
expect(beforeCount + 1).toStrictEqual(afterCount);
const homeassistantSwitch = {
type: 'switch',
@@ -1290,36 +1422,36 @@ describe('HomeAssistant extension', () => {
command_topic: true,
},
};
expect(ha._getMapping()['external_converters_device']).toStrictEqual([homeassistantSwitch]);
expect(ha._getMapping()['external_converters_device_1']).toEqual([homeassistantSwitch]);
});
it('Should clear outdated configs', async () => {
controller = new Controller(false);
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
// Non-existing device -> clear
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/sensor/0x123/temperature/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/0x123/availability'}]}));
await MQTT.events.message('homeassistant/sensor/0x123/temperature/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('homeassistant/sensor/0x123/temperature/config', null, {qos: 0, retain: true}, expect.any(Function));
// Existing device -> don't clear
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/binary_sensor/0x000b57fffec6a5b2/update_available/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/0x000b57fffec6a5b2/availability'}]}));
await MQTT.events.message('homeassistant/binary_sensor/0x000b57fffec6a5b2/update_available/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(0);
// Non-existing device of different instance -> don't clear
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/sensor/0x123/temperature/config', stringify({availability: [{topic: 'zigbee2mqtt_different/bridge/state'}, {topic: 'zigbee2mqtt_different/0x123/availability'}]}));
await MQTT.events.message('homeassistant/sensor/0x123/temperature/config', stringify({availability: [{topic: 'zigbee2mqtt_different/bridge/state'}]}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(0);
// Existing device but non-existing config -> don't clear
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/sensor/0x000b57fffec6a5b2/update_available/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/0x000b57fffec6a5b2/availability'}]}));
await MQTT.events.message('homeassistant/sensor/0x000b57fffec6a5b2/update_available/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('homeassistant/sensor/0x000b57fffec6a5b2/update_available/config', null, {qos: 0, retain: true}, expect.any(Function));
@@ -1341,5 +1473,22 @@ describe('HomeAssistant extension', () => {
await MQTT.events.message('homeassistant/device_automation/0x000b57fffec6a5b2_not_existing/action_button_3_single/config', stringify({topic: 'zigbee2mqtt_different/0x000b57fffec6a5b2_not_existing/availability'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(0);
// Device was flagged to be excluded from homeassistant discovery
await controller.stop();
await flushPromises();
settings.set(['devices', '0x000b57fffec6a5b2', 'homeassistant'], null);
controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/sensor/0x000b57fffec6a5b2/update_available/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('homeassistant/sensor/0x000b57fffec6a5b2/update_available/config', null, {qos: 0, retain: true}, expect.any(Function));
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/device_automation/0x000b57fffec6a5b2/action_button_3_single/config', stringify({topic: 'zigbee2mqtt/0x000b57fffec6a5b2/availability'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('homeassistant/device_automation/0x000b57fffec6a5b2/action_button_3_single/config', null, {qos: 0, retain: true}, expect.any(Function));
});
});
+5 -5
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();
@@ -123,12 +123,12 @@ describe('Bridge legacy', () => {
MQTT.events.message('zigbee2mqtt/bridge/config/permit_join', 'true');
await flushPromises();
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined);
zigbeeHerdsman.permitJoin.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/permit_join', 'false');
await flushPromises();
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false);
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined);
});
it('Should allow to reset', 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');
@@ -1,6 +1,6 @@
const data = require('./stub/data');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const data = require('../stub/data');
const logger = require('../stub/logger');
const zigbeeHerdsman = require('../stub/zigbeeHerdsman');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b3');
zigbeeHerdsman.returnDevices.push('0x00124b00120144ae');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b2');
@@ -8,16 +8,15 @@ zigbeeHerdsman.returnDevices.push('0x0017880104e45553');
zigbeeHerdsman.returnDevices.push('0x0017880104e45559');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b4');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b7');
zigbeeHerdsman.returnDevices.push('0x0017880104e45524');
zigbeeHerdsman.returnDevices.push('0x90fd9ffffe4b64ax');
const MQTT = require('./stub/mqtt');
const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const MQTT = require('../stub/mqtt');
const settings = require('../../lib/util/settings');
const Controller = require('../../lib/controller');
const flushPromises = () => new Promise(setImmediate);
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
jest.mock('debounce', () => jest.fn(fn => fn));
const debounce = require('debounce');
const mocksClear = [MQTT.publish, logger.warn, logger.debug, debounce];
const mocksClear = [MQTT.publish, logger.warn, logger.debug];
describe('Report', () => {
let controller;
@@ -72,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)) {
@@ -80,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();
@@ -98,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);
@@ -111,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);
@@ -195,7 +194,7 @@ describe('Report', () => {
});
it('Should configure reporting when deviceAnnounce message from IKEA device', async () => {
const device = zigbeeHerdsman.devices.bulb;
const device = zigbeeHerdsman.devices.bulb_2;
const endpoint = device.getEndpoint(1);
mockClear(device);
const payload = {device};
@@ -240,36 +239,6 @@ describe('Report', () => {
expect(endpoint.bind).toHaveBeenCalledTimes(1);
});
it('Should poll bounded Hue bulb when receiving message from Hue dimmer', async () => {
const remote = zigbeeHerdsman.devices.remote;
const data = {"button":3,"unknown1":3145728,"type":2,"unknown2":0,"time":1};
const payload = {data, cluster: 'manuSpecificPhilips', device: remote, endpoint: remote.getEndpoint(2), type: 'commandHueNotification', linkquality: 10, groupID: 0};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(debounce).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.devices.bulb_color.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]);
});
it('Should poll grouped Hue bulb when receiving message from TRADFRI remote and should', async () => {
const remote = zigbeeHerdsman.devices.tradfri_remote;
const data = {"stepmode":0,"stepsize":43,"transtime":5};
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(zigbeeHerdsman.devices.bulb_color_2.getEndpoint(1).read).toHaveBeenCalledWith("genLevelCtrl", ["currentLevel"]);
// 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);
// Should only call Hue bulb, not e.g. tradfri
expect(zigbeeHerdsman.devices.bulb.getEndpoint(1).read).toHaveBeenCalledTimes(0);
});
it('Should not configure reporting for the ZNLDP12LM closuresWindowCovering as it is ignored', async () => {
const device = zigbeeHerdsman.devices.ZNLDP12LM;
const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
@@ -287,6 +256,8 @@ describe('Report', () => {
const device = zigbeeHerdsman.devices.bulb;
const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
const endpoint = device.getEndpoint(1);
const configuredReportings = endpoint.configuredReportings;
endpoint.configuredReportings = [];
delete device.meta.reporting;
mockClear(device);
endpoint.getClusterAttributeValue = jest.fn();
@@ -308,5 +279,6 @@ describe('Report', () => {
expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl', ['colorCapabilities'])
expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}]);
expect(endpoint.configureReporting).toHaveBeenCalledTimes(3);
endpoint.configuredReportings = configuredReportings;
});
});
+22 -1
View File
@@ -6,6 +6,7 @@ const path = require('path');
const data = require('./stub/data');
let stdOutWriteOriginal;
const rimraf = require('rimraf');
const Transport = require('winston-transport');
describe('Logger', () => {
beforeEach(async () => {
@@ -13,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 = () => {};
});
@@ -66,6 +67,26 @@ describe('Logger', () => {
expect(logger.getLevel()).toBe('debug');
});
it('Add transport', () => {
class DummyTransport extends Transport {
log(info, callback) {
}
}
const logger = require('../lib/util/logger.js');
expect(logger.transports.length).toBe(2);
logger.addTransport(new DummyTransport());
expect(logger.transports.length).toBe(3);
});
it('Set and get log level warn <-> warning', () => {
const logger = require('../lib/util/logger.js');
logger.logOutput();
logger.setLevel('warn');
expect(logger.transports[0].level).toBe('warning');
expect(logger.getLevel()).toBe('warn');
});
it('Logger should be console and file by default', () => {
const logger = require('../lib/util/logger.js');
logger.logOutput();
+38 -60
View File
@@ -2,33 +2,36 @@ const data = require('./stub/data');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const stringify = require('json-stable-stringify-without-jsonify');
const {coordinator, bulb, bulb_color, WXKG02LM, CC2530_ROUTER, unsupported_router} = zigbeeHerdsman.devices;
const fs = require('fs');
const path = require('path');
const {coordinator, bulb, bulb_color, WXKG02LM_rev1, CC2530_ROUTER, unsupported_router, external_converter_device} = zigbeeHerdsman.devices;
zigbeeHerdsman.returnDevices.push(coordinator.ieeeAddr);
zigbeeHerdsman.returnDevices.push(bulb.ieeeAddr);
zigbeeHerdsman.returnDevices.push(bulb_color.ieeeAddr);
zigbeeHerdsman.returnDevices.push(WXKG02LM.ieeeAddr);
zigbeeHerdsman.returnDevices.push(WXKG02LM_rev1.ieeeAddr);
zigbeeHerdsman.returnDevices.push(CC2530_ROUTER.ieeeAddr);
zigbeeHerdsman.returnDevices.push(unsupported_router.ieeeAddr);
zigbeeHerdsman.returnDevices.push(external_converter_device.ieeeAddr)
const MQTT = require('./stub/mqtt');
const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const flushPromises = () => new Promise(setImmediate);
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
Date.now = jest.fn()
Date.now.mockReturnValue(10000);
const mocksClear = [MQTT.publish, logger.warn, logger.debug];
const setTimeoutNative = setTimeout;
describe('Networkmap', () => {
let controller;
beforeAll(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.reRead();
data.writeEmptyState();
settings.set(['experimental', 'new_api'], true);
controller = new Controller();
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(jest.fn(), jest.fn());
await controller.start();
mocksClear.forEach((m) => m.mockClear());
await flushPromises();
@@ -39,22 +42,29 @@ describe('Networkmap', () => {
await flushPromises();
const device = zigbeeHerdsman.devices.bulb_color;
device.lastSeen = 1000;
external_converter_device.lastSeen = 1000;
global.setTimeout = (r) => r();
});
afterEach(async () => {
global.setTimeout = setTimeoutNative;
});
function mock() {
/**
* Topology
*
* | -> external_device
* | -> bulb_color -> unsupported_router (offline)
* coordinator | ^ ^
* | | | (not valid)
* | -> bulb |
* | -> CC2530_ROUTER -> WXKG02LM
* | -> CC2530_ROUTER -> WXKG02LM_rev1
*
*/
coordinator.lqi = () => {return {neighbors: [
{ieeeAddr: bulb_color.ieeeAddr, networkAddress: bulb_color.networkAddress, relationship: 2, depth: 1, linkquality: 120},
{ieeeAddr: bulb.ieeeAddr, networkAddress: bulb.networkAddress, relationship: 2, depth: 1, linkquality: 92}
{ieeeAddr: bulb.ieeeAddr, networkAddress: bulb.networkAddress, relationship: 2, depth: 1, linkquality: 92},
{ieeeAddr: external_converter_device.ieeeAddr, networkAddress: external_converter_device.networkAddress, relationship: 2, depth: 1, linkquality: 92}
]}};
coordinator.routingTable = () => {return {table: [
{destinationAddress: CC2530_ROUTER.networkAddress, status: 'ACTIVE', nextHop: bulb.networkAddress},
@@ -70,7 +80,7 @@ describe('Networkmap', () => {
bulb_color.routingTable = () => {return {table: []}};
CC2530_ROUTER.lqi = () => {return {neighbors: [
{ieeeAddr: '0x0000000000000000', networkAddress: WXKG02LM.networkAddress, relationship: 1, depth: 2, linkquality: 130},
{ieeeAddr: '0x0000000000000000', networkAddress: WXKG02LM_rev1.networkAddress, relationship: 1, depth: 2, linkquality: 130},
{ieeeAddr: bulb_color.ieeeAddr, networkAddress: bulb_color.networkAddress, relationship: 4, depth: 2, linkquality: 130},
]}};
CC2530_ROUTER.routingTable = () => {return {table: []}};
@@ -87,26 +97,7 @@ describe('Networkmap', () => {
let call = MQTT.publish.mock.calls[0];
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/raw');
const conv = (device) => {return {ieeeAddr: device.ieeeAddr, networkAddress: device.networkAddress}};
const expected = {
"nodes":[
{"definition":null,"lastSeen": 1000,"ieeeAddr":coordinator.ieeeAddr,"friendlyName":"Coordinator","type":"Coordinator","networkAddress":0,"modelID":null,"failed":[]},
{"definition":{"description": "TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model": "LED1545G12","supports": "on/off, brightness, color temperature","vendor": "IKEA"},"lastSeen": 1000,"ieeeAddr":bulb.ieeeAddr,"friendlyName":"bulb","type":"Router","networkAddress":40369,"modelID":"TRADFRI bulb E27 WS opal 980lm","failed":[]},
{"definition":{"description": "Hue Go","model": "7146060PH","supports": "on/off, brightness, color temperature, color xy, power-on behavior","vendor": "Philips"},"lastSeen": 1000,"ieeeAddr":bulb_color.ieeeAddr,"friendlyName":"bulb_color","type":"Router","networkAddress":40399,"modelID":"LLC020","failed":[]},
{"definition":{"description": "Aqara double key wireless wall switch","model": "WXKG02LM","supports": "left, right, both click (and double, long click for left, right and both depending on model)","vendor": "Xiaomi"},"lastSeen": 1000,"ieeeAddr":WXKG02LM.ieeeAddr,"friendlyName":"button_double_key","type":"EndDevice","networkAddress":6538,"modelID":"lumi.sensor_86sw2.es1"},
{"definition":null,"lastSeen": 1000,"ieeeAddr":unsupported_router.ieeeAddr,"friendlyName":"0x0017880104e45525","type":"Router","networkAddress":6536,"modelID":"notSupportedModelID","manufacturerName": "Boef","failed":['lqi', 'routingTable']},
{"definition":{"description": "[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model": "CC2530.ROUTER","supports": "state, description, type, rssi","vendor": "Custom devices (DiY)"},"lastSeen": 1000,"ieeeAddr":CC2530_ROUTER.ieeeAddr,"friendlyName":"cc2530_router","type":"Router","networkAddress":6540,"modelID":"lumi.router","failed":[]},
],
"links":[
{depth: 1, linkquality: 120, routes: [], source: conv(bulb_color), target: conv(coordinator), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 120, relationship: 2},
{depth: 1, linkquality: 92, routes: [{destinationAddress: CC2530_ROUTER.networkAddress, status: 'ACTIVE', nextHop: bulb.networkAddress}], source: conv(bulb), target: conv(coordinator), sourceIeeeAddr: bulb.ieeeAddr, sourceNwkAddr: bulb.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 92, relationship: 2},
{depth: 2, linkquality: 110, routes: [], source: conv(bulb_color), target: conv(bulb), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 110, relationship: 1},
{depth: 2, linkquality: 100, routes: [], source: conv(CC2530_ROUTER), target: conv(bulb), sourceIeeeAddr: CC2530_ROUTER.ieeeAddr, sourceNwkAddr: CC2530_ROUTER.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 100, relationship: 1},
{depth: 2, linkquality: 130, routes: [], source: conv(WXKG02LM), target: conv(CC2530_ROUTER), sourceIeeeAddr: WXKG02LM.ieeeAddr, sourceNwkAddr: WXKG02LM.networkAddress, targetIeeeAddr: CC2530_ROUTER.ieeeAddr, lqi: 130, relationship: 1}
]
};
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":"","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]};
expect(JSON.parse(call[1])).toStrictEqual(expected);
/**
@@ -152,11 +143,13 @@ describe('Networkmap', () => {
"0x000b57fffec6a5b3" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb_color|0x000b57fffec6a5b3 (40399)|Philips Hue Go (7146060PH)|unknown}"];
"0x000b57fffec6a5b3" -> "0x00124b00120144ae" [penwidth=0.5, weight=0, color="#994444", label="120"]
"0x000b57fffec6a5b3" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="110"]
"0x0017880104e45521" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{button_double_key|0x0017880104e45521 (6538)|Xiaomi Aqara double key wireless wall switch (WXKG02LM)|9 seconds ago}"];
"0x0017880104e45521" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{button_double_key|0x0017880104e45521 (6538)|Xiaomi Aqara double key wireless wall switch (2016 model) (WXKG02LM_rev1)|9 seconds ago}"];
"0x0017880104e45521" -> "0x0017880104e45559" [penwidth=1, weight=0, color="#994444", label="130"]
"0x0017880104e45525" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{0x0017880104e45525|0x0017880104e45525 (6536)failed: lqi,routingTable|Boef notSupportedModelID|9 seconds ago}"];
"0x0017880104e45559" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{cc2530_router|0x0017880104e45559 (6540)|Custom devices (DiY) [CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/) (CC2530.ROUTER)|9 seconds ago}"];
"0x0017880104e45559" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="100"]
"0x0017880104e45511" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{0x0017880104e45511|0x0017880104e45511 (1114)|external external (external_converter_device)|9 seconds ago}"];
"0x0017880104e45511" -> "0x00124b00120144ae" [penwidth=1, weight=0, color="#994444", label="92"]
}`;
const expectedLines = expected.split('\n');
@@ -184,6 +177,16 @@ describe('Networkmap', () => {
const expected = `' paste into: https://www.planttext.com/
@startuml
card 0x0017880104e45511 [
0x0017880104e45511
---
0x0017880104e45511 (1114)
---
external external (external_converter_device)
---
9 seconds ago
]
card 0x0017880104e45525 [
0x0017880104e45525
---
@@ -219,7 +222,7 @@ describe('Networkmap', () => {
---
0x0017880104e45521 (6538)
---
Xiaomi Aqara double key wireless wall switch (WXKG02LM)
Xiaomi Aqara double key wireless wall switch (2016 model) (WXKG02LM_rev1)
---
9 seconds ago
]
@@ -244,6 +247,7 @@ describe('Networkmap', () => {
0x000b57fffec6a5b3 --> 0x00124b00120144ae: 120
0x000b57fffec6a5b2 --> 0x00124b00120144ae: 92
0x0017880104e45511 --> 0x00124b00120144ae: 92
0x000b57fffec6a5b3 --> 0x000b57fffec6a5b2: 110
0x0017880104e45559 --> 0x000b57fffec6a5b2: 100
0x0017880104e45521 --> 0x0017880104e45559: 130
@@ -267,33 +271,7 @@ describe('Networkmap', () => {
let call = MQTT.publish.mock.calls[0];
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const conv = (device) => {return {ieeeAddr: device.ieeeAddr, networkAddress: device.networkAddress}};
const expected = {
status: 'ok',
data: {
type: 'raw',
routes: true,
value: {
"nodes":[
{"definition":null,"lastSeen": 1000,"ieeeAddr":coordinator.ieeeAddr,"friendlyName":"Coordinator","type":"Coordinator","networkAddress":0,"modelID":null,"failed":[]},
{"definition":{"description": "TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model": "LED1545G12","supports": "on/off, brightness, color temperature","vendor": "IKEA"},"lastSeen": 1000,"ieeeAddr":bulb.ieeeAddr,"friendlyName":"bulb","type":"Router","networkAddress":40369,"modelID":"TRADFRI bulb E27 WS opal 980lm","failed":[]},
{"definition":{"description": "Hue Go","model": "7146060PH","supports": "on/off, brightness, color temperature, color xy, power-on behavior","vendor": "Philips"},"lastSeen": 1000,"ieeeAddr":bulb_color.ieeeAddr,"friendlyName":"bulb_color","type":"Router","networkAddress":40399,"modelID":"LLC020","failed":[]},
{"definition":{"description": "Aqara double key wireless wall switch","model": "WXKG02LM","supports": "left, right, both click (and double, long click for left, right and both depending on model)","vendor": "Xiaomi"},"lastSeen": 1000,"ieeeAddr":WXKG02LM.ieeeAddr,"friendlyName":"button_double_key","type":"EndDevice","networkAddress":6538,"modelID":"lumi.sensor_86sw2.es1"},
{"definition":null,"lastSeen": 1000,"ieeeAddr":unsupported_router.ieeeAddr,"friendlyName":"0x0017880104e45525","type":"Router","networkAddress":6536,"modelID":"notSupportedModelID","manufacturerName": "Boef","failed":['lqi', 'routingTable']},
{"definition":{"description": "[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model": "CC2530.ROUTER","supports": "state, description, type, rssi","vendor": "Custom devices (DiY)"},"lastSeen": 1000,"ieeeAddr":CC2530_ROUTER.ieeeAddr,"friendlyName":"cc2530_router","type":"Router","networkAddress":6540,"modelID":"lumi.router","failed":[]},
],
"links":[
{depth: 1, linkquality: 120, routes: [], source: conv(bulb_color), target: conv(coordinator), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 120, relationship: 2},
{depth: 1, linkquality: 92, routes: [{destinationAddress: CC2530_ROUTER.networkAddress, status: 'ACTIVE', nextHop: bulb.networkAddress}], source: conv(bulb), target: conv(coordinator), sourceIeeeAddr: bulb.ieeeAddr, sourceNwkAddr: bulb.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 92, relationship: 2},
{depth: 2, linkquality: 110, routes: [], source: conv(bulb_color), target: conv(bulb), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 110, relationship: 1},
{depth: 2, linkquality: 100, routes: [], source: conv(CC2530_ROUTER), target: conv(bulb), sourceIeeeAddr: CC2530_ROUTER.ieeeAddr, sourceNwkAddr: CC2530_ROUTER.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 100, relationship: 1},
{depth: 2, linkquality: 130, routes: [], source: conv(WXKG02LM), target: conv(CC2530_ROUTER), sourceIeeeAddr: WXKG02LM.ieeeAddr, sourceNwkAddr: WXKG02LM.networkAddress, targetIeeeAddr: CC2530_ROUTER.ieeeAddr, lqi: 130, relationship: 1}
]
}
}
};
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":"","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();
+19 -12
View File
@@ -18,22 +18,15 @@ describe('OTA update', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.set(['experimental', 'new_api'], true);
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();
});
it('Should subscribe to topics', async () => {
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/ota_update/check');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/ota_update/update');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/ota_update/check');
expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/request/device/ota_update/update');
});
it('Should OTA update a device', async () => {
const device = zigbeeHerdsman.devices.bulb;
const endpoint = device.endpoints[0];
@@ -58,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');
@@ -257,6 +250,20 @@ 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 data = {imageType: 12382};
@@ -303,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);
+75 -31
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});
});
@@ -195,6 +195,21 @@ describe('Publish', () => {
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should publish messages to zigbee devices to non default-ep with brightness_[EP]', async () => {
const device = zigbeeHerdsman.devices.QS_Zigbee_D02_TRIAC_2C_LN;
const endpoint = device.getEndpoint(2);
await MQTT.events.message('zigbee2mqtt/0x0017882194e45543/set', stringify({state_l2: 'ON', brightness_l2: 50}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {});
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/QS-Zigbee-D02-TRIAC-2C-LN',
stringify({brightness_l2: 50, state_l2: 'ON'}),
{ retain: false, qos: 0 },
expect.any(Function)
);
});
it('Should publish messages to zigbee devices with color xy', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
@@ -204,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});
});
@@ -221,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});
});
@@ -238,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});
});
@@ -255,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});
});
@@ -269,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});
});
@@ -285,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 () => {
@@ -297,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 () => {
@@ -328,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 () => {
@@ -339,7 +354,7 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/thermostat_group/set', stringify({child_lock: 'LOCK'}));
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith("manuSpecificTuyaDimmer", "setData", {data: [1,1], dp: 263, fn: 0, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true});
expect(group.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 7, length_hi: 0, length_lo: 1, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true});
});
it('Should publish messages to groups with on and brightness', async () => {
@@ -372,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 () => {
@@ -383,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(6);
expect(Object.values(zigbeeHerdsman.groups).length).toBe(7);
await MQTT.events.message('zigbee2mqtt/group_2/set', stringify({state: 'ON'}));
await flushPromises();
expect(Object.values(zigbeeHerdsman.groups).length).toBe(7);
expect(Object.values(zigbeeHerdsman.groups).length).toBe(8);
expect(zigbeeHerdsman.groups.group_2.command).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.groups.group_2.command).toHaveBeenCalledWith("genOnOff", "on", {}, {});
});
@@ -539,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 () => {
@@ -729,6 +744,35 @@ describe('Publish', () => {
expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 20, transtime: 200}, {}]);
});
it('Should turn bulb on with correct brightness when device is turned off twice and brightness is reported', async () => {
// Test case for: https://github.com/Koenkk/zigbee2mqtt/issues/5413
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "brightness": 200}));
await flushPromises();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "OFF", "transition": 0}));
await flushPromises();
await zigbeeHerdsman.events.message({data: {currentLevel: 1}, cluster: 'genLevelCtrl', device, endpoint, type: 'attributeReport', linkquality: 10});
await flushPromises();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "OFF", "transition": 0}));
await flushPromises();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "transition": 0}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(5);
expect(MQTT.publish).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 200}), {retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 200}), {retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(3, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 1}), {retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(4, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 1}), {retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(5, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 200}), {retain: false, qos: 0}, expect.any(Function));
expect(endpoint.command).toHaveBeenCalledTimes(4);
expect(endpoint.command).toHaveBeenNthCalledWith(1, "genLevelCtrl", "moveToLevelWithOnOff", {level: 200, transtime: 0}, {});
expect(endpoint.command).toHaveBeenNthCalledWith(2, "genLevelCtrl", "moveToLevelWithOnOff", {level: 0, transtime: 0}, {});
expect(endpoint.command).toHaveBeenNthCalledWith(3, "genLevelCtrl", "moveToLevelWithOnOff", {level: 0, transtime: 0}, {});
expect(endpoint.command).toHaveBeenNthCalledWith(4, "genLevelCtrl", "moveToLevelWithOnOff", {level: 200, transtime: 0}, {});
});
it('Should turn bulb on with full brightness when transition is used and no brightness is known', async () => {
// https://github.com/Koenkk/zigbee2mqtt/issues/3799
const device = zigbeeHerdsman.devices.bulb_color;
@@ -866,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 () => {
@@ -1029,7 +1073,7 @@ 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 () => {
@@ -1047,7 +1091,7 @@ 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 () => {
@@ -1062,7 +1106,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 () => {
@@ -1203,7 +1247,7 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/MKS-CM-W5/l3/set', stringify({state: 'ON'}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuyaDimmer", "setData", {data: [1,1], dp: 259, fn: 0, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true});
expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 3, length_hi: 0, length_lo: 1, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/MKS-CM-W5');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state_l3: 'ON'});
@@ -1318,27 +1362,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,"color":{"x":0.408707336668894,"y":0.39239142575868},"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,"color":{"x":0.408707336668894,"y":0.39239142575868},"state":"ON"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
+13 -13
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;
@@ -35,7 +35,7 @@ describe('Receive', () => {
});
it('Should handle a zigbee message which uses ep (left)', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {onOff: 1}
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
@@ -47,7 +47,7 @@ describe('Receive', () => {
});
it('Should handle a zigbee message which uses ep (right)', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {onOff: 1}
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(2), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
@@ -259,7 +259,7 @@ describe('Receive', () => {
});
it('Should handle a zigbee message with voltage 3010', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {'65281': {'1': 3010}}
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
@@ -271,7 +271,7 @@ describe('Receive', () => {
});
it('Should handle a zigbee message with voltage 2850', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {'65281': {'1': 2850}}
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
@@ -283,7 +283,7 @@ describe('Receive', () => {
});
it('Should handle a zigbee message with voltage 2650', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {'65281': {'1': 2650}}
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
@@ -295,7 +295,7 @@ describe('Receive', () => {
});
it('Should handle a zigbee message with voltage 2000', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {'65281': {'1': 2000}}
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
@@ -314,7 +314,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/occupancy_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'battery': 100, 'illuminance': 381, "illuminance_lux": 381, 'voltage': 3045});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'battery': 100, 'illuminance': 381, "illuminance_lux": 381, 'voltage': 3045, 'temperature': 19});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -328,7 +328,7 @@ describe('Receive', () => {
});
it('Should publish last_seen epoch', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
settings.set(['advanced', 'last_seen'], 'epoch');
const data = {onOff: 1};
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -341,7 +341,7 @@ describe('Receive', () => {
});
it('Should publish last_seen ISO_8601', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
settings.set(['advanced', 'last_seen'], 'ISO_8601');
const data = {onOff: 1};
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -354,7 +354,7 @@ describe('Receive', () => {
});
it('Should publish last_seen ISO_8601_local', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM;
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
settings.set(['advanced', 'last_seen'], 'ISO_8601_local');
const data = {onOff: 1};
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -418,7 +418,7 @@ describe('Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(logger.warn).toHaveBeenCalledWith(`Received message from unsupported device with Zigbee model 'notSupportedModelID'`);
expect(logger.warn).toHaveBeenCalledWith(`Received message from unsupported device with Zigbee model 'notSupportedModelID' and manufacturer name 'notSupportedMfg'`);
expect(logger.warn).toHaveBeenCalledWith(`Please see: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html.`);
expect(MQTT.publish).toHaveBeenCalledTimes(0);
});
+130 -13
View File
@@ -6,6 +6,7 @@ const settings = require('../lib/util/settings.js');
const fs = require('fs');
const configurationFile = data.joinPath('configuration.yaml');
const devicesFile = data.joinPath('devices.yaml');
const devicesFile2 = data.joinPath('devices2.yaml');
const groupsFile = data.joinPath('groups.yaml');
const secretFile = data.joinPath('secret.yaml');
const yaml = require('js-yaml');
@@ -21,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'));
@@ -29,7 +30,7 @@ describe('Settings', () => {
if (fs.existsSync(file)) fs.unlinkSync(file);
}
const clearEnvironmentVariables = () => {
Object.keys(process.env).forEach((key) => {
Object.keys(process.env).forEach((key) => {
if(key.indexOf('ZIGBEE2MQTT_CONFIG_') >= 0) {
delete process.env[key];
}
@@ -153,6 +154,7 @@ describe('Settings', () => {
const expected = {
include_device_information: false,
force_disable_retain: false,
password: "mysecretpassword",
server: "my.mqtt.server",
user: "mysecretusername",
@@ -196,6 +198,32 @@ describe('Settings', () => {
expect(device).toStrictEqual(expected);
});
it('Should read devices form 2 separate files', () => {
const contentConfiguration = {
devices: ['devices.yaml', 'devices2.yaml']
};
const contentDevices = {
'0x12345678': {
friendly_name: '0x12345678',
retain: false,
},
};
const contentDevices2 = {
'0x87654321': {
friendly_name: '0x87654321',
retain: false,
},
};
write(configurationFile, contentConfiguration);
write(devicesFile, contentDevices);
write(devicesFile2, contentDevices2);
expect(settings.getDevice('0x12345678').friendly_name).toStrictEqual('0x12345678');
expect(settings.getDevice('0x87654321').friendly_name).toStrictEqual('0x87654321');
});
it('Should add devices to a separate file', () => {
const contentConfiguration = {
devices: 'devices.yaml',
@@ -228,6 +256,47 @@ describe('Settings', () => {
expect(read(devicesFile)).toStrictEqual(expected);
});
it('Should add devices for first file when using 2 separates file', () => {
const contentConfiguration = {
devices: ['devices.yaml', 'devices2.yaml']
};
const contentDevices = {
'0x12345678': {
friendly_name: '0x12345678',
retain: false,
},
};
const contentDevices2 = {
'0x87654321': {
friendly_name: '0x87654321',
retain: false,
},
};
write(configurationFile, contentConfiguration);
write(devicesFile, contentDevices);
write(devicesFile2, contentDevices2);
settings.addDevice('0x1234');
expect(read(configurationFile)).toStrictEqual({devices: ['devices.yaml', 'devices2.yaml']});
const expected = {
'0x12345678': {
friendly_name: '0x12345678',
retain: false,
},
'0x1234': {
friendly_name: '0x1234',
},
};
expect(read(devicesFile)).toStrictEqual(expected);
expect(read(devicesFile2)).toStrictEqual(contentDevices2);
});
it('Should add devices to a separate file if devices.yaml doesnt exist', () => {
const contentConfiguration = {
devices: 'devices.yaml',
@@ -547,12 +616,24 @@ 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]));
});
it('Should not allow any string values for pan_id', () => {
write(configurationFile, {
...minimalConfig,
advanced: {pan_id: 'NOT_GENERATE'},
});
settings.reRead();
const error = `advanced.pan_id: should be number or 'GENERATE' (is 'NOT_GENERATE')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Should allow retention configuration with MQTT v5', () => {
write(configurationFile, {
...minimalConfig,
@@ -560,7 +641,7 @@ describe('Settings', () => {
devices: {'0x0017880104e45519': {friendly_name: 'tain', retention: 900}},
});
settings._reRead();
settings.reRead();
expect(settings.validate()).toEqual([]);
});
@@ -571,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]));
@@ -584,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]));
@@ -596,7 +677,7 @@ describe('Settings', () => {
advanced: null,
});
settings._reRead();
settings.reRead();
const error = `advanced should be object`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
@@ -642,19 +723,55 @@ 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]));
});
it('Configuration friendly name cannot be empty', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: '', retain: false}},
});
settings.reRead();
const error = `friendly_name must be at least 1 char long`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration friendly name cannot end with /', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'blaa/', retain: false}},
});
settings.reRead();
const error = `friendly_name is not allowed to end or start with /`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration friendly name cannot contain null char', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'blaa/blaa' + String.fromCharCode(0), retain: false}},
});
settings.reRead();
const error = `friendly_name is not allowed to contain null char`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration shouldnt be valid when friendly_name ends with /DIGIT', async () => {
write(configurationFile, {
...minimalConfig,
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]));
@@ -666,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]));
@@ -678,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]));
@@ -692,7 +809,7 @@ describe('Settings', () => {
},
});
settings._reRead();
settings.reRead();
expect(() => {
settings.changeFriendlyName('myname1', 'myname');
@@ -707,7 +824,7 @@ describe('Settings', () => {
},
});
settings._reRead();
settings.reRead();
expect(() => {
settings.removeDevice('myname33');
+3
View File
@@ -162,6 +162,9 @@ function writeDefaultConfiguration() {
'0x0017882104a44559': {
friendly_name: 'TS0601_thermostat',
},
'0x0017882194e45543': {
friendly_name: 'QS-Zigbee-D02-TRIAC-2C-LN',
},
},
groups: {
'1': {
+1
View File
@@ -19,6 +19,7 @@ const mock = {
cleanup: jest.fn(),
logOutput: jest.fn(),
add: (transport) => transports.push(transport),
addTransport: (transport) => transports.push(transport),
setLevel: (newLevel) => {level = newLevel},
getLevel: () => level,
setTransportsEnabled: (value) => {transportsEnabled = value},
+29 -20
View File
@@ -29,7 +29,7 @@ const clusters = {
}
class Endpoint {
constructor(ID, inputClusters, outputClusters, deviceIeeeAddress, binds=[], clusterValues={}) {
constructor(ID, inputClusters, outputClusters, deviceIeeeAddress, binds=[], clusterValues={}, configuredReportings=[]) {
this.deviceIeeeAddress = deviceIeeeAddress;
this.clusterValues = clusterValues;
this.ID = ID;
@@ -45,6 +45,7 @@ class Endpoint {
this.configureReporting = jest.fn();
this.meta = {};
this.binds = binds;
this.configuredReportings = configuredReportings;
this.getInputClusters = () => inputClusters.map((c) => {
return {ID: c, name: getKeyByValue(clusters, c)};
}).filter((c) => c.name);
@@ -84,9 +85,11 @@ class Endpoint {
Object.values(groups).forEach((g) => this.removeFromGroup(g))
}
this.getClusterAttributeValue = (cluster, value) => {
this.getClusterAttributeValue = jest.fn();
this.getClusterAttributeValue.mockImplementation((cluster, value) => {
if (!(cluster in this.clusterValues)) return undefined;
return this.clusterValues[cluster][value];
}
});
}
}
@@ -121,25 +124,36 @@ const returnDevices = [];
const bulb_color = new Device('Router', '0x000b57fffec6a5b3', 40399, 4107, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b3', [], {lightingColorCtrl: {colorCapabilities: 254}})], true, "Mains (single phase)", "LLC020");
const bulb_color_2 = new Device('Router', '0x000b57fffec6a5b4', 401292, 4107, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b4')], true, "Mains (single phase)", "LLC020", false, 'Philips', '2019.09', '5.127.1.26581');
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')], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm");
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 groups = {
'group_1': new Group(1, []),
'group_tradfri_remote': new Group(15071, [bulb_color_2.endpoints[0], bulb_2.endpoints[0]]),
'group/with/slashes': new Group(99, []),
'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]]),
'default_bind_group': new Group(901, []),
}
const devices = {
'coordinator': new Device('Coordinator', '0x00124b00120144ae', 0, 0, [new Endpoint(1, [], [])], false),
'bulb': new Device('Router', '0x000b57fffec6a5b2', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b2', [], {lightingColorCtrl: {colorCapabilities: 17}})], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm"),
'coordinator': new Device('Coordinator', '0x00124b00120144ae', 0, 0, [new Endpoint(1, [], [], '0x00124b00120144ae')], false),
'bulb': new Device('Router', '0x000b57fffec6a5b2', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b2', [], {lightingColorCtrl: {colorCapabilities: 17}}, [{cluster: {name: 'genOnOff'}, attribute: {name: 'onOff'}, minimumReportInterval: 1, maximumReportInterval: 10, reportableChange: 20}])], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm"),
'bulb_color': bulb_color,
'bulb_2': bulb_2,
'bulb_color_2': bulb_color_2,
'remote': new Device('EndDevice', '0x0017880104e45517', 6535, 4107, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x0017880104e45517', [{target: bulb_color.endpoints[0], cluster: {ID: 8, name: 'genLevelCtrl'}}, {target: new Group(1, []), cluster: {ID: 6, name: 'genOnOff'}}]), new Endpoint(2, [0,1,3,15,64512], [25, 6])], true, "Battery", "RWL021"),
'unsupported': new Device('EndDevice', '0x0017880104e45518', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "notSupportedModelID"),
'remote': new Device('EndDevice', '0x0017880104e45517', 6535, 4107, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x0017880104e45517', [{target: bulb_color.endpoints[0], cluster: {ID: 8, name: 'genLevelCtrl'}}, {target: bulb_color.endpoints[0], cluster: {ID: 6, name: 'genOnOff'}}, {target: bulb_color.endpoints[0], cluster: {ID: 768, name: 'lightingColorCtrl'}}, {target: groups.group_1, cluster: {ID: 6, name: 'genOnOff'}}, {target: groups.group_1, cluster: {ID: 6, name: 'genLevelCtrl'}}]), new Endpoint(2, [0,1,3,15,64512], [25, 6])], true, "Battery", "RWL021"),
'unsupported': new Device('EndDevice', '0x0017880104e45518', 6536, 0, [new Endpoint(1, [0], [0,3,4,6,8,5])], true, "Battery", "notSupportedModelID", false, "notSupportedMfg"),
'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"),
'WXKG02LM': new Device('EndDevice', '0x0017880104e45521', 6538,4151, [new Endpoint(1, [0], []), new Endpoint(2, [0], [])], true, "Battery", "lumi.sensor_86sw2.es1"),
'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"),
'ZNCZ02LM': new Device('Router', '0x0017880104e45524', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45524')], true, "Mains (single phase)", "lumi.plug"),
'ZNCZ02LM': ZNCZ02LM,
'E1743': new Device('Router', '0x0017880104e45540', 6540,4476, [new Endpoint(1, [0], [])], true, "Mains (single phase)", 'TRADFRI on/off switch'),
'QBKG04LM': new Device('Router', '0x0017880104e45541', 6549,4151, [new Endpoint(1, [0], [25]), new Endpoint(2, [0, 6], [])], true, "Mains (single phase)", 'lumi.ctrl_neutral1'),
'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'),
@@ -168,15 +182,8 @@ 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,
}
const groups = {
'group_1': new Group(1, []),
'group_tradfri_remote': new Group(15071, [bulb_color_2.endpoints[0], bulb_2.endpoints[0]]),
'group/with/slashes': new Group(99, []),
'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, [devices.ZNCZ02LM.endpoints[0]]),
'external_converter_device': new Device('EndDevice', '0x0017880104e45511', 1114, 'external', [], 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'),
}
const mock = {
@@ -186,6 +193,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}),
@@ -213,6 +221,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, []);
+6
View File
@@ -1,5 +1,6 @@
const utils = require('../lib/util/utils.js');
const version = require('../package.json').version;
const versionHerdsman = require('../node_modules/zigbee-herdsman/package.json').version;
describe('Utils', () => {
describe('Is xiaomi device', () => {
@@ -41,6 +42,11 @@ describe('Utils', () => {
expect(await utils.getZigbee2mqttVersion()).toStrictEqual({"commitHash": "unknown", "version": version});
})
it('Check dependency version', async () => {
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;