Compare commits

...
597 Commits
Author SHA1 Message Date
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
Koen Kanters 9ed84e130f 1.16.0 2020-11-01 12:58:35 +01:00
Koen Kanters 47cd482461 Undev 2020-11-01 12:58:31 +01:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
4fb8d4820f Update zigbee-herdsman-converters to 12.0.224 (#4833)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-11-01 11:05:20 +01:00
Koen Kanters a09bb3bc00 Home Assistant: support multiendpoint for enum sensor 2020-11-01 11:02:35 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 4d3e6eb11f Update zigbee-herdsman to 0.13.26 (#4834)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-01 10:52:41 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 606f7f44cc Update dependencies (#4830)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-01 10:35:22 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 1933b0f29d Update zigbee2mqtt-frontend to 0.2.68 (#4828)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-11-01 07:21:51 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 98d49a77ed Update zigbee2mqtt-frontend to 0.2.67 (#4826)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-31 21:21:06 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 7cead8cb68 Update zigbee2mqtt-frontend to 0.2.66 (#4825)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-31 18:35:18 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 487cc20129 Update zigbee2mqtt-frontend to 0.2.65 (#4823)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-31 14:39:36 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 09ae27503e Update zigbee2mqtt-frontend to 0.2.64 (#4822)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-31 10:20:35 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
e1b5a105dd Update zigbee-herdsman-converters to 12.0.223 (#4820)
* Update zigbee-herdsman-converters to 12.0.223

* 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-10-30 23:39:26 +01:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
79275f9a4c Update zigbee-herdsman-converters to 12.0.222 (#4816)
* Update zigbee-herdsman-converters to 12.0.222

* 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-10-29 21:43:47 +01:00
Koen Kanters aac7cb1ff6 Fix brightness or state not read when reading state and brightness. https://github.com/Koenkk/zigbee2mqtt/issues/4803 2020-10-29 18:03:16 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d78610eecf Update zigbee2mqtt-frontend to 0.2.63 (#4810)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-29 13:41:04 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6880dd786f Update zigbee2mqtt-frontend to 0.2.62 (#4808)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-29 10:14:02 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> a62f8e17ca Update zigbee2mqtt-frontend to 0.2.61 (#4788)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-29 07:43:38 +08:00
Emil FlachandEmil Flach 084fccc53d Partial fix for #4571, changed current_heating_setpoint for occupied_heating_setpoint (#4784)
Co-authored-by: Emil Flach <emil.flach@skopei.com>
2020-10-28 17:54:31 +01:00
Drzony 5d643bac8e Home Assistant fixes for Saswell TRV (#4800)
* Home Assistant fixes for Saswell TRV

* fixup! Home Assistant fixes for Saswell TRV
2020-10-28 17:49:44 +01:00
Hacker-CB 82f3d7d9bb update.sh: add checking if data-backup exists (#4793) 2020-10-28 10:45:49 +01:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
697955997a Update zigbee-herdsman-converters to 12.0.221 (#4796)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-27 21:57:35 +01:00
Koen Kanters 9a53399a8c Changes for zigbee-herdsman-converters 12.0.221 2020-10-27 21:50:55 +01:00
Koen Kanters b2049b449b Home Assistant discovery: use availability instead of availability_topic. https://github.com/Koenkk/zigbee2mqtt/issues/775 2020-10-27 21:49:44 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> a2992ab144 Update zigbee2mqtt-frontend to 0.2.50 (#4787)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-27 05:52:29 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
21fcfb0b17 Update zigbee-herdsman-converters to 12.0.220 (#4782)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-26 17:34:46 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> e449b772cc Update zigbee-herdsman to 0.13.24 (#4781)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-26 17:31:28 +01:00
Koen Kanters 922f828696 Allow binary exposes not in lookup. 2020-10-26 17:25:41 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> fee631080e Update zigbee2mqtt-frontend to 0.2.48 (#4777)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-26 07:25:59 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 22681be3c7 Update dependencies (#4766)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-25 09:58:46 +01:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 7b0498aab0 Update zigbee2mqtt-frontend to 0.2.45 (#4770)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-25 16:25:37 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> c9e030bb19 Update zigbee2mqtt-frontend to 0.2.43 (#4768)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-25 13:20:12 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> c702badd98 Update zigbee2mqtt-frontend to 0.2.40 (#4767)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-25 10:54:01 +08:00
Koen Kanters 63044421fd Get linkquality from herdsman instead of mesage. https://github.com/Koenkk/zigbee2mqtt/issues/4758 2020-10-24 17:15:46 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 8895c14f42 Update zigbee-herdsman to 0.13.23 (#4762)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-24 17:13:26 +02:00
Koen Kanters ddbf936102 Mark ZIGBEE2MQTT_CONFIG as deprecated. https://github.com/Koenkk/zigbee2mqtt/issues/4697 2020-10-24 16:20:48 +02:00
Koen Kanters 70a72dd32a Ignore yaml configuration files in data. https://github.com/Koenkk/zigbee2mqtt/issues/4697 2020-10-24 15:58:49 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
b13d18fa9a Update zigbee-herdsman-converters to 12.0.219 (#4745)
* Update zigbee-herdsman-converters to 12.0.219

* Update homeassistant.js

* Update bridge.test.js

* Update package.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-24 14:35:21 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> fcf10680f4 Update zigbee-herdsman to 0.13.21 (#4761)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-24 14:29:57 +02:00
Jan bd91b704b7 Added manual mode for GS361A-H04 (#4755) 2020-10-24 09:58:45 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> bd149af508 Update zigbee-herdsman to 0.13.20 (#4753)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-23 19:26:00 +02:00
mr G1K aa9ba03228 adapter zigate (#4737) 2020-10-23 19:13:33 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 3cbe7e3f39 Update zigbee2mqtt-frontend to 0.2.39 (#4749)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-23 08:21:04 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 040f612cd5 Update zigbee-herdsman to 0.13.19 (#4740)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-22 16:11:52 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 851aef3b2d Update zigbee2mqtt-frontend to 0.2.36 (#4732)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-21 08:54:18 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
6e74cb571f Update zigbee-herdsman-converters to 12.0.217 (#4728)
* Update zigbee-herdsman-converters to 12.0.217

* 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-10-20 20:10:30 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 466e9636a5 Update zigbee2mqtt-frontend to 0.2.35 (#4724)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-20 08:16:24 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
099aa26c6f Update zigbee-herdsman-converters to 12.0.216 (#4723)
* Update zigbee-herdsman-converters to 12.0.216

* 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-10-19 19:06:49 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
d73aea8ede Update zigbee-herdsman-converters to 12.0.215 (#4721)
* Update zigbee-herdsman-converters to 12.0.215

* Update homeassistant.js

* Update publish.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-10-19 16:48:09 +02:00
Koen Kanters 5134f0b689 Fix 0x0000000000000000 in networkmap. 2020-10-19 16:38:53 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b9662ed866 Update zigbee2mqtt-frontend to 0.2.33 (#4715)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-19 09:33:36 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 3de953aa2b Update zigbee2mqtt-frontend to 0.2.32 (#4714)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-19 08:13:59 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
09fbed6c75 Update zigbee-herdsman-converters to 12.0.214 (#4713)
* Update zigbee-herdsman-converters to 12.0.214

* Update bridge.test.js

* Update homeassistant.js

* 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-10-18 21:51:32 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> a9106924c7 Update dependencies (#4702)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-18 17:11:00 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
9ac660bc22 Update zigbee-herdsman-converters to 12.0.213 (#4696)
* Update zigbee-herdsman-converters to 12.0.213

* 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-10-17 16:58:27 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 87cabc29d4 Update zigbee-herdsman to 0.13.18 (#4695)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-17 16:32:01 +02:00
Utyf a5c9fb2940 Add unit_of_measurement for DIYRuZ_Geiger. (#4689) 2020-10-17 16:17:36 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6837cfa2fc Update zigbee-herdsman-converters to 12.0.212 (#4685)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-16 18:40:46 +02:00
949a16eba0 Support JAVIS lock and TS0601 roller blind motor (#4652)
* Added support for Zemismart garage opener, Larkey dimmer & curtain switch

* support JAVIS lock

* support roller blind motor

* fix test coverage

* fix coding style

* update JAVIS lock name

* Update homeassistant.js

Co-authored-by: ChinhND <dinhchinh@gmail.com>
Co-authored-by: unknown <dungnguyen98nd@gmail.com>
Co-authored-by: TheEnd98 <49032426+TheEnd98@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-16 18:20:53 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 61e449430b Update zigbee-herdsman to 0.13.17 (#4684)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-16 17:33:13 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 9e4e8acdb0 Update zigbee-herdsman to 0.13.16 (#4675)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-15 19:22:22 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
84e725d5ce Update zigbee-herdsman-converters to 12.0.211 (#4667)
* Update zigbee-herdsman-converters to 12.0.211

* Update publish.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-14 22:18:51 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
8319cdbd61 Update zigbee-herdsman-converters to 12.0.210 (#4659)
* Update zigbee-herdsman-converters to 12.0.210

* 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-10-14 08:59:14 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 467be5410c Update zigbee2mqtt-frontend to 0.2.31 (#4658)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-14 06:29:33 +08:00
Marco MassarottoandKoen Kanters e63a2ac7f6 Allow redefine autodiscovery type (#4647)
* allow to redefine entity type and id in homeassistant autodiscovery

* Update homeassistant.test.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-13 20:33:48 +02:00
insipiensandKoen Kanters 24188a3eea add saswell TRV (#4644)
* add saswell TRV

* Update homeassistant.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-12 17:30:36 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
c2f28d4ed6 Update zigbee-herdsman-converters to 12.0.209 (#4646)
* Update zigbee-herdsman-converters to 12.0.209

* 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-10-12 17:22:53 +02:00
Koen Kanters fa5ac2902f Update dumpHomeAssistantMapping.js 2020-10-12 17:20:27 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> ebdf4bb2e2 Update zigbee2mqtt-frontend to 0.2.29 (#4645)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-12 07:13:33 +08:00
Koen Kanters 73bc34f419 Fix frontend not loading after update. #4266 2020-10-11 16:17:52 +02:00
Koen Kanters 9098bb1e06 Add missing test 2020-10-11 15:12:07 +02:00
Koen Kanters 8202a10837 Permit joining on specific device. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-10-11 15:05:21 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2aa652f6e9 Update dependencies (#4636)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-11 14:01:41 +02:00
Koen Kanters 983340f44c Fix BHT-002-GCLZB HA discovery. https://github.com/Koenkk/zigbee2mqtt/pull/4537#issuecomment-706562247 2020-10-11 13:43:11 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d9b9b05494 Update zigbee-herdsman-converters to 12.0.208 (#4633)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-10 16:54:28 +02:00
Koen Kanters 723e3a3964 Fix optimistic group state not published on startup. https://github.com/Koenkk/zigbee2mqtt/issues/3461 2020-10-10 16:41:02 +02:00
timstanley1985 ad0905607b Fix typo is ota logs (#4628) 2020-10-10 16:00:10 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
e836984e5e Update zigbee-herdsman-converters to 12.0.207 (#4627)
* Update zigbee-herdsman-converters to 12.0.207

* 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-10-10 01:25:53 +02:00
Koen Kanters e92a852349 Don't setup reporting for TS0115. https://github.com/Koenkk/zigbee2mqtt/issues/4621 2020-10-09 23:51:48 +02:00
Koen Kanters eecd15ebfa Don't republish identical optimistic group states. #3461 (#4623)
* Don't republish identical optimistic state for groups. #3461

* Fixes
2020-10-09 23:29:34 +02:00
John DoeandKoen Kanters da91158f94 rePublish device state on rename (#4620)
* Republish device state on rename

* Update bridge.js

* Update bridge.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-09 22:59:10 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
9dc34c421f Update zigbee-herdsman-converters to 12.0.205 (#4625)
* Update zigbee-herdsman-converters to 12.0.205

* 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-10-09 22:53:07 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 759751629d Update zigbee-herdsman to 0.13.15 (#4624)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-09 22:44:46 +02:00
Koen Kanters 24d8e5f113 Expose linkquality sensor for all devices. https://github.com/Koenkk/zigbee-herdsman-converters/pull/1638 2020-10-09 21:57:54 +02:00
Koen Kanters 9d29bcc566 Fix switches/plugs not controllable in group. https://github.com/Koenkk/zigbee2mqtt/issues/4558 2020-10-09 21:40:33 +02:00
Koen Kanters e2e51fc32c Revert "Does linux/386 work now?"
This reverts commit 75ca6f3970.
2020-10-09 18:21:50 +02:00
Koen Kanters 75ca6f3970 Does linux/386 work now? 2020-10-09 18:19:59 +02:00
Koen Kanters 33985bc976 Fix buildx command 2020-10-09 18:15:14 +02:00
Koen Kanters 17545a6f72 Fix ci.yml 2020-10-09 18:12:38 +02:00
Koen Kanters e36ca7f8f8 Merge branch 'master' into dev 2020-10-09 18:09:50 +02:00
Koen Kanters ed8b4e567b Migrate to docker setup QEMU/Buildx 2020-10-09 18:09:40 +02:00
Koen Kanters c1a1b91a25 Fix docker build 2020-10-09 15:20:05 +02:00
Koen Kanters 330927d4b7 Merge branch 'master' into dev 2020-10-09 14:25:22 +02:00
Koen Kanters 40ab11bc49 Revert "Use buildx 0.4.2"
This reverts commit a571949f49.
2020-10-09 14:25:11 +02:00
Koen Kanters f618926d09 Merge branch 'master' into dev 2020-10-09 14:21:35 +02:00
Koen Kanters 0e30a73a19 Drop linux/i386 image (causes build errors) 2020-10-09 14:21:26 +02:00
Koen Kanters fb7b8ba867 Merge branch 'master' into dev 2020-10-09 14:18:14 +02:00
Koen Kanters 6fa4807a5a linux/386 -> linux/i386 2020-10-09 14:18:00 +02:00
Koen Kanters 456fb4014c Merge branch 'master' into dev 2020-10-09 14:10:44 +02:00
Koen Kanters a571949f49 Use buildx 0.4.2 2020-10-09 14:01:35 +02:00
Koen Kanters ddf3b57ec5 Use 12-alpine3.12 as base Docker image 2020-10-09 13:55:36 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 5ec3ef5be6 Update zigbee2mqtt-frontend to 0.2.27 (#4619)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-09 18:43:01 +08:00
Koen Kanters 61e650e7b5 Don't add stale to "breaking change" issues. https://github.com/Koenkk/zigbee2mqtt/pull/4572 2020-10-08 22:21:00 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
8cae206c64 Update zigbee-herdsman-converters to 12.0.204 (#4605)
* Update zigbee-herdsman-converters to 12.0.204

* Update homeassistant.js

* 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-10-08 22:08:18 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> e9c96e7a24 Update zigbee-herdsman to 0.13.14 (#4606)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-08 22:00:50 +02:00
434981b567 [ImgBot] Optimize images (#4602)
*Total -- 451.91kb -> 199.42kb (55.87%)

/images/logo_large.png -- 243.86kb -> 81.11kb (66.74%)
/images/logo_bee_only.png -- 89.06kb -> 29.77kb (66.57%)
/images/logo.png -- 3.62kb -> 2.28kb (36.82%)
/images/architecture.png -- 102.93kb -> 75.96kb (26.21%)
/images/logo_vector.svg -- 12.44kb -> 10.30kb (17.19%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-08 20:35:42 +02:00
Hacker-CB dcaaef286d Fix for HEIMAN Smart socket HS2SK (NXP version) haElectricalMeasurement. (#4596)
Device does not support reporting. Need to poll.
2020-10-08 19:43:18 +02:00
Marco MassarottoandKoen Kanters bc7a436241 allow to redefine homeassistant autodiscovery entity "type" and "object_id" (#4593)
* Add link for creating frontend issues. #4568

* allow to redefine entity type and object_id in homeassistant autodiscovery

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-08 19:38:25 +02:00
Koen Kanters 667bc42bee Merge branch 'master' into dev 2020-10-08 19:36:29 +02:00
Koen Kanters 9257cd82b3 Fix optimistic for groups. https://github.com/Koenkk/zigbee2mqtt/pull/4572 2020-10-08 17:59:39 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6989f303b8 Update zigbee2mqtt-frontend to 0.2.26 (#4592)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-08 06:26:30 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
ebca056ad9 Update zigbee-herdsman-converters to 12.0.203 (#4583)
* Update zigbee-herdsman-converters to 12.0.203

* 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-10-07 17:37:03 +02:00
Koen Kanters 172773a7f2 Sort in dumpHomeAssistantMapping.js 2020-10-07 17:34:48 +02:00
Koen Kanters 3a1f697d93 Don't log when including logger.js 2020-10-07 17:15:46 +02:00
Koen Kanters 8f40d18c0a Fix last seen unknown when opening frontend. #4266 2020-10-07 17:05:46 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> a132094be4 Update zigbee2mqtt-frontend to 0.2.24 (#4578)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-07 08:27:32 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
ba57e352b7 Update zigbee-herdsman-converters to 12.0.202 (#4576)
* Update zigbee-herdsman-converters to 12.0.202

* 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-10-06 22:10:40 +02:00
Koen Kanters 5d1975d60c Add optimistic option. https://github.com/Koenkk/zigbee2mqtt/issues/4524 (#4572) 2020-10-06 21:50:07 +02:00
Koen Kanters a982acc1ca Fix tests after https://github.com/Koenkk/zigbee2mqtt/pull/4510 2020-10-06 21:42:05 +02:00
Hacker-CB c045b90d2b Add tz.tuya_thermostat_away_mode (fix for https://github.com/Koenkk/z… (#4510)
* Add tz.tuya_thermostat_away_mode (fix for https://github.com/Koenkk/zigbee2mqtt/pull/4491)

* Added /* istanbul ignore else */
2020-10-06 21:37:15 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
34e82118a1 Update zigbee-herdsman-converters to 12.0.201 (#4575)
* Update zigbee-herdsman-converters to 12.0.201

* Update homeassistant.js

* Update utils.js

* 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-10-06 20:40:20 +02:00
Koen Kanters e90182642d Republish groups on device remove. https://github.com/nurikk/z2m-frontend/issues/31 2020-10-06 19:28:32 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6ca8982237 Update zigbee-herdsman to 0.13.13 (#4573)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-06 16:33:20 +02:00
Koen Kanters e30b2576b7 Add link for creating frontend issues. #4568 2020-10-06 12:49:56 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
20c643552c Update zigbee-herdsman-converters to 12.0.200 (#4566)
* Update zigbee-herdsman-converters to 12.0.200

* 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-10-05 19:36:32 +02:00
Nick RandellandKoen Kanters d5a3bf5d93 Added SmartPlug51AU (#4562)
* Added SmartPlug51AU

* Updated to fix conflicts

* Update homeassistant.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-05 19:22:46 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> cb0cf20e46 Update zigbee-herdsman-converters to 12.0.199 (#4563)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-05 17:47:44 +02:00
Koen Kanters a2a7cebd07 Fix tests 2020-10-05 17:43:57 +02:00
Koen Kanters 2fb3cb62f6 Fix touchlink factory reset failing with ''BUFFER' not available' error. #4543 2020-10-05 16:21:20 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> a7f931b8e3 Update dependencies (#4553)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-05 11:38:00 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> aea803bec7 Update zigbee2mqtt-frontend to 0.2.23 (#4560)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-05 06:41:06 +08:00
John Doe 628ca0daa1 Remove mqtt password from bridge/info (#4556) 2020-10-04 20:42:38 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 75ec7c3e05 Update zigbee2mqtt-frontend to 0.2.22 (#4554)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-04 11:17:39 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
57b5529cd1 Update zigbee-herdsman-converters to 12.0.198 (#4551)
* Update zigbee-herdsman-converters to 12.0.198

* Update homeassistant.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-10-04 00:32:43 +02:00
insipiensandKoen Kanters 9df1ea4359 Moes room thermostat (#4537)
* Add Moes Room Thermostat entry

* Added entry for Moes Room thermostat

* Revert "Added entry for Moes Room thermostat"

This reverts commit 697c530023.

* Correct layout

* remove duplicate

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-03 23:33:31 +02:00
Koen Kanters 2a08127e13 Fix tests 2020-10-03 23:24:08 +02:00
Koen Kanters 0c36b7894b Fix json escaped raw networkmap. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-10-03 22:47:20 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 43f085b069 Update zigbee2mqtt-frontend to 0.2.21 (#4539)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-03 08:19:46 +08:00
Koen Kanters 14c96a1045 Exposes for switch. https://github.com/Koenkk/zigbee2mqtt/issues/4466 2020-10-02 17:26:08 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
145f1e862d Update zigbee-herdsman-converters to 12.0.197 (#4534)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-10-02 17:25:25 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b5dc732a91 Update zigbee-herdsman to 0.13.12 (#4535)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-02 17:24:24 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 30449565ac Update zigbee2mqtt-frontend to 0.2.20 (#4525)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-02 15:28:43 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2117e79324 Update zigbee2mqtt-frontend to 0.2.18 (#4522)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-02 10:44:02 +08:00
Koen Kanters 50226d2c84 Exposes for lights. #4466 (#4516)
* Add dumpHomeAssistantMapping

* Add exposes for lights. https://github.com/Koenkk/zigbee2mqtt/issues/4466

* Updates

* Updates
2020-10-01 18:33:59 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> c75c89c388 Update zigbee-herdsman-converters to 12.0.196 (#4517)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-10-01 18:14:43 +02:00
Koen Kanters 4c2ce0d5eb Promote to dev 2020-10-01 16:49:55 +02:00
Koen Kanters 1bccc5d9f1 1.15.0 2020-10-01 16:44:12 +02:00
Koen Kanters 4de13ef02b Undev 2020-10-01 16:44:06 +02:00
Koen Kanters 87a1fe62cb More fixes for https://github.com/Koenkk/zigbee2mqtt/pull/4491 2020-09-30 20:06:47 +02:00
Koen Kanters a0499af62d Fixes for https://github.com/Koenkk/zigbee2mqtt/pull/4491 2020-09-30 20:04:06 +02:00
Hacker-CB 44224b4d6b Added ETOP HT-08 Thermostat and HT-10 TRV (#4491)
* Added ETOP HT-08 Thermostat

References:
* https://github.com/Koenkk/zigbee2mqtt/issues/4185
* https://github.com/Koenkk/zigbee2mqtt/issues/1856
* https://github.com/Koenkk/zigbee2mqtt/issues/4486
* http://www.etopcontrols.com/thermostat/detail/HT-08.html

* Rename `away` to `away_mode`

* Added ETOP HT-10 TRV
Minor fixes for HT-08

References:
* https://github.com/Koenkk/zigbee2mqtt/issues/1856
* http://www.etopcontrols.com/thermostat/detail/HT-10.html
2020-09-30 19:52:11 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
d5d7d076a7 Update zigbee-herdsman-converters to 12.0.194 (#4508)
* Update zigbee-herdsman-converters to 12.0.194

* 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-09-30 19:44:30 +02:00
jonasled 4afcff5cab added homeassistant config for ZS190000118 (#4492)
added homeassistant config for ZS190000118
2020-09-29 18:47:10 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 3b04e4c771 Update zigbee-herdsman to 0.13.11 (#4489)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-29 17:20:50 +02:00
embak 60e5a14776 Update Sinope TH1123ZB, TH1124ZB and TH1300ZB for Home Assistant (#4484)
* Update Sinope TH1123ZB, TH1124ZB and TH1300ZB for Home Assistant

Uniformized Home Assistant behavior/features for Sinope baseboards and floor thermostats.
TH1123ZB, TH1124ZB and TH1300ZB:
- change temperature setpoint resolution to 0,5C
TH1123ZB, and TH1124ZB:
- add energy entity
TH1124ZB and TH1300ZB:
- add local temperature entity
TH1124ZB:
- add keypad lockout (climate entity attribute)
- add power entity

* Linter correction
2020-09-29 17:00:22 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 08927fcb10 Update zigbee-herdsman to 0.13.10 (#4481)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-29 11:06:32 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 58d56b339c Update zigbee2mqtt-frontend to 0.2.17 (#4483)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-29 07:13:40 +08:00
Koen Kanters 051742e076 Logo bee only. https://github.com/nurikk/z2m-frontend/issues/17 2020-09-28 20:15:51 +02:00
Koen Kanters 0beea18adb Fix devices being undiscovered from Home Assistant when a non-standard Home Assistant discovery topic is used. #4480 2020-09-28 19:43:04 +02:00
Koen Kanters 4bb3ddcff2 Make frontend non-experimental. https://github.com/Koenkk/zigbee2mqtt/issues/4266 2020-09-27 22:18:48 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
546d6193a3 Update zigbee-herdsman-converters to 12.0.193 (#4476)
* Update zigbee-herdsman-converters to 12.0.193

* 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-09-27 22:13:31 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 939a2685af Update dependencies (#4471)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-27 17:38:44 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> fced7edc2d Update zigbee2mqtt-frontend to 0.2.15 (#4463)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-26 07:32:30 +08:00
Koen Kanters 49e621123a Fix Home Assistation device trigger not working after rename. #4452 2020-09-25 19:48:30 +02:00
Koen Kanters e286a66cea Don't rediscover Home Assistant device_automation (triggers). #4452 2020-09-25 18:47:28 +02:00
Koen Kanters 48cf4728bb Clear outdated Home Assistant device_automation discoveries. #4452 2020-09-25 18:20:27 +02:00
Koen Kanters 44f4c436d9 Mark node 13 as unsupported (not maintained anymore) 2020-09-25 17:48:05 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
6889f36ab0 Update zigbee-herdsman-converters to 12.0.192 (#4458)
* Update zigbee-herdsman-converters to 12.0.192

* 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-09-25 17:46:17 +02:00
Koen Kanters d386da6366 Fixes for 4f6797c365 2020-09-25 17:44:24 +02:00
Koen Kanters 4f6797c365 Fix topic not updated for Home Assistant sensor on rename. #4452 2020-09-25 17:32:40 +02:00
Hacker-CB cb5418e9fc Added more HEIMAN devices (#4431)
* Added HEIMAN HS2SS Smart scene switch

* Added HEIMAN HS2SW1A/HS2SW2A/HS2SW3A wall switches

* Added HEIMAN HS2IRC Smart IR Remote Control

* Added missing senosors to homeassistant for HEIMAN HS2SK

* Updated temperature -> device_temperature for genDeviceTempCfg cluster

* Fix device_temperature sensor mistake

* Fix device_temperature sensor 2nd mistake
2020-09-25 16:56:42 +02:00
Koen Kanters da2eb505fd Clear availability topic on device remove. #4432 2020-09-25 16:50:12 +02:00
qm3ster f26ddd93ff Replace "problematic" depenencies
Also one comment typo lol

`node-static` was abandoned and vulnerable, which might matter since
it's used in production usage and not development.
replaced with popular `serve-static` + `finalhandler`

`json-stable-stringify` depended on an unneeded and unlicensed
polyfill for JSON builtin.
2020-09-25 15:58:45 +02:00
potvcind f5e4859a93 Adding support for Sinope SP2600ZB Smart plug 2020-09-25 15:33:27 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> bbc7c7d5fb Update zigbee2mqtt-frontend to 0.2.14 (#4450)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-24 19:29:04 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 64a7840c4f Update zigbee2mqtt-frontend to 0.2.13 (#4448)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-24 10:49:07 +08:00
Koen Kanters b79fb2b6f5 Don't try to setup reporting for Philips devices that do not support it. https://github.com/Koenkk/zigbee-herdsman/issues/110 2020-09-22 17:42:03 +02:00
github-actions[bot] 1a51104931 Update zigbee-herdsman to 0.13.9 2020-09-22 15:19:11 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 610f77714f Update zigbee2mqtt-frontend to 0.2.10 (#4425)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-22 05:53:24 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
7a6ddc1118 Update zigbee-herdsman-converters to 12.0.191 (#4422)
* Update zigbee-herdsman-converters to 12.0.191

* 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-09-21 19:20:37 +02:00
carlos-villanueva bc91b798d2 Add support for Centralite 4200-C Smart Outlet in homeassistant.js (#4421) 2020-09-21 17:11:04 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d8b6e39c6a Update zigbee2mqtt-frontend to 0.2.9 (#4399)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-21 04:45:35 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
d2445ad2e3 Update zigbee-herdsman-converters to 12.0.190 (#4416)
* Update zigbee-herdsman-converters to 12.0.190

* 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-09-20 18:32:00 +02:00
Koen Kanters 92ce133e24 Enable scenes tests. https://github.com/Koenkk/zigbee-herdsman-converters/pull/1531 2020-09-20 18:17:36 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 8314e4d841 Update dependencies (#4409)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-20 17:25:32 +02:00
Ryan Kim(True-World) 9ab3fc902b Add Dawon DNS new 4 devices (Zigbee 3.0) (#4404)
1. 1Gang Switch with router (No neutral wire)
2. 2Gang Switch with router (No neutral wire)
3. 3Gang Switch with router (No neutral wire)
4. IoT 16A outlet
2020-09-19 19:55:00 +02:00
Hacker-CBandPavel Sokolov a33622c52b Added set of HEIMAN devices (#4395)
* Added HEIMAN HS1VS-N water leak sensor

* Added HEIMAN HS1HT-N temperature and humidity sensor

* Added HEIMAN HS3DS Door sensor

* Added HEIMAN HS1RC-N Smart remote controller

* Added/updated HEIMAN HS1EB-E Smart emergency button

* Added HEIMAN HS2AQ-EM Air quality monitor

* Fix lint error

Co-authored-by: Pavel Sokolov <pavel@tvip.ru>
2020-09-19 19:48:09 +02:00
Koen Kanters 9eba6d62db Merge branch 'master' into dev 2020-09-19 11:48:58 +02:00
Koen Kanters 0cd4607856 Fix branch-switcher not working 2020-09-19 11:48:28 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
570c6da67b Update zigbee-herdsman-converters to 12.0.189 (#4403)
* Update zigbee-herdsman-converters to 12.0.189

* 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-09-19 11:45:50 +02:00
robinaartsmaandKoen Kanters bce2b34a9b Update homeassistant.js (#4396)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-19 11:23:08 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> e9c510539a Update zigbee-herdsman to 0.13.8 (#4402)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-19 11:08:19 +02:00
Koen Kanters 206f02b200 Updates for scenes. https://github.com/Koenkk/zigbee-herdsman-converters/pull/1531 2020-09-19 11:07:54 +02:00
Koen Kanters 7fe3167e97 Fix crash when saving state.json fails. #4388 2020-09-19 10:59:12 +02:00
Koen Kanters 4f9c36ee44 Don't allow renaming to not allowed friendlyName. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-09-19 10:57:39 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> c0faae73bb Update zigbee2mqtt-frontend to 0.2.7 (#4387)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-17 21:09:54 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 9c2fd10d8f Update zigbee2mqtt-frontend to 0.2.4 (#4380)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-17 09:24:49 +08:00
KotMorderca 123c321306 Added mapping for 11830304 (#4385) 2020-09-16 22:09:14 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> e1e33a85a6 Update zigbee-herdsman to 0.13.7 (#4379)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-16 12:02:38 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d2f8e22712 Update zigbee2mqtt-frontend to 0.2.2 (#4377)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-16 06:46:34 +08:00
Koen Kanters 8db16cca3d Republish devices on OTA update. https://github.com/Koenkk/zigbee2mqtt/issues/4266 2020-09-15 22:13:30 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6234e2f40e Update zigbee-herdsman-converters to 12.0.188 (#4376)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-15 22:12:38 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b1bf87f845 Update zigbee-herdsman to 0.13.6 (#4373)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-15 20:26:19 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
1e9cb4d1cc Update zigbee-herdsman-converters to 12.0.187 (#4371)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-15 18:41:37 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d9b2e211d3 Update zigbee-herdsman to 0.13.5 (#4370)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-15 18:34:48 +02:00
Koen Kanters ba43744768 Don't discover to Home Assistant when device is still interviewing. #4363 2020-09-15 18:24:48 +02:00
Hacker-CBandPavel Sokolov a47217f1cc HEIMAN HS1VS-N Vibration sensor: added battery level reporting and tamper (#4365)
* HEIMAN HS1VS-N Vibration sensor: added battery level reporting and tamper

* Fix formatting

Co-authored-by: Pavel Sokolov <pavel@tvip.ru>
2020-09-15 16:55:19 +02:00
Hacker-CBandPavel Sokolov b4d32a9d14 Added HEIMAN HS1VS-N Vibration sensor (#4360)
Co-authored-by: Pavel Sokolov <pavel@tvip.ru>
2020-09-14 22:16:23 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> ff37b4f8de Update zigbee2mqtt-frontend to 0.1.58 (#4357)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-14 12:25:45 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 63f527e697 Update zigbee-herdsman to 0.13.4 (#4355)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-13 17:05:07 +02:00
John DoeandKoen Kanters 2caaaa8d24 Fix mallformed (nullish) messages handling (#4347)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-13 15:38:10 +02:00
John DoeandKoen Kanters 59812f1ef6 Fix device removal (#4354)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-13 15:35:33 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
508ee10f0c Update dependencies (#4348)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-13 15:21:39 +02:00
7h0mas-RandKoen Kanters d0fbde8759 added Merten MEG5113-0300/MEG5165-0000 to Home Assistant mapping (#4345)
added Merten MEG5113-0300/MEG5165-0000 to mapping

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-13 15:16:46 +02:00
Koen Kanters e631bbeb60 Merge branch 'master' into dev 2020-09-13 15:14:29 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> aad36371a0 Update zigbee2mqtt-frontend to 0.1.57 (#4350)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-13 15:50:07 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 24a7250e8d Update zigbee-herdsman-converters to 12.0.186 (#4341)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-12 16:55:17 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b432f3693a Update zigbee-herdsman to 0.13.3 (#4339)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-12 16:36:33 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
ced2c00997 Update zigbee-herdsman-converters to 12.0.185 (#4337)
* Update zigbee-herdsman-converters to 12.0.185

* 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-09-12 10:15:54 +02:00
OzGav 7d7b2fc671 Add support for NUE-ZBFLB Update homeassistant.js (#4279)
* Update homeassistant.js

* Update utils.js

* Update utils.js

* Update homeassistant.js

* Update utils.js
2020-09-12 09:45:26 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b2b30685ba Update zigbee2mqtt-frontend to 0.1.56 (#4330)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-12 05:49:09 +08:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 37acdf5a63 Update zigbee-herdsman to 0.13.2 (#4335)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-11 23:14:39 +02:00
embak bd91beb9c0 Add keypad lockout feature to TH1300ZB (#4331) 2020-09-11 17:07:21 +02:00
Koen Kanters 166f1a8b87 Update new_device_support.md 2020-09-11 17:02:47 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 8757d72467 Update zigbee2mqtt-frontend to 0.1.53 (#4327)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-10 22:12:19 +08:00
John Doe 9cabe1e23a Fix frontend api messages pollution (#4326) 2020-09-10 20:54:03 +08:00
Koen Kanters 812909363b Update issue templates 2020-09-09 19:32:28 +02:00
Koen Kanters 9ca8c31448 Add issue templates. https://github.com/Koenkk/zigbee2mqtt/issues/4306 2020-09-09 19:27:05 +02:00
Koen Kanters 5d6a5cb19f Update issue templates 2020-09-09 19:19:17 +02:00
Koen Kanters c29cc784ba Touchlink identify. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-09-09 18:24:32 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 7f29c029a3 Update zigbee-herdsman to 0.13.1 (#4319)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-09 18:17:24 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6be1d7ae7d Update zigbee2mqtt-frontend to 0.1.52 (#4318)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-09 23:54:01 +08:00
Koen Kanters 14da75f4a7 Touchlink factory reset specific device and scan. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-09-08 20:24:49 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b94c5d7283 Update zigbee-herdsman to 0.13.0 (#4310)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-08 20:24:26 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> fd60669a04 Update zigbee-herdsman-converters to 12.0.184 (#4309)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-08 20:19:55 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> fb4c75ad0a Update zigbee2mqtt-frontend to 0.1.49 (#4305)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-08 19:30:06 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 80c9a6011d Update zigbee-herdsman to 0.12.132 (#4308)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-08 19:29:51 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d0011892b1 Update zigbee2mqtt-frontend to 0.1.48 (#4303)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-08 09:46:04 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
fe56483ba2 Update zigbee-herdsman-converters to 12.0.183 (#4301)
* Update zigbee-herdsman-converters to 12.0.183

* 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-09-07 19:05:43 +02:00
Koen Kanters beef2097e4 Pass options to onEvent. https://github.com/Koenkk/zigbee2mqtt/issues/3812 2020-09-07 18:46:46 +02:00
Koen Kanters b1cfafe6b7 Add BHT-002-GCLZB to homeassistant.js 2020-09-07 17:52:12 +02:00
Koen Kanters 7b226e6efe Clear retained messages on device/group rename or delete. #4290 2020-09-07 17:29:53 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 0233cc92b0 Update zigbee2mqtt-frontend to 0.1.47 (#4288)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-06 19:41:35 +08:00
John Doe 037d54a604 Update update_frontend.yml (#4286) 2020-09-06 19:35:53 +08:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
16db8e7dbc Update dependencies (#4280)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-06 13:32:19 +02:00
Koen Kanters 02ac4b17ba Don't auto update zigbee2mqtt-frontend 2020-09-06 13:32:07 +02:00
John Doe a10e5b011c Update update_frontend.yml (#4281)
FIx job name
2020-09-06 13:27:51 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
7d028d3f80 Update zigbee-herdsman-converters to 12.0.182 (#4278)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-06 13:27:15 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
a8e8f43421 Update zigbee-herdsman to 0.12.131 (#4277)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-06 00:47:16 +02:00
Koen Kanters 4f55667760 Lint 2020-09-05 23:43:46 +02:00
Koen KantersandTomáš Martinovič 703a8c19d6 Added Immax 4in1 sensor to homeassistant.js (#4276)
Added Immax 4in1 sensor.

Co-authored-by: Tomáš Martinovič <tomas.martinovic@vsb.cz>
2020-09-05 23:39:03 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
7103ba4219 Update zigbee-herdsman-converters to 12.0.181 (#4275)
* Update zigbee-herdsman-converters to 12.0.181

* Update group.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-09-05 23:34:49 +02:00
nur 25a953bbce Add update frontend gh action 2020-09-05 15:20:22 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
c9094e98df Update zigbee-herdsman-converters to 12.0.180 (#4269)
* Update zigbee-herdsman-converters to 12.0.180

* 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-09-04 22:22:35 +02:00
Koen Kanters 778665b35b Don't rename entity ID in Home Assistant by default. #4095 2020-09-04 21:13:40 +02:00
Koen KantersandJohn Doe ba7a85bbb5 Frontend (#4232)
* Initial implementation of backend for frontend.

* Frontend fixes (#4205)

* Send data to frontend api withoud baseTopic preffix

* Send frontend api requests to mqtt

* Fix topic name sanitisation

* Fix base_topic trimming

* Update frontend.js

* Add zigbee2mqtt-frontend dependency

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>

* Dont' setup separate MQTT connection.

* Correctly stop

* Add frontend tests.

* [WIP] Bindings structure change (#4233)

* Change bindings location

* Bump frontend version

* Republish devices on bindings change

* Fix data structure

* Fix  payload double encoding

* Change endpoints structure

* Expose config to bridge/info

* Fix typo

* Updates

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>

* Resend states on ws reconnect

* Update deps

* Bump frontend (#4264)

Co-authored-by: John Doe <nurikk@users.noreply.github.com>
2020-09-04 18:42:24 +02:00
embak 84029e2057 Add OWON WSP404 Smart Plug to Home Assistant MQTT (#4260)
Device with switch, power sensor and energy measurements entities.
2020-09-04 17:30:00 +02:00
PHISON HOANGandKoen Kanters 48b502714e Add home assistant sensor config for QBKG26LM (#4258)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-09-04 17:28:12 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> cb0dee2f10 Update zigbee-herdsman to 0.12.130 (#4265)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-04 17:19:15 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 7d53408278 Update zigbee-herdsman to 0.12.129 (#4262)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-04 11:50:18 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
b428070416 Update zigbee-herdsman-converters to 12.0.179 (#4249)
* Update zigbee-herdsman-converters to 12.0.179

* 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-09-02 22:59:52 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> c707e50292 Update zigbee-herdsman to 0.12.128 (#4247)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-02 22:47:05 +02:00
Koen Kanters f57ec9f0fd Add definition to raw networkmap 2020-09-02 17:17:35 +02:00
Koen Kanters 37c4c2e1f7 Dev 2020-09-02 16:45:16 +02:00
Koen Kanters 654817a66d 1.14.4 2020-09-02 16:44:42 +02:00
Koen Kanters bb2cdd893b Undev 2020-09-02 16:44:32 +02:00
Koen Kanters 2c80ada344 Merge branch 'master' into dev 2020-09-02 16:43:47 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 9515973f9e Update zigbee-herdsman-converters to 12.0.178 (#4236)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-01 18:42:34 +02:00
Koen Kanters a057f3c171 Update stale.yml 2020-09-01 18:22:47 +02:00
Koen Kanters 27566dc5d1 Add coordinator, endpoint binds/supported clusters to bridge/devices. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-08-31 18:48:04 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> af97b65926 Update zigbee-herdsman to 0.12.127 (#4226)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-31 18:44:16 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
bdd8567229 Update zigbee-herdsman-converters to 12.0.177 (#4220)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-30 22:51:07 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
e54137c8ab Update zigbee-herdsman to 0.12.126 (#4219)
* Update zigbee-herdsman to 0.12.126

* 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-08-30 22:49:00 +02:00
nobodypb 48d5ee70c0 Return friendlyNames as answer to get_group_membership (#4210)
* Return friendlyNames as answer to get_group_membership

It would be a lot more useful, if you'd return the friendly names of the groups 😉

* Make the linter happy

* Gracefully echo warning if response is undefined

* Making friendship with the linter

Sorry, really shoud have used an IDE
2020-08-30 22:45:40 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 1390803738 Update zigbee-herdsman to 0.12.125 (#4218)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-30 22:19:54 +02:00
Koen Kanters ad0f11cc0f Base supported groups converters of its members. https://github.com/Koenkk/zigbee2mqtt/issues/764 2020-08-30 11:30:38 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 7033177917 Update dependencies (#4212)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-30 09:25:01 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 6452500993 Update zigbee-herdsman to 0.12.124 (#4209)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-29 16:22:39 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
ce6005abdd Update zigbee-herdsman-converters to 12.0.176 (#4208)
* Update zigbee-herdsman-converters to 12.0.176

* Update publish.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-29 16:14:53 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
1893ff1150 Update zigbee-herdsman-converters to 12.0.175 (#4207)
* Update zigbee-herdsman-converters to 12.0.175

* 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-08-29 14:46:23 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 50f4f95e80 Update zigbee-herdsman to 0.12.123 (#4206)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-29 14:32:27 +02:00
Koen Kanters 52b273f530 Add branch switcher ignore label. 2020-08-29 13:17:30 +02:00
PHISON HOANG 1bf49cb720 Add sensor temperature config for QBKG23LM (#4199) 2020-08-28 21:23:27 +02:00
Volodymyr Yelnikov a78d0f608a added support for Aqara Opple MX480 (#4195) 2020-08-27 19:51:46 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
9b4ef70fca Update zigbee-herdsman-converters to 12.0.174 (#4191)
* Update zigbee-herdsman-converters to 12.0.174

* 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-08-26 23:12:28 +02:00
Ivan Podogov ef93dd2151 Lonsonho 2-gang switch module discovery (#4190)
See converter here: https://github.com/Koenkk/zigbee-herdsman-converters/pull/1509
2020-08-26 22:46:13 +02:00
Ivan Podogov e3e5dcd039 Lonsonho TS110F 1-gang Home Assistant discovery (#4183) 2020-08-26 22:39:23 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
5f8b9beaf3 Update zigbee-herdsman-converters to 12.0.173 (#4182)
* Update zigbee-herdsman-converters to 12.0.173

* 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-08-25 21:48:50 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 39480c9fc2 Update zigbee-herdsman to 0.12.122 (#4181)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-25 21:41:26 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
db6d9bd5bb Update zigbee-herdsman-converters to 12.0.172 (#4180)
* Update zigbee-herdsman-converters to 12.0.172

* Update homeassistant.js

* Update zigbeeHerdsman.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-08-25 20:01:46 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
57b3511aa6 Update zigbee-herdsman-converters to 12.0.170 (#4174)
* Update zigbee-herdsman-converters to 12.0.170

* 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-08-24 23:02:01 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
85365b5d19 Update zigbee-herdsman-converters to 12.0.169 (#4172)
* Update zigbee-herdsman-converters to 12.0.169

* 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-08-23 23:19:41 +02:00
Koen Kanters ac6e4c7dae Fix coverage 2020-08-23 23:17:15 +02:00
Koen Kanters d980b97413 Fix Home Assistant automations based on click event not working. #4168 2020-08-23 22:58:07 +02:00
mgromandKoen Kanters 54f164cc21 fixes related to #4070 (#4132)
* fix

* eslint fix

* don't use temp topic when low and high temp used

* fix for temp high

* Add tests

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-23 22:36:30 +02:00
Koen Kanters 676684158a Fix messages from Xiaomi devices skipped sometimes when send through Xiaomi router. #3592 (#4170)
* Update receive.js

* Fix messages from Xiaomi devices skipped sometimes when send through Xiaomi router. https://github.com/Koenkk/zigbee2mqtt/issues/3592
2020-08-23 22:05:48 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 9e150efce4 Update zigbee-herdsman to 0.12.121 (#4169)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-23 22:01:48 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d22f3b8b1f Update dependencies (#4164)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-23 21:20:16 +02:00
Fabian AffolterandKoen Kanters 9f1551181b Remove Hass.io (#4159)
Hass.io was renamed to Home Assistant

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-22 17:36:18 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
1d7b5832ca Update zigbee-herdsman-converters to 12.0.168 (#4158)
* Update zigbee-herdsman-converters to 12.0.168

* 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-08-22 13:29:09 +02:00
mog422andKoen Kanters e6f8cee968 Add TPZRCO2HT-Z3 homeassistant mapping (#4155)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-22 12:24:28 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> f15b314634 Update zigbee-herdsman to 0.12.120 (#4156)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-22 12:21:00 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
bffe4db029 Update zigbee-herdsman-converters to 12.0.167 (#4144)
* Update zigbee-herdsman-converters to 12.0.167

* Update homeassistant.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-08-20 22:03:00 +02:00
Koen Kanters 0b5d169b46 Provide state to fromZigbee converter 2020-08-20 21:54:59 +02:00
Koen Kanters 22f657ed4d Add device_announce event to new api. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-08-20 20:55:40 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 57729c140e Update zigbee-herdsman to 0.12.119 (#4141)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-20 16:34:12 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
d69c2c947a Update zigbee-herdsman-converters to 12.0.166 (#4136)
* Update zigbee-herdsman-converters to 12.0.166

* 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-08-19 22:30:51 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
e780dbcb82 Update zigbee-herdsman-converters to 12.0.165 (#4126)
* Update zigbee-herdsman-converters to 12.0.165

* 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-08-18 22:35:48 +02:00
Koen Kanters a20c6728b8 Also use state converter when value is open, close, stop, lock and unlock. #4123 2020-08-18 21:27:36 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
b7409295b5 Update zigbee-herdsman-converters to 12.0.164 (#4121)
* Update zigbee-herdsman-converters to 12.0.164

* 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-08-17 22:22:18 +02:00
Koen Kanters 0f6540c5fc Fix typo https://github.com/Koenkk/zigbee2mqtt/commit/9148977b3968b609add29807a780acd469dbed89#r41534643 2020-08-17 22:14:10 +02:00
KrisandKoen Kanters 5411de8618 Add Philips Hue Nyro (#4120)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-17 22:08:52 +02:00
Koen Kanters b80695cd18 Merge branch 'master' into dev 2020-08-17 22:08:17 +02:00
Koen Kanters 0db3f3ca0b Remove branch-cleanup 2020-08-17 21:52:47 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 30044bc8da Update zigbee-herdsman to 0.12.118 (#4118)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-17 21:51:59 +02:00
Koen Kanters 8c5fae900e Don't use state converter on invalid JSON and the payload is not on, off or toggle. https://github.com/Koenkk/zigbee2mqtt/issues/1348 2020-08-17 21:29:11 +02:00
Koen Kanters d167b149ad Refactor device options retrieval 2020-08-17 20:57:12 +02:00
Koen Kanters 9148977b39 Rename device entity_id in Home Assistant when changing friendly_name. #4096 2020-08-17 20:19:44 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
176d32b2de Update dependencies (#4114)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-17 17:32:01 +02:00
Koen Kanters 5f75c9c9f8 Clear outdated Home Assistant configs 2020-08-17 17:24:57 +02:00
felix schwenzel ffe810a471 Update homeassistant.js (#4113)
add MAXI JOLLY ZB3 LED driver, see https://github.com/Koenkk/zigbee-herdsman-converters/pull/1475.
2020-08-17 13:57:42 +02:00
Koen Kanters 410f6a0f95 Fix update state sensor object_id. 2020-08-14 13:48:19 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 4f27813d96 Update zigbee-herdsman-converters to 12.0.163 (#4100)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-14 11:51:22 +02:00
Koen Kanters 082c5d47be Merge branch 'master' into dev 2020-08-14 11:44:07 +02:00
mgromandKoen Kanters d3a22d49c1 Added presets (hold_modes) to climate sensor #3821 (#4070)
* Added presets (hold_modes) to climate sensor #3821

and removed thermostatHeatCool as it's partially redundant

* Added new configuration to TuYa thermostat TS0601_thermostat

* eslint fixes

* Added eco and comfort modes. Looks like they are usable.

* Update homeassistant.js

* Update homeassistant.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-14 11:43:52 +02:00
Koen Kanters 1213fd6ea6 Remove autosquash and add branch-cleanup 2020-08-14 11:43:35 +02:00
Koen Kanters c6ed37ab47 Switch to autosquash 2020-08-14 11:27:18 +02:00
Koen Kanters 43730cbbd1 Merge branch 'master' into dev 2020-08-14 11:23:04 +02:00
Koen Kanters 146a015455 Add rebase workflow 2020-08-14 11:22:08 +02:00
TomandKoen Kanters 08524953bf Add Environment Variable Override (#4085)
* Add Environment Variable Override

I need to be able to manage settings on this service via Environment Variables.   I've added the ability to override anything in the settings schema with a corresponding environment variable.  

e.g. to override settings.serial.port you can set ZIGBEE2MQTT_SERIAL_PORT=/dev/ttyS0
to override the mqtt username you can set ZIGBEE2MQTT_MQTT_USER=testusername

This new addition will not perform any action on existing installations unless the matching environment variable is set.  I have tested this setting string, number, boolean, object and array values.

* Adding test case for environment variables and slight modification to ensure 100% code coverage.

* Adding a test to confirm that env variables will set non default values.  Also realized that I was errantly applying the env variables to the defaults for testing.  Understanding what this is doing more clearly I realize that should be clean.

* Refactoring to

1. Remove the test variables from the schema and defaults and manually reflect the tests in the test.

2. Rename environment variable base from ZIGBEE2MQTT_ to ZIGBEE2MQTT_CONFIG_

* Small improvements

* Removing the unneeded test.

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-13 23:50:30 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
5de279c20e Update zigbee-herdsman-converters to 12.0.162 (#4097)
* Update zigbee-herdsman-converters to 12.0.162

* 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-08-13 21:57:36 +02:00
Koen Kanters 9774031169 Sort logged and published JSON objects alphabetically. #4091 2020-08-13 20:00:35 +02:00
Koen Kanters c66e65e0fb Merge branch 'master' into dev 2020-08-12 17:36:56 +02:00
Koen Kanters f8066e8da8 1.14.3 2020-08-12 17:36:28 +02:00
Koen Kanters ec28738784 Fix npm repo url. 2020-08-12 17:36:24 +02:00
Koen Kanters 8398bcb51d Revert "1.14.3"
This reverts commit c0143cb40d.
2020-08-12 17:35:22 +02:00
blakadder 3acd123ecd add direction sensor to AD for some dimmers (#4078) 2020-08-12 17:22:05 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 763ae7903e Update zigbee-herdsman to 0.12.117 (#4089)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-12 17:13:14 +02:00
Koen Kanters 66902b963c Dev 2020-08-12 17:07:19 +02:00
Koen Kanters c0143cb40d 1.14.3 2020-08-12 16:58:19 +02:00
Koen Kanters e4c939a643 Undev 2020-08-12 16:58:16 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
4f1a0ae9cd Update zigbee-herdsman-converters to 12.0.161 (#4083)
* Update zigbee-herdsman-converters to 12.0.161

* Update homeassistant.js

* 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-08-11 21:38:19 +02:00
Koen Kanters fb2e149a41 Merge branch 'master' into dev 2020-08-11 21:27:18 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 29f90b77b4 Update zigbee-herdsman to 0.12.116 (#4084)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-11 21:26:38 +02:00
Koen Kanters 954b891698 Only run Docker login/setup on master tag push 2020-08-10 21:54:20 +02:00
Koen Kanters 7ea27b51e2 Merge branch 'master' into dev 2020-08-10 21:50:52 +02:00
Koen Kanters 02e198eca2 Use GitHub Actions badge 2020-08-10 21:50:45 +02:00
Koen Kanters e5e8c58903 Remove azure-pipelines 2020-08-10 21:48:30 +02:00
Koen Kanters aebef20536 Merge branch 'master' into dev 2020-08-10 21:45:20 +02:00
Koen Kanters a519577cf7 Migrate from Azure to Github actions. 2020-08-10 21:44:05 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
f9076b362d Update zigbee-herdsman-converters to 12.0.160 (#4075)
* Update zigbee-herdsman-converters to 12.0.160

* Update homeassistant.js

* Update publish.js

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-10 20:44:44 +02:00
Koen Kanters c2e28eaee1 Switch to Github actions stale 2020-08-10 20:43:50 +02:00
ChamondglandKoen Kanters 14712d6fe1 Add Home Assistant discovery for ROB_200-010-0 (#4074)
Robb Smarrt Zigbee Curtain Motor Controller ROB_200-010-0

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-08-10 20:31:40 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
ff0b812029 Update zigbee-herdsman-converters to 12.0.159 (#4069)
* Update zigbee-herdsman-converters to 12.0.159

* 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-08-09 22:34:13 +02:00
Koen Kanters afbbf70caa Fix null published for attribute output. https://github.com/Koenkk/zigbee2mqtt/issues/4054 2020-08-09 11:31:27 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> f85722da38 Update dependencies (#4065)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-09 11:23:49 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> e97fc903a5 Update zigbee-herdsman to 0.12.115 (#4063)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-08 16:38:20 +02:00
Koen Kanters 81db31b784 Fix attribute output not publishing all values. https://github.com/Koenkk/zigbee2mqtt/issues/4054 2020-08-08 16:36:58 +02:00
Koen Kanters ff2fe2b358 Separate Home Assisant name by space instead of _ when friendlyName contains space. #4048 2020-08-08 13:54:39 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> fbecb7e0d2 Update zigbee-herdsman-converters to 12.0.158 (#4058)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-07 17:48:48 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
1198a2d9ac Update zigbee-herdsman-converters to 12.0.157 (#4051)
* Update zigbee-herdsman-converters to 12.0.157

* 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-08-05 23:01:17 +02:00
mateusz-dziurdziakandMateusz Dziurdziak 7b74971006 Add TuYa SNTZ009 water leak sensor (#4045)
Co-authored-by: Mateusz Dziurdziak <mateusz.dziurdziak@fara.no>
2020-08-05 22:10:29 +02:00
Koen Kanters 3cdf7604a0 Fix no color_temp for group on changing color_xy and vice versa. #4034 2020-08-05 21:54:39 +02:00
Koen Kanters 516d53e9df Fix reporting being disabled every time IKEA device announces. https://github.com/Koenkk/zigbee2mqtt/issues/3826 2020-08-05 16:38:40 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
6c6dfc85cd Update zigbee-herdsman-converters to 12.0.156 (#4043)
* Update zigbee-herdsman-converters to 12.0.156

* 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-08-04 20:10:34 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 8a6a7409be Update zigbee-herdsman to 0.12.114 (#4042)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-04 19:32:38 +02:00
Koen Kanters 6345cb07bf Disable reporting on device when reporting is disabled. https://github.com/Koenkk/zigbee2mqtt/issues/3826 2020-08-04 19:11:13 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 35be95115d Update zigbee-herdsman to 0.12.113 (#4031)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-03 21:06:56 +02:00
Koen Kanters 852bca5a08 Implement update state for new api. https://github.com/Koenkk/zigbee2mqtt/issues/3634 https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-08-02 23:09:43 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
8aa3cf0c29 Update zigbee-herdsman-converters to 12.0.155 (#4025)
* Update zigbee-herdsman-converters to 12.0.155

* 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-08-02 10:51:51 +02:00
Koen Kanters 3e071a73df Invert cover tests. https://github.com/Koenkk/zigbee-herdsman-converters/issues/1447 2020-08-02 10:08:55 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b4cfa7b0b7 Update dependencies (#4023)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-02 10:02:13 +02:00
Koen Kanters 9a6397fe75 Make Zigbee2MQTT naming consistent 2020-08-01 10:36:20 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 22c24a16b3 Update zigbee-herdsman to 0.12.112 (#4021)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-08-01 09:39:00 +02:00
Zhongfu LiandKoen Kanters 3b29266be3 Fix Home Assistant auto-discovery for QBKG22LM, QBKG25LM (#3977)
* Update configuration for QBKG22LM, QBKG25LM

* Update homeassistant.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-07-31 17:59:24 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
0fddfc9ffa Update zigbee-herdsman-converters to 12.0.154 (#4018)
* Update zigbee-herdsman-converters to 12.0.154

* 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-07-30 23:19:05 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> b2e588c1cf Update zigbee-herdsman to 0.12.111 (#4017)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-07-30 22:58:06 +02:00
lionel590andKoen Kanters ded3a58883 Network parameters / config displayed in zigbee2mqtt/bridge/config (#4010)
* Update zigbee.js

* added network parameters to zigbee2mqtt/bridge/config

* added network parameters to zigbee2mqtt/bridge/config

* added test for network config in zigbee2mqtt/bridge/config

* Fix network data

* Added test data for network parameters

* Update zigbee.js

* Update zigbee.js

* Update bridge.test.js

* Update bridge.test.js

* Update utils.js

* Update bridge.js

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-07-30 22:31:52 +02:00
Sebastian Karasek a5b03b5dad Add home-assistant mapping for NLG Q-FRAMELESS (#4012) 2020-07-30 22:19:01 +02:00
Matthew Garrett 469ddb7e76 Add support for Orvibo AM25 blinds (#4011) 2020-07-30 22:14:51 +02:00
Artyom Dudarev 64022897f8 Add support for Lonsonho X713A 3 gang switch (#4015) 2020-07-30 22:12:39 +02:00
Koen Kanters 274109bc85 Allow to disable Home Assistant via MQTT. #3281 (#4007)
* Initial

* Updates
2020-07-29 23:10:03 +02:00
Koen Kanters b9c52ef96e Fix exception in Home Assistant extension when click or action is null/undefined. #4005 2020-07-29 22:08:00 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
9474dfe249 Update zigbee-herdsman-converters to 12.0.153 (#4006)
* Update zigbee-herdsman-converters to 12.0.153

* 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-07-29 17:23:23 +02:00
Koen Kanters a7f990ef95 New api: log to MQTT. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-07-28 22:12:22 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
ae93e6fd0f Update zigbee-herdsman-converters to 12.0.152 (#4004)
* Update zigbee-herdsman-converters to 12.0.152

* 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-07-28 22:06:30 +02:00
Koen Kanters 946ebe16b6 Allow to specify which clusters are being bound/unbound. https://github.com/Koenkk/zigbee2mqtt/issues/3281 2020-07-28 21:05:02 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> a0c895a88d Update zigbee-herdsman to 0.12.110 (#4002)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-07-28 20:07:59 +02:00
Koen Kanters 44b26f9b22 Parse attribute payload to JSON if possible. https://github.com/Koenkk/zigbee-herdsman-converters/pull/1427 2020-07-27 23:04:59 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> ef6572dfd0 Update zigbee-herdsman-converters to 12.0.151 (#3994)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-07-27 22:59:07 +02:00
Diego Rodríguez 7c6fe4d95e Add voltage to ZNCZ02LM (#3991) 2020-07-27 22:33:15 +02:00
Hamish MoffattandHamish Moffatt f5e5c76ca9 output plain text to syslog (#3985)
* output plain text to syslog (#3984)

* fix lint warnings

Co-authored-by: Hamish Moffatt <hamish@moffatt.id.au>
2020-07-27 22:22:02 +02:00
Koen Kanters 0d9ed11ceb Merge branch 'master' into dev 2020-07-27 22:21:00 +02:00
Koen Kanters b9ba635bea Fix crash when attribute value is undefined of null (publish empty payload in this case). #3980 2020-07-27 22:07:03 +02:00
iharyadiandKoen Kanters 88521b25d3 Add HA model mapping (#3974)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2020-07-26 14:28:17 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> efc971d2ca Update zigbee-herdsman-converters to 12.0.150 (#3979)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-07-26 14:22:36 +02:00
Koen Kanters 7d27e6db69 Changes for zigbee-herdsman-converters 12.0.150 2020-07-26 14:19:33 +02:00
Koen Kanters 812c4927e7 Handle group setup failure. https://github.com/Koenkk/zigbee2mqtt/issues/3966 2020-07-26 10:11:58 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> d7b678e636 Update dependencies (#3976)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-07-26 09:22:56 +02:00
Koen Kanters 00f836fce6 Don't discover sensor_click when legacy: false is set. https://github.com/Koenkk/zigbee-herdsman-converters/issues/1393 2020-07-24 22:53:10 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
992ebafc63 Update zigbee-herdsman-converters to 12.0.149 (#3963)
* Update zigbee-herdsman-converters to 12.0.149

* 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-07-23 21:06:13 +02:00
senax f0068d73cb Added 1746330P7/Philips Hue Appear (#3962) 2020-07-23 20:31:19 +02:00
Koen Kanters 867918bd63 Fix incorrect device type. https://github.com/Koenkk/zigbee2mqtt/issues/3953 2020-07-23 19:55:56 +02:00
github-actions[bot]andgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 1fa547437f Update zigbee-herdsman to 0.12.109 (#3960)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-07-23 19:51:03 +02:00
Koen Kanters ad13564687 Improve error messages when configuration is invalid. #3943 2020-07-23 19:17:26 +02:00
Koen Kanters b91b456f98 Include zigbee-herdsman-(converters) version in pull request title. 2020-07-22 23:34:35 +02:00
github-actions[bot]github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Koen Kanters
a01048487d Update zigbee-herdsman-converters to 12.0.148
* Update zigbee-herdsman-converters to 12.0.148

* 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-07-22 23:32:38 +02:00
Koen Kanters 2d3ab1cf8a Promote to dev 2020-07-22 22:19:15 +02:00
79 changed files with 16677 additions and 7706 deletions
+3 -3
View File
@@ -3,7 +3,7 @@ Before submitting an issue make sure you've searched for a similar issue and rea
Rules (don't ignore these, your issue will be closed without further notice):
- English only
- Make sure you are on the latest Zigbee2mqtt version
- Make sure you are on the latest Zigbee2MQTT version
- Provide a clear description of the problem
- DON'T copy logs directly here, post a link to https://hastebin.com/ or https://pastebin.com/.
- Make sure you are running the latest firmware https://github.com/koenkk/z-stack-firmware.
@@ -11,7 +11,7 @@ Rules (don't ignore these, your issue will be closed without further notice):
Did you read the FAQ?
- https://www.zigbee2mqtt.io/information/FAQ.html
Zigbee2mqtt fails to start?
Zigbee2MQTT fails to start?
- https://www.zigbee2mqtt.io/information/FAQ.html#help-zigbee2mqtt-fails-to-start
Having issues when using a CC2531?
@@ -41,6 +41,6 @@ Bug report?
## How to reproduce it (minimal and precise)
## Debug Info
Zigbee2mqtt version:
Zigbee2MQTT version:
Adapter hardware: CC2531, CC2530, CC26X2R1 or CC1352P-2
Adapter firmware version:
+17
View File
@@ -0,0 +1,17 @@
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
about: Issues related to the Home Assistant addon.
- name: FAQ
url: https://www.zigbee2mqtt.io/information/FAQ.html
about: Frequently asked questions.
- name: Support Chat
url: https://discord.gg/NyseBeK
about: Chat for feedback, questions and troubleshooting.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
## Is your feature request related to a problem? Please describe
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
## Describe the solution you'd like
A clear and concise description of what you want to happen.
## Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
## Additional context
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,21 @@
---
name: New device support request
about: Request support for a new device
title: ''
labels: new device support
assignees: ''
---
<!--
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
..
## data/database.db entry of the device
..
+54
View File
@@ -0,0 +1,54 @@
---
name: Problem report
about: Create a report to help us improve
title: ''
labels: problem
assignees: ''
---
<!--
Before submitting an issue make sure you've searched for a similar issue and read the documentation: https://www.zigbee2mqtt.io/.
Rules (don't ignore these, your issue will be closed without further notice):
- English only
- Make sure you are on the latest Zigbee2MQTT version
- Provide a clear description of the problem
- DON'T copy logs directly here, post a link to https://hastebin.com/ or https://pastebin.com/.
- Make sure you are running the latest firmware https://github.com/koenkk/z-stack-firmware.
Did you read the FAQ?
- https://www.zigbee2mqtt.io/information/FAQ.html
Zigbee2MQTT fails to start?
- https://www.zigbee2mqtt.io/information/FAQ.html#help-zigbee2mqtt-fails-to-start
Having issues when using a CC2531?
- Make sure the CC2531 is connected through a USB extension cable
- Try the source routing firmware: https://github.com/Koenkk/Z-Stack-firmware/tree/master/coordinator/Z-Stack_Home_1.2/bin/source_routing
- With larger networks (30/40+) devices, your CC2531 might not be powerful enough. This will cause weird issues, in this case it's advised to use a more powerful adapter: https://www.zigbee2mqtt.io/information/supported_adapters.html#texas-instruments-cc26x2r1
Unsupported device?
- https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html
Device does not pair or interview fails?
- https://www.zigbee2mqtt.io/information/FAQ.html#why-does-my-device-not-or-fail-to-pair
Bug report?
- If applicable, provide steps how to reproduce the problem.
- Provide the herdsman debug logging: https://www.zigbee2mqtt.io/information/debug.html#zigbee-herdsman-debug-logging
-->
<!--Start your bug report below this line-->
## What happened
## What did you expect to happen
## How to reproduce it (minimal and precise)
## Debug info
Zigbee2MQTT version:
Adapter hardware: CC2531, CC2530, CC26X2R1, CC1352P-2, Conbee II
Adapter firmware version:
-13
View File
@@ -1,13 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+60 -1
View File
@@ -10,6 +10,7 @@ jobs:
- uses: actions/setup-node@v2-beta
with:
node-version: 12
registry-url: https://registry.npmjs.org/
- name: Get NodeJS Version
id: nodeversion
run: |
@@ -26,4 +27,62 @@ jobs:
- name: Test
run: npm run test-with-coverage
- name: Lint
run: npm run eslint
run: npm run eslint
- 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 setup - QEMU
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Docker setup - Buildx
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
id: buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
- name: Docker build dev
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
docker buildx build \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest-dev \
.
- name: Docker build release
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: |
TAG="$(git describe --tags)"
docker buildx build \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest -t "koenkk/zigbee2mqtt:$TAG" \
.
- name: Publish to npm
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN }}
- name: Trigger danielwelch/hassio-zigbee2mqtt build
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
curl -s \
--request POST \
--header "Content-Type: application/json" \
--data '{"definition": {"id": 1}}' \
-u ${{ secrets.HASSIO_TRIGGER_SECRET }} \
"https://dev.azure.com/danielwelch2101/hassio-zigbee2mqtt/_apis/build/builds?api-version=5.1"
- name: Trigger zigbee2mqtt/hassio-zigbee2mqtt build
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
curl -s \
--request POST \
--header "Content-Type: application/json" \
--data '{"definition": {"id": 9}}' \
-u ${{ secrets.HASSIO_TRIGGER_SECRET }} \
"https://dev.azure.com/zigbee2mqtt/Zigbee2mqtt%20Add-on/_apis/build/builds?api-version=5.1"
+19
View File
@@ -0,0 +1,19 @@
name: "Close stale issues/pull requests"
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days'
stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days'
days-before-stale: 30
days-before-close: 7
operations-per-run: 500
exempt-issue-labels: 'breaking change'
+2 -2
View File
@@ -16,9 +16,9 @@ jobs:
with:
node-version: 12
- run: npm install
- run: npm ls --depth 0 --prod --parseable | tail -n +2 | grep -v "zigbee-herdsman" | xargs -I '{}' basename "{}" | xargs -I '{}' npm install '{}'@latest
- 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"
+22
View File
@@ -0,0 +1,22 @@
name: Update zigbee-frontend
on:
repository_dispatch:
types: update_frontend
jobs:
update_frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: dev
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
with:
node-version: 12
- run: npm install zigbee2mqtt-frontend@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v3
with:
commit-message: Update zigbee2mqtt-frontend to ${{ github.event.client_payload.version }}
branch: "update_frontend"
title: Update zigbee-frontend to ${{ github.event.client_payload.version }}
+2 -2
View File
@@ -15,8 +15,8 @@ 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"
title: Update zigbee-herdsman
title: Update zigbee-herdsman to ${{ github.event.client_payload.version }}
+2 -2
View File
@@ -15,8 +15,8 @@ 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"
title: Update zigbee-herdsman-converters
title: Update zigbee-herdsman-converters to ${{ github.event.client_payload.version }}
+4 -1
View File
@@ -70,4 +70,7 @@ data/database.db.backup
data/extension
# MacOS indexing file
.DS_Store
.DS_Store
# Ignore config
data/*.yaml
-1
View File
@@ -7,7 +7,6 @@ coverage
.jenkins.yml
.codeclimate.yml
.github
azure-pipelines.yaml
#linters
.jscsrc
-5
View File
@@ -1,5 +0,0 @@
{
"files.associations": {
"*.yaml": "home-assistant"
}
}
+6 -6
View File
@@ -5,8 +5,8 @@
<br>
<br>
<div style="display: flex;">
<a href="https://dev.azure.com/koenkk/zigbee/_build?definitionId=5">
<img src="https://dev.azure.com/koenkk/zigbee/_apis/build/status/Koenkk.zigbee2mqtt?branchName=dev">
<a href="https://github.com/Koenkk/zigbee2mqtt/actions?query=workflow%3ACI">
<img src="https://github.com/koenkk/zigbee2mqtt/workflows/CI/badge.svg">
</a>
<a href="https://github.com/Koenkk/zigbee2mqtt/releases">
<img src="https://img.shields.io/github/release/koenkk/zigbee2mqtt.svg">
@@ -33,7 +33,7 @@
<img src="https://img.shields.io/npm/v/zigbee2mqtt">
</a>
</div>
<h1>Zigbee2mqtt 🌉 🐝</h1>
<h1>Zigbee2MQTT 🌉 🐝</h1>
<p>
Allows you to use your Zigbee devices <b>without</b> the vendors bridge or gateway.
</p>
@@ -48,12 +48,12 @@ The [documentation](https://www.zigbee2mqtt.io/) provides you all the informatio
If you aren't familiar with **Zigbee** terminology make sure you [read this](https://www.zigbee2mqtt.io/information/zigbee_network.html) to help you out.
## [Integrations](https://www.zigbee2mqtt.io/#integration)
Zigbee2mqtt integrates well with (almost) every home automation solution because it uses MQTT. However the following integrations are worth mentioning:
Zigbee2MQTT integrates well with (almost) every home automation solution because it uses MQTT. However the following integrations are worth mentioning:
<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/)
- [Hassio](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/danielwelch/hassio-zigbee2mqtt) from [danielwelch](https://github.com/danielwelch)
- 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.
-88
View File
@@ -1,88 +0,0 @@
trigger:
tags:
include:
- '*'
branches:
include:
- '*'
pool:
vmImage: 'ubuntu-latest'
steps:
# Prepare
- task: NodeTool@0
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- bash: npm ci
displayName: 'Install dependencies'
# Verify
- bash: npm run test-with-coverage
displayName: 'Test'
- bash: npm run eslint
displayName: 'Lint'
# NPM
- task: Npm@1
inputs:
command: publish
publishEndpoint: npm
displayName: 'Publish to npm'
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'), ne(variables['Build.Reason'], 'PullRequest'))
# Docker
- task: DockerInstaller@0
displayName: Docker install
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
inputs:
dockerVersion: 19.03.5
releaseType: stable
- task: Docker@2
displayName: Docker registry login
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
inputs:
command: login
containerRegistry: docker_registry
- bash: |
sudo wget -O /usr/local/bin/buildx https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-amd64
sudo chmod a+x /usr/local/bin/buildx
docker run --rm --privileged hypriot/qemu-register:v2.7.0
buildx create --use
buildx ls
displayName: 'Docker setup'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
- bash: |
buildx build \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest-dev \
.
displayName: 'Docker build dev'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/dev'), ne(variables['Build.Reason'], 'PullRequest'))
- bash: |
TAG="$(git describe --tags)"
buildx build \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest -t "koenkk/zigbee2mqtt:$TAG" \
.
displayName: 'Docker build release'
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'), ne(variables['Build.Reason'], 'PullRequest'))
# Trigger Home Assistant Zigbee2mqtt addon build
- bash: |
curl -s \
--request POST \
--header "Content-Type: application/json" \
--data '{"definition": {"id": 1}}' \
-u "$(HASSIO_TRIGGER_SECRET)" \
"https://dev.azure.com/danielwelch2101/hassio-zigbee2mqtt/_apis/build/builds?api-version=5.1"
displayName: 'Trigger Hass.io build'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/dev'), ne(variables['Build.Reason'], 'PullRequest'))
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-alpine as base
FROM node:12-alpine3.12 as base
WORKDIR /app
RUN apk add --no-cache tzdata eudev
@@ -10,7 +10,7 @@ FROM base as dependencies
COPY npm-shrinkwrap.json ./
RUN apk add --no-cache --virtual .buildtools make gcc g++ python linux-headers git && \
RUN apk add --no-cache --virtual .buildtools make gcc g++ python3 linux-headers git && \
npm ci --production && \
apk del .buildtools
Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 76 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Executable → Regular
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 81 KiB

+1 -146
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+16 -1
View File
@@ -3,7 +3,22 @@ const engines = require('./package.json').engines;
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
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');
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`);
console.log(`\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n`);
process.exit(1);
}
const Controller = require('./lib/controller');
+75 -54
View File
@@ -6,11 +6,14 @@ const logger = require('./util/logger');
const settings = require('./util/settings');
const objectAssignDeep = require('object-assign-deep');
const utils = require('./util/utils');
const stringify = require('json-stable-stringify-without-jsonify');
const fs = require('fs');
const data = require('./util/data');
const path = require('path');
const assert = require('assert');
// Extensions
const ExtensionFrontend = require('./extension/frontend');
const ExtensionPublish = require('./extension/publish');
const ExtensionReceive = require('./extension/receive');
const ExtensionNetworkMap = require('./extension/networkMap');
@@ -28,6 +31,13 @@ const ExtensionOnEvent = require('./extension/onEvent');
const ExtensionOTAUpdate = require('./extension/otaUpdate');
const ExtensionExternalConverters = require('./extension/externalConverters');
const AllExtensions = [
ExtensionPublish, ExtensionReceive, ExtensionNetworkMap, ExtensionSoftReset, ExtensionHomeAssistant,
ExtensionConfigure, ExtensionDeviceGroupMembership, ExtensionBridgeLegacy, ExtensionBridge, ExtensionGroups,
ExtensionAvailability, ExtensionBind, ExtensionReport, ExtensionOnEvent, ExtensionOTAUpdate,
ExtensionExternalConverters,
];
class Controller {
constructor() {
this.zigbee = new Zigbee();
@@ -36,11 +46,13 @@ class Controller {
this.state = new State(this.eventBus);
this.publishEntityState = this.publishEntityState.bind(this);
this.enableDisableExtension = this.enableDisableExtension.bind(this);
this.onZigbeeAdapterDisconnected = this.onZigbeeAdapterDisconnected.bind(this);
// Initialize extensions.
const args = [this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus];
this.extensions = [
new ExtensionBridge(...args, this.enableDisableExtension),
new ExtensionPublish(...args),
new ExtensionReceive(...args),
new ExtensionDeviceGroupMembership(...args),
@@ -50,20 +62,17 @@ class Controller {
new ExtensionBind(...args),
new ExtensionOnEvent(...args),
new ExtensionOTAUpdate(...args),
new ExtensionReport(...args),
];
if (settings.get().experimental.new_api) {
this.extensions.push(new ExtensionBridge(...args));
if (settings.get().frontend) {
this.extensions.push(new ExtensionFrontend(...args));
}
if (settings.get().advanced.legacy_api) {
this.extensions.push(new ExtensionBridgeLegacy(...args));
}
if (settings.get().advanced.report) {
this.extensions.push(new ExtensionReport(...args));
}
if (settings.get().homeassistant) {
this.extensions.push(new ExtensionHomeAssistant(...args));
}
@@ -91,22 +100,11 @@ class Controller {
}
async start() {
const settingsErrors = settings.validate();
if (settingsErrors) {
logger.error(`Refusing to start, configuration.yaml is not valid, found the following errors:`);
for (const error of settingsErrors) {
logger.error(`\t - ${error}`);
}
logger.error(
`If you don't know how to solve this, read https://www.zigbee2mqtt.io/configuration/configuration.html`,
);
process.exit(1);
}
this.state.start();
logger.logOutput();
const info = await utils.getZigbee2mqttVersion();
logger.info(`Starting zigbee2mqtt version ${info.version} (commit #${info.commitHash})`);
logger.info(`Starting Zigbee2MQTT version ${info.version} (commit #${info.commitHash})`);
// Start zigbee
try {
@@ -126,14 +124,10 @@ class Controller {
logger.info(`Currently ${devices.length} devices are joined:`);
for (const device of devices) {
const entity = this.zigbee.resolveEntity(device);
logger.info(
(entity.settings ? entity.settings.friendlyName : entity.device.ieeeAddr) +
` (${entity.device.ieeeAddr}): ` +
(entity.definition ?
`${entity.definition.model} - ${entity.definition.vendor} ${entity.definition.description} ` :
'Not supported ') +
`(${entity.device.type})`,
);
const model = entity.definition ?
`${entity.definition.model} - ${entity.definition.vendor} ${entity.definition.description}` :
'Not supported';
logger.info(`${entity.name} (${entity.device.ieeeAddr}): ${model} (${entity.device.type})`);
}
// Enable zigbee join.
@@ -173,6 +167,23 @@ class Controller {
await this.callExtensionMethod('onMQTTConnected', []);
}
async enableDisableExtension(enable, name) {
if (!enable) {
const extension = this.extensions.find((e) => e.constructor.name === name);
if (extension) {
await this.callExtensionMethod('stop', [], [extension]);
this.extensions.splice(this.extensions.indexOf(extension), 1);
}
} else {
const Extension = AllExtensions.find((e) => e.name === name);
assert(Extension, `Extension '${name}' does not exist`);
const extension = new Extension(this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus);
this.extensions.push(extension);
this.callExtensionMethod('onZigbeeStarted', [], [extension]);
this.callExtensionMethod('onMQTTConnected', [], [extension]);
}
}
async stop() {
// Call extensions
await this.callExtensionMethod('stop', []);
@@ -197,9 +208,14 @@ class Controller {
async onZigbeeEvent(type, data) {
const resolvedEntity = this.zigbee.resolveEntity(data.device || data.ieeeAddr);
if (data.device && !resolvedEntity.settings && 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.addDevice(data.device.ieeeAddr);
resolvedEntity.settings = {...settings.get().device_options, ...settings.addDevice(data.device.ieeeAddr)};
}
const name = resolvedEntity && resolvedEntity.settings ? resolvedEntity.settings.friendlyName : null;
@@ -207,7 +223,7 @@ class Controller {
if (type === 'message') {
logger.debug(
`Received Zigbee message from '${name}', type '${data.type}', cluster '${data.cluster}'` +
`, data '${JSON.stringify(data.data)}' from endpoint ${data.endpoint.ID}` +
`, data '${stringify(data.data)}' from endpoint ${data.endpoint.ID}` +
(data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``),
);
} else if (type === 'deviceJoined') {
@@ -221,7 +237,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`,
);
}
@@ -239,6 +256,7 @@ class Controller {
/* istanbul ignore else */
if (type === 'deviceLeave') {
logger.warn(`Device '${name || data.ieeeAddr}' left the network`);
this.state.remove(data.ieeeAddr);
}
}
@@ -274,13 +292,12 @@ class Controller {
messagePayload = newState;
}
const deviceOptions = settings.get().device_options;
const options = {
retain: utils.getObjectsProperty([resolvedEntity.settings, deviceOptions], 'retain', false),
qos: utils.getObjectsProperty([resolvedEntity.settings, deviceOptions], 'qos', 0),
retain: utils.getObjectProperty(resolvedEntity.settings, 'retain', false),
qos: utils.getObjectProperty(resolvedEntity.settings, 'qos', 0),
};
const retention = utils.getObjectsProperty([resolvedEntity.settings, deviceOptions], 'retention', false);
const retention = utils.getObjectProperty(resolvedEntity.settings, 'retention', false);
if (retention !== false) {
options.properties = {messageExpiryInterval: retention};
}
@@ -300,30 +317,30 @@ class Controller {
attributes.forEach((a) => messagePayload.device[a] = resolvedEntity.device[a]);
}
// Add lastseen
const lastSeen = settings.get().advanced.last_seen;
if (isDevice && lastSeen !== 'disable' && resolvedEntity.device.lastSeen) {
messagePayload.last_seen = utils.formatDate(resolvedEntity.device.lastSeen, lastSeen);
}
// filter mqtt message attributes
if (deviceOptions.filtered_attributes) {
deviceOptions.filtered_attributes.forEach((a) => delete messagePayload[a]);
// Add device linkquality.
if (resolvedEntity.type === 'device' && resolvedEntity.device.linkquality !== undefined) {
messagePayload.linkquality = resolvedEntity.device.linkquality;
}
// filter mqtt message attributes
if (resolvedEntity.settings.filtered_attributes) {
resolvedEntity.settings.filtered_attributes.forEach((a) => delete messagePayload[a]);
}
if (Object.entries(messagePayload).length) {
if (settings.get().experimental.output === 'attribute_and_json') {
await this.mqtt.publish(resolvedEntity.name, JSON.stringify(messagePayload), options);
const output = settings.get().experimental.output;
if (output === 'attribute_and_json' || output === 'json') {
await this.mqtt.publish(resolvedEntity.name, stringify(messagePayload), options);
}
if (output === 'attribute_and_json' || output === 'attribute') {
await this.iteratePayloadAttributeOutput(`${resolvedEntity.name}/`, messagePayload, options);
} else if (settings.get().experimental.output === 'json') {
await this.mqtt.publish(resolvedEntity.name, JSON.stringify(messagePayload), options);
} else {
/* istanbul ignore else */
if (settings.get().experimental.output === 'attribute') {
await this.iteratePayloadAttributeOutput(`${resolvedEntity.name}/`, messagePayload, options);
}
}
}
@@ -333,7 +350,7 @@ class Controller {
async iteratePayloadAttributeOutput(topicRoot, payload, options) {
for (const [key, value] of Object.entries(payload)) {
let subPayload = value;
let message;
let message = null;
// Special cases
if (key === 'color' && utils.objectHasProperties(subPayload, ['r', 'g', 'b'])) {
@@ -341,20 +358,24 @@ class Controller {
}
// Check Array first, since it is also an Object
if (Array.isArray(subPayload)) {
if (subPayload === null || subPayload === undefined) {
message = '';
} else if (Array.isArray(subPayload)) {
message = subPayload.map((x) => `${x}`).join(',');
} else if (typeof subPayload === 'object') {
return this.iteratePayloadAttributeOutput(`${topicRoot}${key}-`, subPayload, options);
this.iteratePayloadAttributeOutput(`${topicRoot}${key}-`, subPayload, options);
} else {
message = typeof subPayload === 'string' ? subPayload : JSON.stringify(subPayload);
message = typeof subPayload === 'string' ? subPayload : stringify(subPayload);
}
await this.mqtt.publish(`${topicRoot}${key}`, message, options);
if (message !== null) {
await this.mqtt.publish(`${topicRoot}${key}`, message, options);
}
}
}
async callExtensionMethod(method, parameters) {
for (const extension of this.extensions) {
async callExtensionMethod(method, parameters, extensions=null) {
for (const extension of extensions || this.extensions) {
if (extension[method]) {
try {
await extension[method](...parameters);
+19 -1
View File
@@ -8,18 +8,36 @@ const allowedEvents = [
'publishEntityState', // Entity state will be published
'stateChange', // Entity changes its state
'groupMembersChanged', // Members of a group has been changed
'reportingDisabled', // Reporting is disabled for a device
'devicesChanged', // Devices changed
];
class EventBus extends events.EventEmitter {
constructor() {
super();
this.callbackByExtension = {};
}
emit(event, data) {
assert(allowedEvents.includes(event), `Event '${event}' not supported`);
super.emit(event, data);
}
on(event, callback) {
on(event, callback, extension=null) {
assert(allowedEvents.includes(event), `Event '${event}' not supported`);
if (extension) {
if (!this.callbackByExtension[extension]) this.callbackByExtension[extension] = [];
this.callbackByExtension[extension].push({event, callback});
}
super.on(event, callback);
}
removeListenersExtension(extension) {
for (const entry of this.callbackByExtension[extension] || []) {
super.removeListener(entry.event, entry.callback);
}
}
}
module.exports = EventBus;
+26 -8
View File
@@ -3,6 +3,7 @@ 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
@@ -24,6 +25,9 @@ class Availability extends Extension {
this.timers = {};
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)
.map((e) => settings.getEntity(e).ID);
@@ -33,6 +37,16 @@ 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];
clearTimeout(this.timers[resolvedEntity.device.ieeeAddr]);
}
inPasslistOrNotInBlocklist(device) {
const ieeeAddr = device.ieeeAddr;
const deviceSettings = settings.getDevice(ieeeAddr);
@@ -57,7 +71,7 @@ class Availability extends Extension {
}
// Device is a mains powered router
return utils.isRouter(device) && !utils.isBatteryPowered(device);
return device.type === 'Router' && device.powerSource !== 'Battery';
}
onMQTTConnected() {
@@ -78,6 +92,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);
@@ -114,16 +136,14 @@ class Availability extends Extension {
}
setTimerPingable(device) {
if (this.timers[device.ieeeAddr]) {
clearTimeout(this.timers[device.ieeeAddr]);
}
clearTimeout(this.timers[device.ieeeAddr]);
this.timers[device.ieeeAddr] = setTimeout(async () => {
await this.handleIntervalPingable(device);
}, utils.secondsToMilliseconds(this.availability_timeout));
}
async stop() {
super.stop();
for (const timer of Object.values(this.timers)) {
clearTimeout(timer);
}
@@ -134,13 +154,11 @@ class Availability extends Extension {
async onReconnect(device) {
const resolvedEntity = this.zigbee.resolveEntity(device);
if (resolvedEntity && resolvedEntity.definition) {
const used = [];
try {
for (const key of ['state', 'brightness', 'color', 'color_temp']) {
const converter = resolvedEntity.definition.toZigbee.find((tz) => tz.key.includes(key));
if (converter && !used.includes(converter)) {
if (converter) {
await converter.convertGet(device.endpoints[0], key, {});
used.push(converter);
}
}
} catch (error) {
+16 -23
View File
@@ -4,8 +4,9 @@ const utils = require('../util/utils');
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/(bind|unbind)/.+$`);
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 clusters = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering'];
const clusterCandidates = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering'];
// See zigbee-herdsman-converters devices.js
const defaultBindGroup = {type: 'group_number', ID: 901};
@@ -16,42 +17,30 @@ class Bind extends Extension {
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`);
}
}
parseMQTTMessage(topic, message) {
let type = null;
let sourceKey = null;
let targetKey = null;
let clusters = null;
if (this.legacyApi && topic.match(legacyTopicRegex)) {
topic = topic.replace(`${settings.get().mqtt.base_topic}/bridge/`, '');
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;
targetKey = message.to;
clusters = message.clusters;
}
return {type, sourceKey, targetKey};
return {type, sourceKey, targetKey, clusters};
}
async onMQTTMessage(topic, message) {
const {type, sourceKey, targetKey} = this.parseMQTTMessage(topic, message);
const {type, sourceKey, targetKey, clusters} = this.parseMQTTMessage(topic, message);
if (!type) return null;
let error = null;
@@ -72,7 +61,9 @@ class Bind extends Extension {
// Find which clusters are supported by both the source and target.
// Groups are assumed to support all clusters.
for (const cluster of clusters) {
for (const cluster of clusterCandidates) {
if (clusters && !clusters.includes(cluster)) continue;
const targetValid = target.type === 'group' || target.type === 'group_number' ||
target.device.type === 'Coordinator' || target.endpoint.supportsInputCluster(cluster);
@@ -102,7 +93,7 @@ class Bind extends Extension {
if (settings.get().advanced.legacy_api) {
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_${type}`,
stringify({type: `device_${type}`,
message: {from: sourceName, to: targetName, cluster}}),
);
}
@@ -117,7 +108,7 @@ class Bind extends Extension {
if (settings.get().advanced.legacy_api) {
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_${type}_failed`,
stringify({type: `device_${type}_failed`,
message: {from: sourceName, to: targetName, cluster}}),
);
}
@@ -133,7 +124,7 @@ class Bind extends Extension {
if (settings.get().advanced.legacy_api) {
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_${type}_failed`, message: {from: sourceName, to: targetName}}),
stringify({type: `device_${type}_failed`, message: {from: sourceName, to: targetName}}),
);
}
} else if (failedClusters.length === attemptedClusters.length) {
@@ -147,11 +138,13 @@ class Bind extends Extension {
const triggeredViaLegacyApi = topic.match(legacyTopicRegex);
if (!triggeredViaLegacyApi) {
const response = utils.getResponse(message, responseData, error);
await this.mqtt.publish(`bridge/response/device/${type}`, JSON.stringify(response));
await this.mqtt.publish(`bridge/response/device/${type}`, stringify(response));
}
if (error) {
logger.error(error);
} else {
this.eventBus.emit(`devicesChanged`);
}
}
}
+222 -35
View File
@@ -3,16 +3,22 @@ const utils = require('../util/utils');
const Extension = require('./extension');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
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 requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);
class Bridge extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
constructor(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension) {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.enableDisableExtension = enableDisableExtension;
this.lastJoinedDeviceIeeeAddr = null;
this.setupMQTTLogging();
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),
@@ -21,9 +27,12 @@ class Bridge extends Extension {
'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),
'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),
};
}
@@ -33,12 +42,25 @@ class Bridge extends Extension {
this.coordinatorVersion = await this.zigbee.getCoordinatorVersion();
this.eventBus.on(`groupMembersChanged`, () => this.publishGroups());
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/request/#`);
this.eventBus.on(`devicesChanged`, () => this.publishDevices());
await this.publishInfo();
await this.publishDevices();
await this.publishGroups();
}
setupMQTTLogging() {
const mqtt = this.mqtt;
class EventTransport extends Transport {
log(info, callback) {
const payload = stringify({message: info.message, level: info.level});
mqtt.publish(`bridge/logging`, payload, {}, settings.get().mqtt.base_topic, true);
callback();
}
}
logger.add(new EventTransport({level: logger.getLevel()}));
}
async onMQTTMessage(topic, message) {
const match = topic.match(requestRegex);
if (match && this.requestLookup[match[1].toLowerCase()]) {
@@ -46,11 +68,11 @@ class Bridge extends Extension {
try {
const response = await this.requestLookup[match[1].toLowerCase()](message);
await this.mqtt.publish(`bridge/response/${match[1]}`, JSON.stringify(response));
await this.mqtt.publish(`bridge/response/${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/${match[1]}`, JSON.stringify(response));
await this.mqtt.publish(`bridge/response/${match[1]}`, stringify(response));
}
}
}
@@ -60,7 +82,7 @@ class Bridge extends Extension {
this.lastJoinedDeviceIeeeAddr = resolvedEntity.device.ieeeAddr;
}
if (['deviceJoined', 'deviceLeave', 'deviceInterview'].includes(type)) {
if (['deviceJoined', 'deviceLeave', 'deviceInterview', 'deviceAnnounce'].includes(type)) {
let payload;
const ieeeAddress = data.device ? data.device.ieeeAddr : data.ieeeAddr;
if (type === 'deviceJoined') {
@@ -70,19 +92,19 @@ class Bridge extends Extension {
friendly_name: resolvedEntity.settings.friendlyName, status: data.status, ieee_address: ieeeAddress,
};
if (data.status === 'successful') {
payload.supported = !!resolvedEntity.definition;
payload.definition = resolvedEntity.definition ? {
model: resolvedEntity.definition.model,
vendor: resolvedEntity.definition.vendor,
description: resolvedEntity.definition.description,
supports: resolvedEntity.definition.supports,
} : null;
const definition = resolvedEntity.definition;
payload.supported = !!definition;
payload.definition = this.getDefinitionPayload(definition);
}
} else if (type === 'deviceAnnounce') {
payload = {
friendly_name: resolvedEntity.settings.friendlyName, ieee_address: ieeeAddress,
};
} else payload = {ieee_address: ieeeAddress}; // deviceLeave
await this.mqtt.publish(
'bridge/event',
JSON.stringify({type: utils.toSnakeCase(type), data: payload}),
stringify({type: utils.toSnakeCase(type), data: payload}),
{retain: false, qos: 0},
);
}
@@ -138,10 +160,29 @@ class Bridge extends Extension {
}
async permitJoin(message) {
const value = this.getValue(message);
await this.zigbee.permitJoin(value);
if (typeof message === 'object' && !message.hasOwnProperty('value')) {
throw new Error('Invalid payload');
}
let value;
let resolvedEntity;
if (typeof message === 'object') {
value = message.value;
if (message.device) {
resolvedEntity = this.zigbee.resolveEntity(message.device);
if (!resolvedEntity || resolvedEntity.type !== 'device') {
throw new Error(`Device '${message.device}' does not exist`);
}
}
} else {
value = message;
}
await this.zigbee.permitJoin(value, resolvedEntity);
await this.publishInfo();
return utils.getResponse(message, {value: value}, null);
return utils.getResponse(
message, resolvedEntity ? {value: value, device: message.device} : {value: value}, null,
);
}
configLastSeen(message) {
@@ -152,6 +193,20 @@ class Bridge extends Extension {
}
settings.set(['advanced', 'last_seen'], message);
this.publishInfo();
return utils.getResponse(message, {value}, null);
}
configHomeAssistant(message) {
const allowed = [true, false];
const value = this.getValue(message);
if (!allowed.includes(value)) {
throw new Error(`'${value}' is not an allowed value, allowed: ${allowed}`);
}
this.enableDisableExtension(value, 'HomeAssistant');
settings.set(['homeassistant'], value);
this.publishInfo();
return utils.getResponse(message, {value}, null);
}
@@ -163,6 +218,7 @@ class Bridge extends Extension {
}
settings.set(['advanced', 'elapsed'], value);
this.publishInfo();
return utils.getResponse(message, {value}, null);
}
@@ -178,18 +234,50 @@ class Bridge extends Extension {
return utils.getResponse(message, {value}, null);
}
async touchlinkIdentify(message) {
if (typeof message !== 'object' || !message.hasOwnProperty('ieee_address') ||
!message.hasOwnProperty('channel')) {
throw new Error('Invalid payload');
}
logger.info(`Start Touchlink identify of '${message.ieee_address}' on channel ${message.channel}`);
await this.zigbee.touchlinkIdentify(message.ieee_address, message.channel);
return utils.getResponse(message, {ieee_address: message.ieee_address, channel: message.channel}, null);
}
async touchlinkFactoryReset(message) {
logger.info('Start touchlink factory reset');
const result = await this.zigbee.touchlinkFactoryReset();
let result = false;
const payload = {};
if (typeof message === 'object' && message.hasOwnProperty('ieee_address') &&
message.hasOwnProperty('channel')) {
logger.info(`Start Touchlink factory reset of '${message.ieee_address}' on channel ${message.channel}`);
result = await this.zigbee.touchlinkFactoryReset(message.ieee_address, message.channel);
payload.ieee_address = message.ieee_address;
payload.channel = message.channel;
} else {
logger.info('Start Touchlink factory reset of first found device');
result = await this.zigbee.touchlinkFactoryResetFirst();
}
if (result) {
logger.info('Successfully factory reset device through Touchlink');
return utils.getResponse(message, {}, null);
return utils.getResponse(message, payload, null);
} else {
logger.error('Failed to factory reset device through Touchlink');
throw new Error('Failed to factory reset device through Touchlink');
}
}
async touchlinkScan(message) {
logger.info('Start Touchlink scan');
const result = await this.zigbee.touchlinkScan();
const found = result.map((r) => {
return {ieee_address: r.ieeeAddr, channel: r.channel};
});
logger.info('Finished Touchlink scan');
return utils.getResponse(message, {found}, null);
}
/**
* Utils
*/
@@ -223,6 +311,31 @@ class Bridge extends Extension {
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;
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) ||
@@ -234,21 +347,39 @@ 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') ?
message.homeassistant_rename : false;
const entity = this.getEntity(entityType, from);
settings.changeFriendlyName(from, to);
// 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});
this.eventBus.emit(`deviceRenamed`, {device: entity.device, homeAssisantRename, from: oldFriendlyName, to});
} else {
this.publishGroups();
this.eventBus.emit(`groupRenamed`, {group: entity.group});
this.eventBus.emit(`groupRenamed`, {group: entity.group, homeAssisantRename, from: oldFriendlyName, to});
}
return utils.getResponse(message, {from: entity.settings.friendlyName, to}, null);
// Repulish entity state
this.publishEntityState(to, {});
return utils.getResponse(
message,
{from: oldFriendlyName, to, homeassistant_rename: homeAssisantRename},
null,
);
}
async removeEntity(entityType, message) {
@@ -290,7 +421,7 @@ class Bridge extends Extension {
// Fire event
if (entity.type === 'device') {
this.eventBus.emit('deviceRemoved', {device: entity.device});
this.eventBus.emit('deviceRemoved', {resolvedEntity: entity});
}
// Remove from configuration.yaml
@@ -303,9 +434,13 @@ class Bridge extends Extension {
// Remove from state
this.state.remove(entity.settings.ID);
// Clear any retained messages
this.mqtt.publish(entity.name, '', {retain: true});
logger.info(`Successfully removed ${entity.type} '${entity.settings.friendlyName}'${blockForceLog}`);
if (entity.type === 'device') {
this.publishGroups();
this.publishDevices();
return utils.getResponse(message, {id: ID, block, force}, null);
} else {
@@ -328,27 +463,64 @@ class Bridge extends Extension {
}
async publishInfo() {
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(),
config,
};
await this.mqtt.publish('bridge/info', JSON.stringify(payload), {retain: true, qos: 0});
await this.mqtt.publish('bridge/info', stringify(payload), {retain: true, qos: 0});
}
async publishDevices() {
const devices = this.zigbee.getClients().map((device) => {
const devices = this.zigbee.getDevices().map((device) => {
const definition = zigbeeHerdsmanConverters.findByDevice(device);
const resolved = this.zigbee.resolveEntity(device);
const definitionPayload = definition ? {
model: definition.model,
vendor: definition.vendor,
description: definition.description,
supports: definition.supports,
} : null;
const endpoints = {};
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),
},
};
for (const bind of endpoint.binds) {
let target;
if (bind.target.constructor.name === 'Endpoint') {
target = {
type: 'endpoint', ieee_address: bind.target.getDevice().ieeeAddr, endpoint: bind.target.ID,
};
} else {
target = {type: 'group', id: bind.target.groupID};
}
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,
minimum_report_interval: configuredReporting.minimumReportInterval,
maximum_report_interval: configuredReporting.maximumReportInterval,
reportable_change: configuredReporting.reportableChange,
});
}
endpoints[endpoint.ID] = data;
}
return {
ieee_address: device.ieeeAddr,
@@ -356,16 +528,18 @@ class Bridge extends Extension {
network_address: device.networkAddress,
supported: !!definition,
friendly_name: resolved.name,
definition: definitionPayload,
definition: this.getDefinitionPayload(definition),
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', JSON.stringify(devices), {retain: true, qos: 0});
await this.mqtt.publish('bridge/devices', stringify(devices), {retain: true, qos: 0});
}
async publishGroups() {
@@ -373,7 +547,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,
@@ -383,7 +557,20 @@ class Bridge extends Extension {
};
});
await this.mqtt.publish('bridge/groups', JSON.stringify(groups), {retain: true, qos: 0});
await this.mqtt.publish('bridge/groups', stringify(groups), {retain: true, qos: 0});
}
getDefinitionPayload(definition) {
if (definition) {
return {
model: definition.model,
vendor: definition.vendor,
description: definition.description,
exposes: definition.exposes,
};
} else {
return null;
}
}
}
+21 -14
View File
@@ -2,6 +2,7 @@ const settings = require('../util/settings');
const utils = require('../util/utils');
const logger = require('../util/logger');
const Extension = require('./extension');
const stringify = require('json-stable-stringify-without-jsonify');
/**
* This extension calls the zigbee-herdsman-converters definition configure() method
@@ -11,10 +12,27 @@ class Configure extends Extension {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.configuring = new Set();
this.onReportingDisabled = this.onReportingDisabled.bind(this);
this.attempts = {};
this.topic = `${settings.get().mqtt.base_topic}/bridge/request/device/configure`;
this.legacyTopic = `${settings.get().mqtt.base_topic}/bridge/configure`;
this.eventBus.on(`reportingDisabled`, this.onReportingDisabled);
}
onReportingDisabled(data) {
// Disabling reporting unbinds some cluster which could be bound by configure, re-setup.
const device = data.device;
const resolvedEntity = this.zigbee.resolveEntity(device);
if (resolvedEntity.device.meta && resolvedEntity.device.meta.hasOwnProperty('configured')) {
delete device.meta.configured;
device.save();
}
if (this.shouldConfigure(resolvedEntity)) {
this.configure(resolvedEntity);
}
}
shouldConfigure(resolvedEntity) {
@@ -35,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);
@@ -80,7 +86,7 @@ class Configure extends Extension {
}
const response = utils.getResponse(message, {id: ID}, error);
await this.mqtt.publish(`bridge/response/device/configure`, JSON.stringify(response));
await this.mqtt.publish(`bridge/response/device/configure`, stringify(response));
}
}
@@ -122,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];
+3 -1
View File
@@ -46,7 +46,9 @@ class Extension {
/**
* Is called once the extension has to stop
*/
// stop() {}
stop() {
this.eventBus.removeListenersExtension(this.constructor.name);
}
}
module.exports = Extension;
+127
View File
@@ -0,0 +1,127 @@
const http = require('http');
const serveStatic = require('serve-static');
const finalhandler = require('finalhandler');
const Extension = require('./extension');
const logger = require('../util/logger');
const frontend = require('zigbee2mqtt-frontend');
const WebSocket = require('ws');
const url = require('url');
const settings = require('../util/settings');
const utils = require('../util/utils');
const stringify = require('json-stable-stringify-without-jsonify');
/**
* This extension servers the frontend
*/
class Frontend extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.onRequest = this.onRequest.bind(this);
this.onUpgrade = this.onUpgrade.bind(this);
this.mqttBaseTopic = settings.get().mqtt.base_topic;
this.onMQTTPublishedMessage = this.onMQTTPublishedMessage.bind(this);
this.mqtt.on('publishedMessage', this.onMQTTPublishedMessage);
this.onWebSocketConnection = this.onWebSocketConnection.bind(this);
this.server = http.createServer(this.onRequest);
this.server.on('upgrade', this.onUpgrade);
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();
/* 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() {
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.close();
}
return new Promise((resolve) => {
this.server.close(resolve);
});
}
onRequest(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) {
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) {
ws.on('message', (message) => {
if (message) {
const {topic, payload} = utils.parseJSON(message, message);
this.mqtt.onMessage(`${this.mqttBaseTopic}/${topic}`, stringify(payload));
}
});
for (const [key, value] of this.retainedMessages) {
ws.send(stringify({topic: key, payload: value}));
}
for (const device of this.zigbee.getClients()) {
let payload = {};
const resolvedEntity = this.zigbee.resolveEntity(device);
if (this.state.exists(device.ieeeAddr)) {
payload = {...payload, ...this.state.get(device.ieeeAddr)};
}
const lastSeen = settings.get().advanced.last_seen;
if (lastSeen !== 'disable') {
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}));
}
}
onMQTTPublishedMessage(data) {
let {topic, payload, options} = data;
if (topic.startsWith(`${this.mqttBaseTopic}/`)) {
// Send topic without base_topic
topic = topic.substring(this.mqttBaseTopic.length + 1);
payload = utils.parseJSON(payload, payload);
if (options.retain) {
this.retainedMessages.set(topic, payload);
}
for (const client of this.wss.clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(stringify({topic, payload}));
}
}
}
}
}
module.exports = Frontend;
+32 -38
View File
@@ -3,6 +3,8 @@ const logger = require('../util/logger');
const Extension = require('./extension');
const utils = require('../util/utils');
const postfixes = utils.getEndpointNames();
const stringify = require('json-stable-stringify-without-jsonify');
const equals = require('fast-deep-equal/es6');
const topicRegex =
new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
@@ -14,37 +16,31 @@ class Groups extends Extension {
super(zigbee, mqtt, state, publishEntityState, eventBus);
this.onStateChange = this.onStateChange.bind(this);
this.legacyApi = settings.get().advanced.legacy_api;
}
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`);
}
this.lastOptimisticState = {};
}
async onZigbeeStarted() {
await this.syncGroupsWithSettings();
this.eventBus.on('stateChange', this.onStateChange);
await this.syncGroupsWithSettings();
}
async syncGroupsWithSettings() {
const settingsGroups = settings.getGroups();
const zigbeeGroups = this.zigbee.getGroups();
const addRemoveFromGroup = async (action, deviceName, groupName, endpoint, group) => {
try {
logger.info(`${action === 'add' ? 'Adding' : 'Removing'} '${deviceName}' to group '${groupName}'`);
if (action === 'remove') {
await endpoint.removeFromGroup(group);
} else {
await endpoint.addToGroup(group);
}
} catch (error) {
logger.error(`Failed to ${action} '${deviceName}' from '${groupName}'`);
}
};
for (const settingGroup of settingsGroups) {
const groupID = settingGroup.ID;
const zigbeeGroup = zigbeeGroups.find((g) => g.groupID === groupID) || this.zigbee.createGroup(groupID);
@@ -57,28 +53,24 @@ class Groups extends Extension {
// In settings but not in zigbee
for (const entity of settingsEntity) {
if (!zigbeeGroup.hasMember(entity.endpoint)) {
logger.info(`Adding '${entity.name}' to group '${settingGroup.friendlyName}'`);
await entity.endpoint.addToGroup(zigbeeGroup);
addRemoveFromGroup('add', entity.name, settingGroup.friendlyName, entity.endpoint, zigbeeGroup);
}
}
// In zigbee but not in settings
for (const endpoint of zigbeeGroup.members) {
if (!settingsEntity.find((e) => e.endpoint === endpoint)) {
const deviceSettings = settings.getDevice(endpoint.getDevice().ieeeAddr);
logger.info(`Removing '${deviceSettings.friendlyName}' from group '${settingGroup.friendlyName}'`);
await endpoint.removeFromGroup(zigbeeGroup);
const deviceName = settings.getDevice(endpoint.getDevice().ieeeAddr).friendlyName;
addRemoveFromGroup('remove', deviceName, settingGroup.friendlyName, endpoint, zigbeeGroup);
}
}
}
// eslint-disable-next-line
for (const zigbeeGroup of zigbeeGroups) {
if (!settingsGroups.find((g) => g.ID === zigbeeGroup.groupID)) {
for (const endpoint of zigbeeGroup.members) {
const deviceSettings = settings.getDevice(endpoint.getDevice().ieeeAddr);
logger.info(`Removing '${deviceSettings.friendlyName}' from group ${zigbeeGroup.groupID}`);
await endpoint.removeFromGroup(zigbeeGroup);
const deviceName = settings.getDevice(endpoint.getDevice().ieeeAddr).friendlyName;
addRemoveFromGroup('remove', deviceName, zigbeeGroup.groupID, endpoint, zigbeeGroup);
}
}
}
@@ -108,8 +100,10 @@ class Groups extends Extension {
if (resolvedEntity.type === 'device') {
for (const zigbeeGroup of zigbeeGroups) {
if (zigbeeGroup.hasMember(resolvedEntity.endpoint)) {
if (zigbeeGroup.hasMember(resolvedEntity.endpoint) &&
!equals(this.lastOptimisticState[zigbeeGroup.groupID], payload)) {
if (!payload || payload.state !== 'OFF' || this.areAllMembersOff(zigbeeGroup)) {
this.lastOptimisticState[zigbeeGroup.groupID] = payload;
await this.publishEntityState(zigbeeGroup.groupID, payload, reason);
}
}
@@ -178,7 +172,7 @@ class Groups extends Extension {
};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_${type}_failed`, message: payload}),
stringify({type: `device_group_${type}_failed`, message: payload}),
);
}
@@ -199,7 +193,7 @@ class Groups extends Extension {
};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_${type}_failed`, message: payload}),
stringify({type: `device_group_${type}_failed`, message: payload}),
);
}
@@ -279,7 +273,7 @@ class Groups extends Extension {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_add`, message: payload}),
stringify({type: `device_group_add`, message: payload}),
);
}
} else if (type === 'remove') {
@@ -292,7 +286,7 @@ class Groups extends Extension {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_remove`, message: payload}),
stringify({type: `device_group_remove`, message: payload}),
);
}
} else { // remove_all
@@ -306,7 +300,7 @@ class Groups extends Extension {
const payload = {friendly_name: resolvedEntityDevice.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_group_remove_all`, message: payload}),
stringify({type: `device_group_remove_all`, message: payload}),
);
}
}
@@ -318,7 +312,7 @@ class Groups extends Extension {
if (!triggeredViaLegacyApi) {
const response = utils.getResponse(message, responseData, error);
await this.mqtt.publish(`bridge/response/group/members/${type}`, JSON.stringify(response));
await this.mqtt.publish(`bridge/response/group/members/${type}`, stringify(response));
}
if (error) {
File diff suppressed because it is too large Load Diff
+30 -27
View File
@@ -4,6 +4,7 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const utils = require('../../util/utils');
const assert = require('assert');
const Extension = require('../extension');
const stringify = require('json-stable-stringify-without-jsonify');
const configRegex =
new RegExp(`${settings.get().mqtt.base_topic}/bridge/config/((?:\\w+/get)|(?:\\w+/factory_reset)|(?:\\w+))`);
@@ -66,7 +67,7 @@ class BridgeLegacy extends Extension {
logger.info(`Whitelisted '${entity.friendlyName}'`);
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: 'device_whitelisted', message: {friendly_name: entity.friendlyName}}),
stringify({type: 'device_whitelisted', message: {friendly_name: entity.friendlyName}}),
);
} catch (error) {
logger.error(`Failed to whitelist '${message}' '${error}'`);
@@ -90,7 +91,7 @@ class BridgeLegacy extends Extension {
const entity = settings.getEntity(json.friendly_name);
assert(entity, `Entity '${json.friendly_name}' does not exist`);
settings.changeEntityOptions(entity.ID, json.options);
logger.info(`Changed device specific options of '${json.friendly_name}' (${JSON.stringify(json.options)})`);
logger.info(`Changed device specific options of '${json.friendly_name}' (${stringify(json.options)})`);
}
async permitJoin(topic, message) {
@@ -176,9 +177,11 @@ class BridgeLegacy extends Extension {
});
if (topic.split('/').pop() == 'get') {
this.mqtt.publish(`bridge/config/devices`, JSON.stringify(devices), {});
this.mqtt.publish(
`bridge/config/devices`, stringify(devices), {}, settings.get().mqtt.base_topic, false, false,
);
} else {
this.mqtt.publish('bridge/log', JSON.stringify({type: 'devices', message: devices}));
this.mqtt.publish('bridge/log', stringify({type: 'devices', message: devices}));
}
}
@@ -189,7 +192,7 @@ class BridgeLegacy extends Extension {
return group;
});
this.mqtt.publish('bridge/log', JSON.stringify({type: 'groups', message: payload}));
this.mqtt.publish('bridge/log', stringify({type: 'groups', message: payload}));
}
rename(topic, message) {
@@ -228,12 +231,13 @@ 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);
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `${isGroup ? 'group' : 'device'}_renamed`, message: {from, to}}),
stringify({type: `${isGroup ? 'group' : 'device'}_renamed`, message: {from, to}}),
);
} catch (error) {
logger.error(`Failed to rename - ${from} to ${to}`);
@@ -265,7 +269,7 @@ class BridgeLegacy extends Extension {
const group = settings.addGroup(name, id);
this.zigbee.createGroup(group.ID);
this.mqtt.publish('bridge/log', JSON.stringify({type: `group_added`, message: name}));
this.mqtt.publish('bridge/log', stringify({type: `group_added`, message: name}));
logger.info(`Added group '${name}'`);
}
@@ -281,7 +285,7 @@ class BridgeLegacy extends Extension {
}
settings.removeGroup(message);
this.mqtt.publish('bridge/log', JSON.stringify({type: `group_removed`, message}));
this.mqtt.publish('bridge/log', stringify({type: `group_removed`, message}));
logger.info(`Removed group '${name}'`);
}
@@ -308,13 +312,13 @@ class BridgeLegacy extends Extension {
if (!entity) {
logger.error(`Cannot ${lookup[action][2]}, device '${message}' does not exist`);
this.mqtt.publish('bridge/log', JSON.stringify({type: `device_${lookup[action][0]}_failed`, message}));
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
return;
}
const cleanup = () => {
// Fire event
this.eventBus.emit('deviceRemoved', {device: entity.device});
this.eventBus.emit('deviceRemoved', {resolvedEntity: entity});
// Remove from configuration.yaml
settings.removeDevice(entity.settings.ID);
@@ -323,7 +327,7 @@ class BridgeLegacy extends Extension {
this.state.remove(entity.settings.ID);
logger.info(`Successfully ${lookup[action][0]} ${entity.settings.friendlyName}`);
this.mqtt.publish('bridge/log', JSON.stringify({type: `device_${lookup[action][0]}`, message}));
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}`, message}));
};
try {
@@ -340,7 +344,7 @@ class BridgeLegacy extends Extension {
// eslint-disable-next-line
logger.error(`See https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html#zigbee2mqttbridgeconfigremove for more info`);
this.mqtt.publish('bridge/log', JSON.stringify({type: `device_${lookup[action][0]}_failed`, message}));
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
}
if (action === 'ban') {
@@ -349,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();
}
@@ -378,11 +380,12 @@ class BridgeLegacy extends Extension {
version: info.version,
commit: info.commitHash,
coordinator,
network: await this.zigbee.getNetworkParameters(),
log_level: logger.getLevel(),
permit_join: await this.zigbee.getPermitJoin(),
};
await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0});
await this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0});
}
onZigbeeEvent(type, data, resolvedEntity) {
@@ -393,7 +396,7 @@ class BridgeLegacy extends Extension {
if (type === 'deviceJoined') {
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_connected`, message: {friendly_name: resolvedEntity.name}}),
stringify({type: `device_connected`, message: {friendly_name: resolvedEntity.name}}),
);
} else if (type === 'deviceInterview') {
if (data.status === 'successful') {
@@ -402,20 +405,20 @@ class BridgeLegacy extends Extension {
const log = {friendly_name: resolvedEntity.name, model, vendor, description, supported: true};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `pairing`, message: 'interview_successful', meta: log}),
stringify({type: `pairing`, message: 'interview_successful', meta: log}),
);
} else {
const meta = {friendly_name: resolvedEntity.name, supported: false};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `pairing`, message: 'interview_successful', meta}),
stringify({type: `pairing`, message: 'interview_successful', meta}),
);
}
} else if (data.status === 'failed') {
const meta = {friendly_name: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `pairing`, message: 'interview_failed', meta}),
stringify({type: `pairing`, message: 'interview_failed', meta}),
);
} else {
/* istanbul ignore else */
@@ -423,13 +426,13 @@ class BridgeLegacy extends Extension {
const meta = {friendly_name: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `pairing`, message: 'interview_started', meta}),
stringify({type: `pairing`, message: 'interview_started', meta}),
);
}
}
} else if (type === 'deviceAnnounce') {
const meta = {friendly_name: resolvedEntity.name};
this.mqtt.publish('bridge/log', JSON.stringify({type: `device_announced`, message: 'announce', meta}));
this.mqtt.publish('bridge/log', stringify({type: `device_announced`, message: 'announce', meta}));
} else {
/* istanbul ignore else */
if (type === 'deviceLeave') {
@@ -437,7 +440,7 @@ class BridgeLegacy extends Extension {
const meta = {friendly_name: name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `device_removed`, message: 'left_network', meta}),
stringify({type: `device_removed`, message: 'left_network', meta}),
);
}
}
@@ -447,21 +450,21 @@ class BridgeLegacy extends Extension {
logger.info('Starting touchlink factory reset...');
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `touchlink`, message: 'reset_started', meta: {status: 'started'}}),
stringify({type: `touchlink`, message: 'reset_started', meta: {status: 'started'}}),
);
const result = await this.zigbee.touchlinkFactoryReset();
const result = await this.zigbee.touchlinkFactoryResetFirst();
if (result) {
logger.info('Successfully factory reset device through Touchlink');
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}),
stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}),
);
} else {
logger.warn('Failed to factory reset device through Touchlink');
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}),
stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}),
);
}
}
+12 -8
View File
@@ -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) {
@@ -29,7 +22,18 @@ class DeviceGroupMembership extends Extension {
`genGroups`, 'getMembership', {groupcount: 0, grouplist: []}, {}, true,
);
const {grouplist, capacity} = response;
if (!response) {
logger.warn(`Couldn't get group membership of ${entity.device.ieeeAddr}`);
return;
}
let {grouplist, capacity} = response;
grouplist = grouplist.map((gid) => {
const g = settings.getGroup(gid);
return g ? g.friendlyName : gid;
});
const msgGroupList = `${entity.device.ieeeAddr} is in groups [${grouplist}]`;
let msgCapacity;
if (capacity === 254) {
+28 -20
View File
@@ -2,6 +2,7 @@ const settings = require('../util/settings');
const utils = require('../util/utils');
const logger = require('../util/logger');
const Extension = require('./extension');
const stringify = require('json-stable-stringify-without-jsonify');
/**
* This extension creates a network map
@@ -28,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) {
@@ -47,12 +36,13 @@ class NetworkMap extends Extension {
this.supportedFormats.hasOwnProperty(message)) {
const includeRoutes = topic === this.legacyTopicRoutes;
const topology = await this.networkScan(includeRoutes);
const converted = this.supportedFormats[message](topology);
let converted = this.supportedFormats[message](topology);
converted = message === 'raw' ? stringify(converted) : converted;
this.mqtt.publish(`bridge/networkmap/${message}`, converted, {});
}
}
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;
@@ -65,19 +55,19 @@ class NetworkMap extends Extension {
const value = this.supportedFormats[type](topology);
await this.mqtt.publish(
'bridge/response/networkmap',
JSON.stringify(utils.getResponse(message, {routes, type, value}, null)),
stringify(utils.getResponse(message, {routes, type, value}, null)),
);
} catch (error) {
await this.mqtt.publish(
'bridge/response/networkmap',
JSON.stringify(utils.getResponse(message, {}, error.message)),
stringify(utils.getResponse(message, {}, error.message)),
);
}
}
}
raw(topology) {
return JSON.stringify(topology);
return topology;
}
graphviz(topology) {
@@ -100,9 +90,8 @@ class NetworkMap extends Extension {
// Add the device model
if (node.type !== 'Coordinator') {
const definition = this.zigbee.resolveEntity(node.ieeeAddr).definition;
if (definition) {
labels.push(`${definition.vendor} ${definition.description} (${definition.model})`);
if (node.definition) {
labels.push(`${node.definition.vendor} ${node.definition.description} (${node.definition.model})`);
} else {
// This model is not supported by zigbee-herdsman-converters, add zigbee model information
labels.push(`${node.manufacturerName} ${node.modelID}`);
@@ -251,10 +240,22 @@ class NetworkMap extends Extension {
// Add nodes
for (const device of devices) {
const resolvedEntity = this.zigbee.resolveEntity(device);
const definition = resolvedEntity.definition ? {
model: resolvedEntity.definition.model,
vendor: resolvedEntity.definition.vendor,
description: resolvedEntity.definition.description,
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({
ieeeAddr: device.ieeeAddr, friendlyName: resolvedEntity.name, type: device.type,
networkAddress: device.networkAddress, manufacturerName: device.manufacturerName,
modelID: device.modelID, failed: failed.get(device), lastSeen: device.lastSeen,
definition,
});
}
@@ -266,6 +267,13 @@ class NetworkMap extends Extension {
continue;
}
// Some Xiaomi devices return 0x00 as the neighbor ieeeAddr (obviously not correct).
// Determine the correct ieeeAddr based on the networkAddress.
const neighborDevice = this.zigbee.getDeviceByNetworkAddress(neighbor.networkAddress);
if (neighbor.ieeeAddr === '0x0000000000000000' && neighborDevice) {
neighbor.ieeeAddr = neighborDevice.ieeeAddr;
}
const link = {
source: {ieeeAddr: neighbor.ieeeAddr, networkAddress: neighbor.networkAddress},
target: {ieeeAddr: device.ieeeAddr, networkAddress: device.networkAddress},
+3 -2
View File
@@ -19,6 +19,7 @@ class OnEvent extends Extension {
}
async stop() {
super.stop();
for (const device of this.zigbee.getClients()) {
const resolvedEntity = this.zigbee.resolveEntity(device);
this.callOnEvent(resolvedEntity, 'stop', {});
@@ -26,10 +27,10 @@ class OnEvent extends Extension {
}
callOnEvent(resolvedEntity, type, data) {
zigbeeHerdsmanConverters.onEvent(type, data, resolvedEntity.device);
zigbeeHerdsmanConverters.onEvent(type, data, resolvedEntity.device, resolvedEntity.settings);
if (resolvedEntity.definition && resolvedEntity.definition.onEvent) {
resolvedEntity.definition.onEvent(type, data, resolvedEntity.device);
resolvedEntity.definition.onEvent(type, data, resolvedEntity.device, resolvedEntity.settings);
}
}
}
+53 -23
View File
@@ -1,7 +1,9 @@
const settings = require('../util/settings');
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');
@@ -14,20 +16,23 @@ 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`);
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.
this.removeProgressAndRemainingFromState(device);
}
}
/* 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`);
}
removeProgressAndRemainingFromState(device) {
this.state.removeKey(device.ieeeAddr, ['update', 'progress']);
this.state.removeKey(device.ieeeAddr, ['update', 'remaining']);
}
async onZigbeeEvent(type, data, resolvedEntity) {
@@ -44,7 +49,8 @@ class OTAUpdate extends Extension {
this.lastChecked[data.device.ieeeAddr] = Date.now();
const available = await resolvedEntity.definition.ota.isUpdateAvailable(data.device, logger, data.data);
this.publishEntityState(data.device.ieeeAddr, {update_available: available});
const payload = this.getEntityPublishPayload(available ? 'available' : 'idle');
this.publishEntityState(data.device.ieeeAddr, payload);
if (available) {
const message = `Update available for '${resolvedEntity.settings.friendly_name}'`;
@@ -55,7 +61,7 @@ class OTAUpdate extends Extension {
const meta = {status: 'available', device: resolvedEntity.settings.friendly_name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `ota_update`, message, meta}),
stringify({type: `ota_update`, message, meta}),
);
}
}
@@ -88,6 +94,19 @@ class OTAUpdate extends Extension {
}
}
getEntityPublishPayload(state, progress=null, remaining=null) {
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';
}
return payload;
}
async onMQTTMessage(topic, message) {
if ((!this.legacyApi || !topic.match(legacyTopicRegex)) && !topic.match(topicRegex)) {
return null;
@@ -110,7 +129,7 @@ class OTAUpdate extends Extension {
const meta = {status: `not_supported`, device: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `ota_update`, message: error, meta}),
stringify({type: `ota_update`, message: error, meta}),
);
}
} else if (this.inProgress.has(resolvedEntity.device.ieeeAddr)) {
@@ -127,7 +146,7 @@ class OTAUpdate extends Extension {
const meta = {status: `checking_if_available`, device: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `ota_update`, message: msg, meta}),
stringify({type: `ota_update`, message: msg, meta}),
);
}
@@ -143,11 +162,12 @@ class OTAUpdate extends Extension {
const meta = {status: available ? 'available' : 'not_available', device: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `ota_update`, message: msg, meta}),
stringify({type: `ota_update`, message: msg, meta}),
);
}
this.publishEntityState(resolvedEntity.device.ieeeAddr, {update_available: available});
const payload = this.getEntityPublishPayload(available ? 'available' : 'idle');
this.publishEntityState(resolvedEntity.device.ieeeAddr, payload);
this.lastChecked[resolvedEntity.device.ieeeAddr] = Date.now();
responseData.updateAvailable = available;
} catch (e) {
@@ -158,7 +178,7 @@ class OTAUpdate extends Extension {
const meta = {status: `check_failed`, device: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `ota_update`, message: error, meta}),
stringify({type: `ota_update`, message: error, meta}),
);
}
}
@@ -171,7 +191,7 @@ class OTAUpdate extends Extension {
const meta = {status: `update_in_progress`, device: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `ota_update`, msg, meta}),
stringify({type: `ota_update`, message: msg, meta}),
);
}
@@ -184,36 +204,46 @@ class OTAUpdate extends Extension {
logger.info(msg);
const payload = this.getEntityPublishPayload('updating', progress, remaining);
this.publishEntityState(resolvedEntity.device.ieeeAddr, payload);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `update_progress`, device: resolvedEntity.name, progress};
this.mqtt.publish('bridge/log', JSON.stringify({type: `ota_update`, message: msg, meta}));
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: msg, meta}));
}
};
const from_ = await this.readSoftwareBuildIDAndDateCode(resolvedEntity.device, false);
await resolvedEntity.definition.ota.updateToLatest(resolvedEntity.device, logger, onProgress);
const to = await this.readSoftwareBuildIDAndDateCode(resolvedEntity.device, true);
const [fromS, toS] = [JSON.stringify(from_), JSON.stringify(to)];
const [fromS, toS] = [stringify(from_), stringify(to)];
const msg = `Finished update of '${resolvedEntity.name}'` +
(to ? `, from '${fromS}' to '${toS}'` : ``);
logger.info(msg);
this.publishEntityState(resolvedEntity.device.ieeeAddr, {update_available: false});
this.removeProgressAndRemainingFromState(resolvedEntity.device);
const payload = this.getEntityPublishPayload('idle');
this.publishEntityState(resolvedEntity.device.ieeeAddr, payload);
responseData.from = from_ ? utils.toSnakeCase(from_) : null;
responseData.to = to ? utils.toSnakeCase(to) : null;
this.eventBus.emit(`devicesChanged`);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `update_succeeded`, device: resolvedEntity.name, from: from_, to};
this.mqtt.publish('bridge/log', JSON.stringify({type: `ota_update`, message, meta}));
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message, meta}));
}
} catch (e) {
error = `Update of '${resolvedEntity.name}' failed (${e.message})`;
this.removeProgressAndRemainingFromState(resolvedEntity.device);
const payload = this.getEntityPublishPayload('available');
this.publishEntityState(resolvedEntity.device.ieeeAddr, payload);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `update_failed`, device: resolvedEntity.name};
this.mqtt.publish('bridge/log', JSON.stringify({type: `ota_update`, message: error, meta}));
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: error, meta}));
}
}
}
@@ -224,7 +254,7 @@ class OTAUpdate extends Extension {
const triggeredViaLegacyApi = topic.match(legacyTopicRegex);
if (!triggeredViaLegacyApi) {
const response = utils.getResponse(message, responseData, error);
await this.mqtt.publish(`bridge/response/device/ota_update/${type}`, JSON.stringify(response));
await this.mqtt.publish(`bridge/response/device/ota_update/${type}`, stringify(response));
}
if (error) {
+74 -38
View File
@@ -5,35 +5,28 @@ const logger = require('../util/logger');
const utils = require('../util/utils');
const assert = require('assert');
const Extension = require('./extension');
const stringify = require('json-stable-stringify-without-jsonify');
const topicRegex = new RegExp(`^(.+?)(?:/(${utils.getEndpointNames().join('|')}))?/(get|set)(?:/(.+))?`);
const stateValues = ['on', 'off', 'toggle', 'open', 'close', 'stop', 'lock', 'unlock'];
const groupConverters = [
zigbeeHerdsmanConverters.toZigbeeConverters.light_onoff_brightness,
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp,
zigbeeHerdsmanConverters.toZigbeeConverters.light_color,
zigbeeHerdsmanConverters.toZigbeeConverters.light_alert,
zigbeeHerdsmanConverters.toZigbeeConverters.light_color_colortemp,
zigbeeHerdsmanConverters.toZigbeeConverters.effect,
zigbeeHerdsmanConverters.toZigbeeConverters.ignore_transition,
zigbeeHerdsmanConverters.toZigbeeConverters.cover_position_tilt,
zigbeeHerdsmanConverters.toZigbeeConverters.thermostat_occupied_heating_setpoint,
zigbeeHerdsmanConverters.toZigbeeConverters.tint_scene,
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_step,
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_step,
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_step,
];
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) {
@@ -64,7 +57,7 @@ class EntityPublish extends Extension {
const message = {friendly_name: entityKey};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `entity_not_found`, message}),
stringify({type: `entity_not_found`, message}),
);
}
@@ -78,6 +71,7 @@ class EntityPublish extends Extension {
let options = {};
let device = null;
let definition = null;
let membersState = null;
assert(resolvedEntity.type === 'device' || resolvedEntity.type === 'group');
if (resolvedEntity.type === 'device') {
@@ -91,39 +85,60 @@ class EntityPublish extends Extension {
definition = resolvedEntity.definition;
target = resolvedEntity.endpoint;
converters = resolvedEntity.definition.toZigbee;
options = {...settings.get().device_options, ...resolvedEntity.settings};
options = resolvedEntity.settings;
} else {
converters = groupConverters;
target = resolvedEntity.group;
options = {...settings.get().device_options, ...resolvedEntity.settings};
definition = resolvedEntity.group.members.map((e) => zigbeeHerdsmanConverters.findByDevice(e.getDevice()));
options = resolvedEntity.settings;
definition = resolvedEntity.group.members
.map((e) => zigbeeHerdsmanConverters.findByDevice(e.getDevice())).filter((d) => d);
converters = new Set(groupConverters);
for (const d of definition) {
d.toZigbee.forEach(converters.add, converters);
}
converters = [...converters];
membersState = {};
for (const member of resolvedEntity.group.members) {
const ieeeAddr = member.getDevice().ieeeAddr;
membersState[ieeeAddr] = this.state.get(ieeeAddr);
}
}
// Convert the MQTT message to a Zigbee message.
let json = {};
if (topic.hasOwnProperty('attribute') && topic.attribute) {
json[topic.attribute] = message;
try {
json[topic.attribute] = JSON.parse(message);
} catch (e) {
json[topic.attribute] = message;
}
} else {
try {
json = JSON.parse(message);
} catch (e) {
// Cannot be parsed to JSON, assume state message.
json = {state: message};
if (stateValues.includes(message.toLowerCase())) {
json = {state: message};
} else {
logger.error(`Invalid JSON '${message}', skipping...`);
return false;
}
}
}
if (!json) {
logger.error(`Invalid JSON '${message}', skipping...`);
return;
}
/**
* Home Assistant always publishes 'state', even when e.g. only setting
* the color temperature. This would lead to 2 zigbee publishes, where the first one
* (state) is probably unecessary.
*/
const deviceState = this.state.get(resolvedEntity.settings.ID) || {};
const entityState = this.state.get(resolvedEntity.settings.ID) || {};
if (settings.get().homeassistant) {
const hasColorTemp = json.hasOwnProperty('color_temp');
const hasColor = json.hasOwnProperty('color');
const hasBrightness = json.hasOwnProperty('brightness');
const isOn = deviceState && deviceState.state === 'ON' ? true : false;
const isOn = entityState && entityState.state === 'ON' ? true : false;
if (isOn && (hasColorTemp || hasColor) && !hasBrightness) {
delete json.state;
logger.debug('Skipping state because of Home Assistant');
@@ -151,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;
@@ -170,8 +184,9 @@ class EntityPublish extends Extension {
if (!usedConverters.hasOwnProperty(endpointOrGroupID)) usedConverters[endpointOrGroupID] = [];
const converter = converters.find((c) => c.key.includes(key));
if (usedConverters[endpointOrGroupID].includes(converter)) {
// Use a converter only once (e.g. light_onoff_brightness converters can convert state and brightness)
if (topic.type === 'set' && usedConverters[endpointOrGroupID].includes(converter)) {
// Use a converter for set only once
// (e.g. light_onoff_brightness converters can convert state and brightness)
continue;
}
@@ -184,18 +199,33 @@ class EntityPublish extends Extension {
const meta = {
endpoint_name: endpointName,
options,
message: json,
message: {...json},
logger,
device,
state: deviceState,
state: entityState,
membersState,
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}'`);
const result = await converter.convertSet(actualTarget, key, value, meta);
if (result && result.state) {
const isGroup = actualTarget.constructor.name == 'Group';
// Optimistic behaviour of groups and devices is different.
const publishOptimistic = isGroup || (!options.hasOwnProperty('optimistic') || options.optimistic);
if (result && result.state && publishOptimistic) {
const msg = result.state;
if (endpointName) {
@@ -210,6 +240,12 @@ class EntityPublish extends Extension {
this.publishEntityState(resolvedEntity.settings.ID, msg);
}
if (result && result.membersState && publishOptimistic) {
for (const [ieeeAddr, state] of Object.entries(result.membersState)) {
this.publishEntityState(ieeeAddr, state);
}
}
// It's possible for devices to get out of sync when writing an attribute that's not reportable.
// So here we re-read the value after a specified timeout, this timeout could for example be the
// transition time of a color change or for forcing a state read for devices that don't
@@ -241,7 +277,7 @@ class EntityPublish extends Extension {
const meta = {friendly_name: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
JSON.stringify({type: `zigbee_publish_error`, message, meta}),
stringify({type: `zigbee_publish_error`, message, meta}),
);
}
}
+32 -39
View File
@@ -3,6 +3,7 @@ const logger = require('../util/logger');
const utils = require('../util/utils');
const debounce = require('debounce');
const Extension = require('./extension');
const stringify = require('json-stable-stringify-without-jsonify');
class Receive extends Extension {
constructor(zigbee, mqtt, state, publishEntityState, eventBus) {
@@ -73,35 +74,13 @@ class Receive extends Extension {
return false;
}
if (data.device.ieeeAddr === this.coordinator.ieeeAddr) {
logger.debug('Ignoring message from coordinator');
return false;
}
/**
* Don't handle re-transmitted Xiaomi messages.
* https://github.com/Koenkk/zigbee2mqtt/issues/1238
*
* Some Xiaomi router devices re-transmit messages from Xiaomi end devices.
* The source address of these message is set to the one of the Xiaomi router.
* Therefore it looks like if the message came from the Xiaomi router, while in
* fact it came from the end device.
* Handling these message would result in false state updates.
* The group ID attribute of these message defines the source address of the end device.
* As the same message is also received directly from the end device, it makes no sense
* to handle these messages.
*/
const hasGroupID = data.hasOwnProperty('groupID') && !!data.groupID;
if (utils.isXiaomiDevice(data.device) && utils.isRouter(data.device) && hasGroupID) {
logger.debug('Skipping re-transmitted Xiaomi message');
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.`);
}
@@ -112,6 +91,24 @@ class Receive extends Extension {
}
onZigbeeEvent(type, data, resolvedEntity) {
/**
* Handling of re-transmitted Xiaomi messages.
* https://github.com/Koenkk/zigbee2mqtt/issues/1238
* https://github.com/Koenkk/zigbee2mqtt/issues/3592
*
* Some Xiaomi router devices re-transmit messages from Xiaomi end devices.
* The network address of these message is set to the one of the Xiaomi router.
* Therefore it looks like if the message came from the Xiaomi router, while in
* fact it came from the end device.
* Handling these message would result in false state updates.
* The group ID attribute of these message defines the network address of the end device.
*/
if (type === 'message' && utils.isXiaomiDevice(data.device) && data.device.type === 'Router' && data.groupID) {
logger.debug('Handling re-transmitted Xiaomi message');
data.device = this.zigbee.getDeviceByNetworkAddress(data.groupID);
resolvedEntity = this.zigbee.resolveEntity(data.device);
}
if (!this.shouldProcess(type, data, resolvedEntity)) {
return;
}
@@ -125,25 +122,17 @@ class Receive extends Extension {
if (!converters.length && data.cluster !== 'genOta' && data.cluster !== 'genTime') {
logger.debug(
`No converter available for '${resolvedEntity.definition.model}' with cluster '${data.cluster}' ` +
`and type '${data.type}' and data '${JSON.stringify(data.data)}'`,
`and type '${data.type}' and data '${stringify(data.data)}'`,
);
return;
}
const debounce = resolvedEntity.settings.debounce || settings.get().device_options.debounce;
const debounceIgnore = resolvedEntity.settings.debounce_ignore || settings.get().device_options.debounce_ignore;
// Convert this Zigbee message to a MQTT message.
// Get payload for the message.
// - If a payload is returned publish it to the MQTT broker
// - If NO payload is returned do nothing. This is for non-standard behaviour
// for e.g. click switches where we need to count number of clicks and detect long presses.
const publish = (payload) => {
// Add device linkquality.
if (data.hasOwnProperty('linkquality')) {
payload.linkquality = data.linkquality;
}
if (settings.get().advanced.elapsed) {
const now = Date.now();
if (this.elapsed[data.device.ieeeAddr]) {
@@ -154,18 +143,22 @@ class Receive extends Extension {
}
// Check if we have to debounce
if (debounce) {
this.publishDebounce(data.device.ieeeAddr, payload, debounce, debounceIgnore);
if (resolvedEntity.settings.debounce) {
this.publishDebounce(
data.device.ieeeAddr, payload, resolvedEntity.settings.debounce,
resolvedEntity.settings.debounce_ignore,
);
} else {
this.publishEntityState(data.device.ieeeAddr, payload);
}
};
const meta = {device: data.device, logger};
const meta = {device: data.device, logger, state: this.state.get(data.device.ieeeAddr)};
let payload = {};
converters.forEach((converter) => {
const options = {...settings.get().device_options, ...settings.getDevice(data.device.ieeeAddr)};
const converted = converter.convert(resolvedEntity.definition, data, publish, options, meta);
const converted = converter.convert(
resolvedEntity.definition, data, publish, resolvedEntity.settings, meta,
);
if (converted) {
payload = {...payload, ...converted};
}
+60 -14
View File
@@ -1,5 +1,6 @@
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
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');
@@ -15,6 +16,7 @@ const devicesNotSupportingReporting = [
zigbeeHerdsmanConverters.devices.find((d) => d.model === 'BASICZBR3'),
zigbeeHerdsmanConverters.devices.find((d) => d.model === 'ZM-CSW032-D'),
zigbeeHerdsmanConverters.devices.find((d) => d.model === 'TS0001'),
zigbeeHerdsmanConverters.devices.find((d) => d.model === 'TS0115'),
];
const reportKey = 1;
@@ -79,8 +81,13 @@ const pollOnMessage = [
},
// 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,
// When the bound devices/members of group have the following manufacturerIDs
manufacturerIDs: [
ZigbeeHerdsman.Zcl.ManufacturerCode.Philips,
ZigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
ZigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
ZigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
],
},
{
key: 2,
@@ -96,7 +103,12 @@ const pollOnMessage = [
],
},
read: {cluster: 'genOnOff', attributes: ['onOff']},
manufacturerID: ZigbeeHerdsman.Zcl.ManufacturerCode.Philips,
manufacturerIDs: [
ZigbeeHerdsman.Zcl.ManufacturerCode.Philips,
ZigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
ZigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
ZigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
],
},
];
@@ -106,6 +118,7 @@ class Report extends Extension {
this.queue = new Set();
this.failed = new Set();
this.pollDebouncers = {};
this.enabled = settings.get().advanced.report;
}
shouldIgnoreClusterForDevice(cluster, definition) {
@@ -124,34 +137,48 @@ class Report extends Extension {
if (this.queue.has(device.ieeeAddr) || this.failed.has(device.ieeeAddr)) return;
this.queue.add(device.ieeeAddr);
const term1 = this.enabled ? 'Setup' : 'Disable';
const term2 = this.enabled ? 'setup' : 'disabled';
try {
for (const ep of device.endpoints) {
for (const [cluster, configuration] of Object.entries(clusters)) {
if (ep.supportsInputCluster(cluster) && !this.shouldIgnoreClusterForDevice(cluster, definition)) {
logger.debug(`Setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`);
logger.debug(`${term1} reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`);
const items = [];
for (const entry of configuration) {
if (!entry.hasOwnProperty('condition') || (await entry.condition(ep))) {
items.push({...entry});
const toAdd = {...entry};
if (!this.enabled) toAdd.maximumReportInterval = 0xFFFF;
items.push(toAdd);
delete items[items.length - 1].condition;
}
}
await ep.bind(cluster, this.coordinatorEndpoint);
this.enabled ?
await ep.bind(cluster, this.coordinatorEndpoint) :
await ep.unbind(cluster, this.coordinatorEndpoint);
await ep.configureReporting(cluster, items);
logger.info(
`Successfully setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`,
`Successfully ${term2} reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`,
);
}
}
}
// eslint-disable-next-line
device.meta.reporting = reportKey;
if (this.enabled) {
device.meta.reporting = reportKey;
} else {
delete device.meta.reporting;
this.eventBus.emit('reportingDisabled', {device});
}
this.eventBus.emit(`devicesChanged`);
} catch (error) {
logger.error(
`Failed to setup reporting for '${device.ieeeAddr}' - ${error.stack}`,
`Failed to ${term1.toLowerCase()} reporting for '${device.ieeeAddr}' - ${error.stack}`,
);
this.failed.add(device.ieeeAddr);
@@ -170,14 +197,33 @@ 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 (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
const philipsIgnoreSw = ['5.127.1.26581', '5.130.1.30000'];
if (device.manufacturerName === 'Philips' && philipsIgnoreSw.includes(device.softwareBuildID)) return false;
if (resolvedEntity.device.interviewing === true) return false;
if (device.meta.hasOwnProperty('reporting') && device.meta.reporting === reportKey) return false;
if (!utils.isRouter(device) || utils.isBatteryPowered(device)) return false;
if (device.type !== 'Router' || device.powerSource === 'Battery') return false;
// Gledopto devices don't support reporting.
if (devicesNotSupportingReporting.includes(definition) || definition.vendor === 'Gledopto') return false;
if (this.enabled && device.meta.hasOwnProperty('reporting') && device.meta.reporting === reportKey) {
return false;
}
if (!this.enabled && !device.meta.hasOwnProperty('reporting')) {
return false;
}
return true;
}
@@ -236,7 +282,7 @@ class Report extends Extension {
for (const endpoint of toPoll) {
for (const poll of polls) {
if (poll.manufacturerID !== endpoint.getDevice().manufacturerID) {
if (!poll.manufacturerIDs.includes(endpoint.getDevice().manufacturerID)) {
continue;
}
+33 -8
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,9 +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');
this.publish('bridge/state', 'online', {retain: true, qos: 0});
self.subscribe(`${settings.get().mqtt.base_topic}/#`);
self.publish('bridge/state', 'online', {retain: true, qos: 0});
resolve();
});
@@ -92,27 +98,46 @@ class MQTT extends events.EventEmitter {
}
onMessage(topic, message) {
this.emit('message', {topic, message: message.toString()});
// 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) {
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()) {
logger.error(`Not connected to MQTT server!`);
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
if (!skipLog) {
logger.error(`Not connected to MQTT server!`);
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
}
return;
}
logger.info(`MQTT publish: topic '${topic}', payload '${payload}'`);
if (!skipLog) {
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, () => resolve());
this.client.publish(topic, payload, actualOptions, () => {
this.emit('publishedMessage', {topic, payload, options});
resolve();
});
});
}
}
+23 -1
View File
@@ -57,7 +57,11 @@ class State {
if (settings.get().advanced.cache_state_persistent) {
logger.debug(`Saving state to file ${this.file}`);
const json = JSON.stringify(this.state, null, 4);
fs.writeFileSync(this.file, json, 'utf8');
try {
fs.writeFileSync(this.file, json, 'utf8');
} catch (e) {
logger.error(`Failed to write state to '${this.file}' (${e.message})`);
}
} else {
logger.debug(`Not saving state`);
}
@@ -87,6 +91,24 @@ class State {
this.eventBus.emit('stateChange', {ID, from: fromState, to: toState, reason});
}
removeKey(ID, path) {
if (this.exists(ID)) {
let state = this.state[ID];
for (let i = 0; i < path.length; i++) {
const key = path[i];
if (i === path.length - 1) {
delete state[key];
} else {
if (state[key]) {
state = state[key];
} else {
break;
}
}
}
}
}
remove(ID) {
if (this.exists(ID)) {
delete this.state[ID];
+23 -14
View File
@@ -46,7 +46,7 @@ const transportsToUse = [
winston.format.printf(/* istanbul ignore next */(info) => {
let {timestamp, level, message} = info;
level = level === 'warning' ? 'warn' : level;
const prefix = colorizer.colorize(level, `zigbee2mqtt:${levelWithCompensatedLength[level]}`);
const prefix = colorizer.colorize(level, `Zigbee2MQTT:${levelWithCompensatedLength[level]}`);
return `${prefix} ${timestamp.split('.')[0]}: ${message}`;
}),
),
@@ -82,7 +82,13 @@ if (output.includes('file')) {
/* istanbul ignore next */
if (output.includes('syslog')) {
require('winston-syslog').Syslog;
const options = {app_name: 'zigbee2mqtt', ...settings.get().advanced.log_syslog};
const options = {
app_name: 'Zigbee2MQTT',
format: winston.format.printf(/* istanbul ignore next */(info) => {
return `${info.message}`;
}),
...settings.get().advanced.log_syslog,
};
if (options.hasOwnProperty('type')) options.type = options.type.toString();
transportsToUse.push(new winston.transports.Syslog(options));
}
@@ -109,24 +115,27 @@ function cleanup() {
}
}
// Print to user what logging is enabled
function logOutput() {
if (output.includes('file')) {
if (output.includes('console')) {
logger.info(`Logging to console and directory: '${directory}' filename: ${logFilename}`);
} else {
logger.info(`Logging to directory: '${directory}' filename: ${logFilename}`);
}
logger.cleanup();
} else if (output.includes('console')) {
logger.info(`Logging to console only'`);
}
}
logger.cleanup = cleanup;
logger.logOutput = logOutput;
logger.getLevel = () => transportsToUse[0].level;
logger.setLevel = (level) => {
transportsToUse.forEach((transport) => transport.level = level);
};
// Print to user what logging is enabled
if (output.includes('file')) {
if (output.includes('console')) {
logger.info(`Logging to console and directory: '${directory}' filename: ${logFilename}`);
} else {
logger.info(`Logging to directory: '${directory}' filename: ${logFilename}`);
}
logger.cleanup();
} else if (output.includes('console')) {
logger.info(`Logging to console only'`);
}
// winston.config.syslog.levels doesnt have warn, but is required for syslog.
/* istanbul ignore next */
logger.warn = (message) => logger.warning(message);
+122 -37
View File
@@ -1,5 +1,6 @@
const data = require('./data');
const utils = require('./utils');
// DEPRECATED ZIGBEE2MQTT_CONFIG: https://github.com/Koenkk/zigbee2mqtt/issues/4697
const file = process.env.ZIGBEE2MQTT_CONFIG || data.joinPath('configuration.yaml');
const objectAssignDeep = require(`object-assign-deep`);
const path = require('path');
@@ -16,6 +17,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,
@@ -44,7 +50,6 @@ const defaults = {
experimental: {
// json or attribute or attribute_and_json
output: 'json',
new_api: false,
},
advanced: {
legacy_api: true,
@@ -59,6 +64,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,
@@ -72,7 +78,7 @@ const defaults = {
* Home Assistant requires ALL attributes to be present in ALL MQTT messages send by the device.
* https://community.home-assistant.io/t/missing-value-with-mqtt-only-last-data-set-is-shown/47070/9
*
* Therefore zigbee2mqtt BY DEFAULT caches all values and resend it with every message.
* Therefore Zigbee2MQTT BY DEFAULT caches all values and resend it with every message.
* advanced.cache_state in configuration.yaml allows to configure this.
* https://www.zigbee2mqtt.io/configuration/configuration.html
*/
@@ -152,6 +158,7 @@ const schema = {
reject_unauthorized: {type: 'boolean'},
include_device_information: {type: 'boolean'},
version: {type: 'number'},
force_disable_retain: {type: 'boolean'},
},
required: ['base_topic', 'server'],
},
@@ -160,7 +167,7 @@ const schema = {
properties: {
port: {type: ['string', 'null']},
disable_led: {type: 'boolean'},
adapter: {type: 'string', enum: ['deconz', 'zstack']},
adapter: {type: 'string', enum: ['deconz', 'zstack', 'zigate']},
},
},
blocklist: {type: 'array', items: {type: 'string'}},
@@ -207,6 +214,7 @@ const schema = {
homeassistant_status_topic: {type: 'string'},
timestamp_format: {type: 'string'},
adapter_concurrent: {type: 'number'},
adapter_delay: {type: 'number'},
},
},
map_options: {
@@ -316,15 +324,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);
@@ -333,24 +353,36 @@ function write() {
}
function validate() {
const validate = ajv.compile(schema);
const valid = validate(_settings);
if (_settings.advanced && _settings.advanced.network_key && typeof _settings.advanced.network_key === 'string' &&
_settings.advanced.network_key !== 'GENERATE') {
throw new Error(`advanced.network_key: should be array or 'GENERATE' (is '${_settings.advanced.network_key}')`);
try {
get();
} catch (error) {
if (error.name === 'YAMLException') {
return [
`Your YAML file: '${error.file}' is invalid ` +
`(use https://jsonformatter.org/yaml-validator to find and fix the issue)`,
];
}
return [error.message];
}
const postfixes = utils.getEndpointNames();
const validate = ajv.compile(schema);
validate(_settings);
if (validate.errors) {
return validate.errors.map((v) => `${v.dataPath.substring(1)} ${v.message}`);
}
const errors = [];
if (_settings.advanced && _settings.advanced.network_key && typeof _settings.advanced.network_key === 'string' &&
_settings.advanced.network_key !== 'GENERATE') {
errors.push(`advanced.network_key: should be array or 'GENERATE' (is '${_settings.advanced.network_key}')`);
}
// Verify that all friendly names are unique
const names = [];
const check = (name) => {
if (names.includes(name)) throw new Error(`Duplicate friendly_name '${name}' found`);
if (postfixes.includes(name)) throw new Error(`Following friendly_name are not allowed: '${postfixes}'`);
if (name.match(/.*\/\d*$/)) throw new Error(`Friendly name cannot end with a "/DIGIT" ('${name}')`);
if (name.includes('#') || name.includes('+')) {
throw new Error(`MQTT wildcard (+ and #) not allowed in friendly_name ('${name}')`);
}
if (names.includes(name)) errors.push(`Duplicate friendly_name '${name}' found`);
errors.push(...utils.validateFriendlyName(name));
names.push(name);
};
@@ -361,7 +393,7 @@ function validate() {
if (settingsWithDefaults.mqtt.version !== 5) {
for (const device of Object.values(settingsWithDefaults.devices)) {
if (device.retention) {
throw new Error('MQTT retention requires protocol version 5');
errors.push('MQTT retention requires protocol version 5');
}
}
}
@@ -369,7 +401,7 @@ function validate() {
const checkAvailabilityList = (list, type) => {
list.forEach((e) => {
if (!getEntity(e)) {
throw new Error(`Non-existing entity '${e}' specified in '${type}'`);
errors.push(`Non-existing entity '${e}' specified in '${type}'`);
}
});
};
@@ -379,7 +411,7 @@ function validate() {
checkAvailabilityList(settingsWithDefaults.advanced.availability_blocklist, 'availability_blocklist');
checkAvailabilityList(settingsWithDefaults.advanced.availability_passlist, 'availability_passlist');
return !valid ? validate.errors.map((v) => `${v.dataPath.substring(1)} ${v.message}`) : null;
return errors;
}
function read() {
@@ -407,23 +439,70 @@ 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) || {};
}
// 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);
}
}
};
if (typeof s.groups === 'string') {
const file = data.joinPath(s.groups);
s.groups = yaml.readIfExists(file) || {};
}
readDevicesOrGroups('devices');
readDevicesOrGroups('groups');
return s;
}
function applyEnvironmentVariables(settings) {
const iterate = (obj, path) => {
Object.keys(obj).forEach((key) => {
if (key !== 'type') {
if (key !== 'properties') {
const type = (obj[key].type || 'object').toString();
const envPart = path.reduce((acc, val) => `${acc}${val}_`, '');
const envVariableName = (`ZIGBEE2MQTT_CONFIG_${envPart}${key}`).toUpperCase();
if (process.env[envVariableName]) {
const setting = path.reduce((acc, val, index) => {
acc[val] = acc[val] || {};
return acc[val];
}, settings);
if (type.indexOf('object') >= 0 || type.indexOf('array') >= 0) {
setting[key] = JSON.parse(process.env[envVariableName]);
} else if (type.indexOf('number') >= 0) {
setting[key] = process.env[envVariableName] * 1;
} else if (type.indexOf('boolean') >= 0) {
setting[key] = process.env[envVariableName].toLowerCase() === 'true';
} else {
/* istanbul ignore else */
if (type.indexOf('string') >= 0) {
setting[key] = process.env[envVariableName];
}
}
}
}
if (typeof obj[key] === 'object') {
const newPath = [...path];
if (key !== 'properties') {
newPath.push(key);
}
iterate(obj[key], newPath);
}
}
});
};
iterate(schema.properties, []);
}
function get() {
if (!_settings) {
_settings = read();
applyEnvironmentVariables(_settings);
}
return _settings;
@@ -726,8 +805,14 @@ module.exports = {
// For tests only
_write: write,
_reRead: () => {
_settings = read();
_settingsWithDefaults = objectAssignDeep.noMutate(defaults, get());
_settings = null;
get();
_settingsWithDefaults = null;
getWithDefaults();
},
_clear: () => {
_settings = null;
_settingsWithDefaults = null;
},
_getDefaults: () => defaults,
};
+43 -29
View File
@@ -1,7 +1,8 @@
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const equals = require('fast-deep-equal/es6');
const humanizeDuration = require('humanize-duration');
const data = require('./data');
const vm = require('vm');
const fs = require('fs');
// Xiaomi uses 4151 and 4447 (lumi.plug) as manufacturer ID.
const xiaomiManufacturerID = [4151, 4447];
@@ -38,24 +39,14 @@ const endpointNames = [
'button_6', 'button_7', 'button_8', 'button_9', 'button_10',
'button_11', 'button_12', 'button_13', 'button_14', 'button_15',
'button_16', 'button_17', 'button_18', 'button_19', 'button_20',
'button_light', 'button_fan_high', 'button_fan_med', 'button_fan_low',
'heat', 'cool', 'water',
];
function flatten(arr) {
return arr.reduce((flat, toFlatten) => {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
function capitalize(s) {
return s[0].toUpperCase() + s.slice(1);
}
const forceEndDevice = flatten(
['QBKG03LM', 'QBKG04LM', 'ZNMS13LM', 'ZNMS12LM']
.map((model) => zigbeeHerdsmanConverters.devices.find((d) => d.model === model))
.filter((definition) => definition.hasOwnProperty('zigbeeModel'))
.map((definition) => definition.zigbeeModel));
async function getZigbee2mqttVersion() {
return new Promise((resolve, reject) => {
const git = require('git-last-commit');
@@ -80,6 +71,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;
@@ -124,14 +123,8 @@ function equalsPartial(object, expected) {
return true;
}
function getObjectsProperty(objects, key, defaultValue) {
for (const object of objects) {
if (object.hasOwnProperty(key)) {
return object[key];
}
}
return defaultValue;
function getObjectProperty(object, key, defaultValue) {
return object.hasOwnProperty(key) ? object[key] : defaultValue;
}
function getResponse(request, data, error) {
@@ -155,13 +148,20 @@ 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]);
const sandbox = {
require,
module: {},
};
const converterCode = fs.readFileSync(data.joinPath(moduleName), {encoding: 'utf8'});
vm.runInNewContext(converterCode, sandbox);
converter = sandbox.module.exports;
} else {
converter = require(moduleName);
}
const converter = require(converterModule);
if (Array.isArray(converter)) {
for (const item of converter) {
yield item;
@@ -174,9 +174,13 @@ function* getExternalConvertersDefinitions(settings) {
function toSnakeCase(value) {
if (typeof value === 'object') {
value = {...value};
for (const key of Object.keys(value)) {
value[toSnakeCase(key)] = value[key];
delete value[key];
const keySnakeCase = toSnakeCase(key);
if (key !== keySnakeCase) {
value[keySnakeCase] = value[key];
delete value[key];
}
}
return value;
} else {
@@ -184,25 +188,35 @@ function toSnakeCase(value) {
}
}
function validateFriendlyName(name) {
const errors = [];
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}')`);
}
return errors;
}
module.exports = {
millisecondsToSeconds: (milliseconds) => milliseconds / 1000,
secondsToMilliseconds: (seconds) => seconds * 1000,
getZigbee2mqttVersion,
getDependencyVersion,
objectHasProperties,
toSnakeCase,
getObjectsProperty,
getObjectProperty,
getEndpointNames: () => endpointNames,
isXiaomiDevice: (device) => {
return device.modelID !== 'lumi.router' && xiaomiManufacturerID.includes(device.manufacturerID) &&
(!device.manufacturerName || !device.manufacturerName.startsWith('Trust'));
},
isIkeaTradfriDevice: (device) => ikeaTradfriManufacturerID.includes(device.manufacturerID),
isRouter: (device) => device.type === 'Router' && !forceEndDevice.includes(device.modelID),
isBatteryPowered: (device) => device.powerSource && device.powerSource === 'Battery',
formatDate: (date, type) => formatDate(date, type),
equalsPartial,
getResponse,
capitalize,
parseJSON,
getExternalConvertersDefinitions,
validateFriendlyName,
};
+3 -11
View File
@@ -7,23 +7,15 @@ function read(file) {
return yaml.safeLoad(fs.readFileSync(file, 'utf8'));
} catch (error) {
if (error.name === 'YAMLException') {
error.message =
`\n\n\n` +
`\t====================================================================\n` +
`\tYour YAML file '${file}' is invalid\n` +
`\tUse e.g. https://jsonformatter.org/yaml-validator to find and fix the issue.\n` +
`\t====================================================================\n` +
`\n\n` +
error.message;
error.file = file;
}
throw error;
}
}
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) {
+42 -13
View File
@@ -7,6 +7,7 @@ const utils = require('./util/utils');
const events = require('events');
const objectAssignDeep = require('object-assign-deep');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const stringify = require('json-stable-stringify-without-jsonify');
const endpointNames = utils.getEndpointNames();
const keyEndpointByNumber = new RegExp(`.*/([0-9]*)$`);
@@ -18,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,
@@ -37,12 +39,13 @@ class Zigbee extends events.EventEmitter {
},
adapter: {
concurrent: settings.get().advanced.adapter_concurrent,
delay: settings.get().advanced.adapter_delay,
},
};
const herdsmanSettingsLog = objectAssignDeep.noMutate(herdsmanSettings);
herdsmanSettingsLog.network.networkKey = 'HIDDEN';
logger.debug(`Using zigbee-herdsman with settings: '${JSON.stringify(herdsmanSettingsLog)}'`);
logger.debug(`Using zigbee-herdsman with settings: '${stringify(herdsmanSettingsLog)}'`);
if (herdsmanSettings.network.networkKey === 'GENERATE') {
const newKey = Array.from({length: 16}, () => Math.floor(Math.random() * 255));
@@ -67,8 +70,8 @@ class Zigbee extends events.EventEmitter {
this.herdsman.on('message', (data) => this.emit('event', 'message', data));
logger.info('zigbee-herdsman started');
logger.info(`Coordinator firmware version: '${JSON.stringify(await this.getCoordinatorVersion())}'`);
logger.debug(`Zigbee network parameters: ${JSON.stringify(await this.herdsman.getNetworkParameters())}`);
logger.info(`Coordinator firmware version: '${stringify(await this.getCoordinatorVersion())}'`);
logger.debug(`Zigbee network parameters: ${stringify(await this.herdsman.getNetworkParameters())}`);
for (const device of this.getClients()) {
// If a passlist is used, all other device will be removed from the network.
@@ -102,6 +105,10 @@ class Zigbee extends events.EventEmitter {
return this.herdsman.getCoordinatorVersion();
}
async getNetworkParameters() {
return this.herdsman.getNetworkParameters();
}
async reset(type) {
await this.herdsman.reset(type);
}
@@ -111,12 +118,16 @@ class Zigbee extends events.EventEmitter {
logger.info('zigbee-herdsman stopped');
}
async permitJoin(permit) {
async permitJoin(permit, resolvedEntity) {
permit ?
logger.info('Zigbee: allowing new devices to join.') :
logger.info(`Zigbee: allowing new devices to join${resolvedEntity ? ` via ${resolvedEntity.name}` : ''}.`) :
logger.info('Zigbee: disabling joining new devices.');
await this.herdsman.permitJoin(permit);
if (resolvedEntity && permit) {
await this.herdsman.permitJoin(permit, resolvedEntity.device);
} else {
await this.herdsman.permitJoin(permit);
}
}
async getPermitJoin() {
@@ -135,6 +146,10 @@ class Zigbee extends events.EventEmitter {
return this.herdsman.getDeviceByIeeeAddr(ieeeAddr);
}
getDeviceByNetworkAddress(networkAddress) {
return this.herdsman.getDeviceByNetworkAddress(networkAddress);
}
getDevicesByType(type) {
return this.herdsman.getDevicesByType(type);
}
@@ -157,6 +172,7 @@ class Zigbee extends events.EventEmitter {
`Wrong type '${typeof key}'`,
);
const deviceOptions = settings.get().device_options;
if (typeof key === 'string' || typeof key === 'number') {
if (typeof key === 'number') {
key = key.toString();
@@ -211,12 +227,13 @@ class Zigbee extends events.EventEmitter {
}
return {
type: 'device', device, endpoint, settings: entity, name: entity.friendlyName, definition,
type: 'device', device, endpoint, settings: {...deviceOptions, ...entity},
name: entity.friendlyName, definition,
};
} else {
let group = this.getGroupByID(entity.ID);
if (!group) group = this.createGroup(entity.ID);
return {type: 'group', group, settings: entity, name: entity.friendlyName};
return {type: 'group', group, settings: {...deviceOptions, ...entity}, name: entity.friendlyName};
}
} else if (key.constructor.name === 'Device') {
const setting = settings.getEntity(key.ieeeAddr);
@@ -224,7 +241,7 @@ class Zigbee extends events.EventEmitter {
type: 'device',
device: key,
endpoint: key.endpoints[0],
settings: setting,
settings: {...deviceOptions, ...(setting || {})},
name: setting ? setting.friendlyName : (key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr),
definition: zigbeeHerdsmanConverters.findByDevice(key),
};
@@ -233,7 +250,7 @@ class Zigbee extends events.EventEmitter {
return {
type: 'group',
group: key,
settings: setting,
settings: {...deviceOptions, ...(setting || {})},
name: setting ? setting.friendlyName : key.groupID,
};
}
@@ -276,8 +293,20 @@ class Zigbee extends events.EventEmitter {
}
}
async touchlinkFactoryReset() {
return this.herdsman.touchlinkFactoryReset();
async touchlinkFactoryResetFirst() {
return this.herdsman.touchlinkFactoryResetFirst();
}
async touchlinkFactoryReset(ieeeAddr, channel) {
return this.herdsman.touchlinkFactoryReset(ieeeAddr, channel);
}
async touchlinkIdentify(ieeeAddr, channel) {
await this.herdsman.touchlinkIdentify(ieeeAddr, channel);
}
async touchlinkScan() {
return this.herdsman.touchlinkScan();
}
}
+12294 -4401
View File
File diff suppressed because it is too large Load Diff
+14 -6
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "1.14.2",
"version": "1.17.0",
"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 || ^13 || ^14"
"node": "^10 || ^12 || ^14 || ^15"
},
"keywords": [
"xiaomi",
@@ -33,22 +33,27 @@
},
"homepage": "https://koenkk.github.io/zigbee2mqtt",
"dependencies": {
"ajv": "^6.12.3",
"ajv": "^6.12.4",
"debounce": "^1.2.0",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.1.2",
"git-last-commit": "^1.0.0",
"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.1.0",
"mqtt": "4.2.1",
"object-assign-deep": "^0.4.0",
"rimraf": "^3.0.2",
"semver": "^7.3.2",
"serve-static": "^1.14.1",
"winston": "^3.3.3",
"winston-syslog": "^2.4.4",
"zigbee-herdsman": "0.12.108",
"zigbee-herdsman-converters": "12.0.147"
"ws": "^7.3.1",
"zigbee-herdsman": "0.13.46",
"zigbee-herdsman-converters": "14.0.3",
"zigbee2mqtt-frontend": "0.2.129"
},
"devDependencies": {
"eslint": "*",
@@ -69,5 +74,8 @@
"collectCoverageFrom": [
"lib/**"
]
},
"bin": {
"zigbee2mqtt": "./cli.js"
}
}
+35
View File
@@ -0,0 +1,35 @@
const HomeAssistant = require('../lib/extension/homeassistant');
const stringify = require('json-stable-stringify-without-jsonify');
const homeassistant = new HomeAssistant(null, null, null, null, {on: () => {}});
const mapping = Object.entries(homeassistant._getMapping());
mapping.sort((a, b) => {
if (a[0] > b[0]) {
return -1;
}
if (b[0] > a[0]) {
return 1;
}
return 0;
});
for (const map of mapping) {
map[1].sort((a, b) => {
return JSON.stringify(a).localeCompare(JSON.stringify(b));
});
const toDelete = [];
for (const entry of map[1]) {
if (entry.type === 'light') {
if (entry.discovery_payload.brightness === false) delete entry.discovery_payload.brightness;
if (entry.discovery_payload.color_temp === false) delete entry.discovery_payload.color_temp;
if (entry.discovery_payload.xy === false) delete entry.discovery_payload.xy;
if (entry.discovery_payload.hs === false) delete entry.discovery_payload.hs;
} else if (entry.object_id === 'linkquality') {
toDelete.push(entry);
}
}
toDelete.forEach((d) => map[1].splice(map[1].indexOf(d), 1));
console.log(`${map[0]} - ${stringify(map[1])}`);
}
+3 -3
View File
@@ -1,10 +1,10 @@
#!/bin/bash
function zigbee2mqtt-show-short-info {
echo "Setup for Zigbee2mqtt bridge."
echo "Setup for Zigbee2MQTT bridge."
}
function zigbee2mqtt-show-long-info {
echo "This script installs the Zigbee2mqtt bridge"
echo "This script installs the Zigbee2MQTT bridge"
}
function zigbee2mqtt-show-copyright-info {
@@ -20,7 +20,7 @@ if [ -z "${node}" ]; then #Installing NodeJS if not already installed.
apt install -y nodejs
fi
echo "Cloning Zigbee2mqtt git repository"
echo "Cloning Zigbee2MQTT git repository"
git clone https://github.com/Koenkk/zigbee2mqtt.git /opt/zigbee2mqtt
chown -R pi:pi /opt/zigbee2mqtt
+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();
+7 -1
View File
@@ -1,5 +1,11 @@
const mockDevice = {
mock: true
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
model: ['external_converter_device'],
definition: {
foo: 'bar'
}
};
module.exports = mockDevice;
+50 -8
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');
@@ -27,7 +28,7 @@ describe('Availability', () => {
settings._reRead();
data.writeEmptyState();
jest.useFakeTimers();
settings.set(['advanced', 'availability_timeout'], 10)
settings.set(['advanced', 'availability_timeout'], 10);
controller = new Controller();
mocksClear.forEach((m) => m.mockClear());
await controller.start();
@@ -80,7 +81,7 @@ describe('Availability', () => {
});
it('Should publish availabilty online and query state on reconnect', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const device = zigbeeHerdsman.devices.E11_G13;
const endpoint = device.getEndpoint(1);
device.ping.mockImplementationOnce(() => {throw new Error('failed')});
jest.advanceTimersByTime(11 * 1000);
@@ -89,16 +90,15 @@ describe('Availability', () => {
jest.advanceTimersByTime(11 * 1000);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color/availability',
'zigbee2mqtt/bulb_enddevice/availability',
'online',
{ retain: true, qos: 0 },
expect.any(Function)
);
expect(endpoint.read).toHaveBeenCalledTimes(3);
expect(endpoint.read).toHaveBeenCalledTimes(2);
expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']);
expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']);
expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl', ['currentX', 'currentY', 'colorTemperature']);
});
it('Should fail gracefully when quering state after reconnect fails', async () => {
@@ -172,10 +172,11 @@ describe('Availability', () => {
endpoint.read.mockClear();
await zigbeeHerdsman.events.deviceAnnounce({device});
await flushPromises();
expect(endpoint.read).toHaveBeenCalledTimes(3);
expect(endpoint.read).toHaveBeenCalledTimes(4);
expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']);
expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']);
expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl', ['currentX', 'currentY', 'colorTemperature']);
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 () => {
@@ -342,7 +343,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);
@@ -397,4 +398,45 @@ describe('Availability', () => {
expect.any(Function)
);
});
it('Should clear retained availability topic when device is remove', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', 'bulb_color');
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 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);
});
});
+55 -37
View File
@@ -5,6 +5,7 @@ const MQTT = require('./stub/mqtt');
const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const flushPromises = () => new Promise(setImmediate);
const stringify = require('json-stable-stringify-without-jsonify');
describe('Bind', () => {
let controller;
@@ -24,7 +25,6 @@ describe('Bind', () => {
data.writeDefaultConfiguration();
settings._reRead();
data.writeEmptyState();
settings.set(['experimental', 'new_api'], true);
controller = new Controller();
await controller.start();
await flushPromises();
@@ -32,26 +32,44 @@ describe('Bind', () => {
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 () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(5);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/devices',
expect.any(String),
{ retain: true, qos: 0 },
expect.any(Function)
);
});
it('Should bind only specifief clusters', async () => {
const device = zigbeeHerdsman.devices.remote;
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color', clusters: ["genOnOff"]}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genOnOff"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -61,12 +79,12 @@ describe('Bind', () => {
const endpoint = device.getEndpoint(1);
mockClear(device);
logger.error.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'button'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'button'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote","to":"button","clusters":[],"failed":[]},"status":"error","error":"Nothing to bind"}),
stringify({"data":{"from":"remote","to":"button","clusters":[],"failed":[]},"status":"error","error":"Nothing to bind"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -76,7 +94,7 @@ describe('Bind', () => {
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', JSON.stringify({from: 'remote', to: 'bulb_color'}));
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);
@@ -84,7 +102,7 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
JSON.stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -95,7 +113,7 @@ describe('Bind', () => {
const endpoint = device.getEndpoint(1);
mockClear(device);
endpoint.unbind.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', JSON.stringify({from: 'remote', to: 'Coordinator'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: 'Coordinator'}));
await flushPromises();
expect(endpoint.unbind).toHaveBeenCalledTimes(3);
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target);
@@ -103,7 +121,7 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
JSON.stringify({"data":{"from":"remote","to":"Coordinator","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote","to":"Coordinator","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -113,7 +131,7 @@ describe('Bind', () => {
const target = zigbeeHerdsman.groups.group_1;
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'group_1'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'group_1'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
@@ -121,7 +139,7 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -131,7 +149,7 @@ describe('Bind', () => {
const target = zigbeeHerdsman.groups.group_1;
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: '1'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: '1'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
@@ -139,7 +157,7 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote","to":"1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote","to":"1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -150,12 +168,12 @@ describe('Bind', () => {
const endpoint = device.getEndpoint(1);
mockClear(device);
endpoint.bind.mockImplementation(() => {throw new Error('failed')});
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote","to":"bulb_color","clusters":[],"failed":["genScenes","genOnOff","genLevelCtrl"]},"status":"error","error":"Failed to bind"}),
stringify({"data":{"from":"remote","to":"bulb_color","clusters":[],"failed":["genScenes","genOnOff","genLevelCtrl"]},"status":"error","error":"Failed to bind"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -165,13 +183,13 @@ describe('Bind', () => {
const target = zigbeeHerdsman.devices.QBKG03LM.getEndpoint(3);
const endpoint = device.getEndpoint(2);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote/ep2', to: 'wall_switch_double/right'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote/ep2', to: 'wall_switch_double/right'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote/ep2","to":"wall_switch_double/right","clusters":["genOnOff"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote/ep2","to":"wall_switch_double/right","clusters":["genOnOff"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -181,13 +199,13 @@ describe('Bind', () => {
const target = zigbeeHerdsman.devices.QBKG04LM.getEndpoint(2);
const endpoint = device.getEndpoint(2);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote/ep2', to: 'wall_switch'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote/ep2', to: 'wall_switch'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote/ep2","to":"wall_switch","clusters":["genOnOff"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote/ep2","to":"wall_switch","clusters":["genOnOff"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -197,7 +215,7 @@ describe('Bind', () => {
const target = 'default_bind_group';
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', JSON.stringify({from: 'remote', to: target}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: target}));
await flushPromises();
expect(endpoint.unbind).toHaveBeenCalledTimes(3);
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", 901);
@@ -205,7 +223,7 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", 901);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
JSON.stringify({"data":{"from":"remote","to":"default_bind_group","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
stringify({"data":{"from":"remote","to":"default_bind_group","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -215,11 +233,11 @@ describe('Bind', () => {
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote_not_existing', to: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote_not_existing', to: 'bulb_color'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote_not_existing","to":"bulb_color"},"status":"error","error":"Source device 'remote_not_existing' does not exist"}),
stringify({"data":{"from":"remote_not_existing","to":"bulb_color"},"status":"error","error":"Source device 'remote_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -229,11 +247,11 @@ describe('Bind', () => {
const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
const endpoint = device.getEndpoint(1);
mockClear(device);
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color_not_existing'}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color_not_existing'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
JSON.stringify({"data":{"from":"remote","to":"bulb_color_not_existing"},"status":"error","error":"Target device or group 'bulb_color_not_existing' does not exist"}),
stringify({"data":{"from":"remote","to":"bulb_color_not_existing"},"status":"error","error":"Target device or group 'bulb_color_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -249,7 +267,7 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
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');
@@ -280,7 +298,7 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
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');
@@ -301,7 +319,7 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
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');
@@ -321,7 +339,7 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
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');
@@ -341,7 +359,7 @@ describe('Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target);
expect(endpoint.bind).toHaveBeenCalledWith("genLevelCtrl", target);
expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
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');
@@ -395,7 +413,7 @@ describe('Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", 901);
expect(endpoint.unbind).toHaveBeenCalledWith("genLevelCtrl", 901);
expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", 901);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
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');
+334 -78
View File
File diff suppressed because one or more lines are too long
+18 -7
View File
@@ -6,6 +6,7 @@ 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));
const stringify = require('json-stable-stringify-without-jsonify');
const mocksClear = [MQTT.publish, logger.warn, logger.debug];
@@ -68,6 +69,16 @@ describe('Configure', () => {
expectRemoteConfigured();
});
it('Should reconfigure reporting on reportingDisabled event', async () => {
expectRemoteConfigured();
const device = zigbeeHerdsman.devices.remote;
mockClear(device);
expectRemoteNotConfigured();
controller.eventBus.emit('reportingDisabled', {device})
await flushPromises();
expectRemoteConfigured();
});
it('Should not configure twice', async () => {
expectRemoteConfigured();
const device = zigbeeHerdsman.devices.remote;
@@ -99,38 +110,38 @@ describe('Configure', () => {
expectRemoteConfigured();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
JSON.stringify({"data":{"id": "remote"},"status":"ok"}),
stringify({"data":{"id": "remote"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Fail to configure via MQTT when device does not exist', async () => {
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', JSON.stringify({id: "not_existing_device"}));
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "not_existing_device"}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
JSON.stringify({"data":{"id": "not_existing_device"},"status":"error","error": "Device 'not_existing_device' does not exist"}),
stringify({"data":{"id": "not_existing_device"},"status":"error","error": "Device 'not_existing_device' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Fail to configure via MQTT when configure fails', async () => {
zigbeeHerdsman.devices.remote.getEndpoint(1).bind.mockImplementationOnce(async () => {throw new Error('Bind timeout after 10s')});
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', JSON.stringify({id: "remote"}));
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "remote"}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
JSON.stringify({"data":{"id": "remote"},"status":"error","error": "Failed to configure (Bind timeout after 10s)"}),
stringify({"data":{"id": "remote"},"status":"error","error": "Failed to configure (Bind timeout after 10s)"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
it('Fail to configure via MQTT when device has no configure', async () => {
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', JSON.stringify({id: "bulb", transaction: 20}));
await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "bulb", transaction: 20}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
JSON.stringify({"data":{"id": "bulb"},"status":"error","error": "Device 'bulb' cannot be configured","transaction":20}),
stringify({"data":{"id": "bulb"},"status":"error","error": "Device 'bulb' cannot be configured","transaction":20}),
{retain: false, qos: 0}, expect.any(Function)
);
});
+73 -39
View File
@@ -6,6 +6,7 @@ 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 = [
@@ -30,7 +31,7 @@ describe('Controller', () => {
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"}});
expect(zigbeeHerdsman.start).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.setLED).toHaveBeenCalledTimes(0);
expect(zigbeeHerdsman.setTransmitPower).toHaveBeenCalledTimes(0);
@@ -42,8 +43,8 @@ describe('Controller', () => {
expect(logger.info).toHaveBeenCalledWith('0x0017880104e45518 (0x0017880104e45518): Not supported (EndDevice)');
expect(MQTT.connect).toHaveBeenCalledTimes(1);
expect(MQTT.connect).toHaveBeenCalledWith("mqtt://localhost", {"will": {"payload": "offline", "retain": true, "topic": "zigbee2mqtt/bridge/state"}});
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}',{ retain: true, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/remote', '{"brightness":255}', { retain: true, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}),{ retain: true, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/remote', stringify({"brightness":255}), { retain: true, qos: 0 }, expect.any(Function));
});
it('Start controller when permit join fails', async () => {
@@ -104,8 +105,8 @@ describe('Controller', () => {
data.writeDefaultState();
await controller.start();
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", `{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}`, {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/remote", `{"brightness":255}`, {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/remote", stringify({"brightness":255}), {"qos": 0, "retain": true}, expect.any(Function));
});
it('Start controller should not publish cached states when disabled', async () => {
@@ -148,7 +149,7 @@ describe('Controller', () => {
await flushPromises();
expect(logger.error).toHaveBeenCalledTimes(2);
expect(logger.error).toHaveBeenCalledWith("Not connected to MQTT server!");
expect(logger.error).toHaveBeenCalledWith("Cannot send message: topic: 'zigbee2mqtt/bulb', payload: '{\"state\":\"ON\",\"brightness\":50,\"color_temp\":370,\"linkquality\":99,\"color\":{\"r\":100,\"g\":50,\"b\":10},\"dummy\":{\"1\":\"yes\",\"2\":\"no\"}}");
expect(logger.error).toHaveBeenCalledWith("Cannot send message: topic: 'zigbee2mqtt/bulb', payload: '{\"brightness\":50,\"color\":{\"b\":10,\"g\":50,\"r\":100},\"color_temp\":370,\"dummy\":{\"1\":\"yes\",\"2\":\"no\"},\"linkquality\":99,\"state\":\"ON\"}");
controller.mqtt.client.reconnecting = false;
});
@@ -204,15 +205,6 @@ describe('Controller', () => {
expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false);
});
it('Refuse to start when configuration.yaml is invalid', async () => {
settings.set(['permit_join'], 'invalid');
await controller.start();
expect(logger.error).toHaveBeenCalledWith('Refusing to start, configuration.yaml is not valid, found the following errors:');
expect(logger.error).toHaveBeenCalledWith('\t - permit_join should be boolean');
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(1);
});
it('Start controller with disable_led', async () => {
settings.set(['serial', 'disable_led'], true);
await controller.start();
@@ -264,6 +256,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;
@@ -305,7 +308,7 @@ describe('Controller', () => {
const payload = {device};
await zigbeeHerdsman.events.deviceJoined(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_connected","message":{"friendly_name":"bulb"}}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_connected","message":{"friendly_name":"bulb"}}), {"retain": false, qos: 0}, expect.any(Function));
});
it('acceptJoiningDeviceHandler reject device on blocklist', async () => {
@@ -363,7 +366,7 @@ describe('Controller', () => {
zigbeeHerdsman.events.deviceJoined(payload);
zigbeeHerdsman.events.deviceJoined(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_connected","message":{"friendly_name":"bulb"}}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_connected","message":{"friendly_name":"bulb"}}), {"retain": false, qos: 0}, expect.any(Function));
});
it('On zigbee deviceInterview started', async () => {
@@ -372,7 +375,7 @@ describe('Controller', () => {
const payload = {device, status: 'started'};
await zigbeeHerdsman.events.deviceInterview(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_started","meta":{"friendly_name":"bulb"}}', { retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_started","meta":{"friendly_name":"bulb"}}), { retain: false, qos: 0 }, expect.any(Function));
});
it('On zigbee deviceInterview failed', async () => {
@@ -381,7 +384,7 @@ describe('Controller', () => {
const payload = {device, status: 'failed'};
await zigbeeHerdsman.events.deviceInterview(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_failed","meta":{"friendly_name":"bulb"}}', { retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_failed","meta":{"friendly_name":"bulb"}}), { retain: false, qos: 0 }, expect.any(Function));
});
it('On zigbee deviceInterview successful supported', async () => {
@@ -390,7 +393,7 @@ describe('Controller', () => {
const payload = {device, status: 'successful'};
await zigbeeHerdsman.events.deviceInterview(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_successful","meta":{"friendly_name":"bulb","model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supported":true}}', { retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_successful","meta":{"friendly_name":"bulb","model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supported":true}}), { retain: false, qos: 0 }, expect.any(Function));
});
it('On zigbee deviceInterview successful not supported', async () => {
@@ -399,7 +402,7 @@ describe('Controller', () => {
const payload = {device, status: 'successful'};
await zigbeeHerdsman.events.deviceInterview(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_successful","meta":{"friendly_name":"0x0017880104e45518","supported":false}}', { retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_successful","meta":{"friendly_name":"0x0017880104e45518","supported":false}}), { retain: false, qos: 0 }, expect.any(Function));
});
it('On zigbee event device announce', async () => {
@@ -409,7 +412,7 @@ describe('Controller', () => {
await zigbeeHerdsman.events.deviceAnnounce(payload);
await flushPromises();
expect(logger.debug).toHaveBeenCalledWith(`Device 'bulb' announced itself`);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_announced","message":"announce","meta":{"friendly_name":"bulb"}}', { retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"device_announced","message":"announce","meta":{"friendly_name":"bulb"}}), { retain: false, qos: 0}, expect.any(Function));
});
it('On zigbee event device leave (removed from database and settings)', async () => {
@@ -421,7 +424,7 @@ describe('Controller', () => {
const payload = {ieeeAddr: device.ieeeAddr};
await zigbeeHerdsman.events.deviceLeave(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}', { retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0}, expect.any(Function));
});
it('On zigbee event device leave (removed from database and NOT settings)', async () => {
@@ -432,21 +435,23 @@ describe('Controller', () => {
const payload = {ieeeAddr: device.ieeeAddr};
await zigbeeHerdsman.events.deviceLeave(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}', { retain: false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0}, expect.any(Function));
});
it('Publish entity state attribute output', async () => {
await controller.start();
settings.set(['experimental', 'output'], 'attribute');
MQTT.publish.mockClear();
await controller.publishEntityState('bulb', {state: 'ON', brightness: 50, color_temp: 370, color: {r: 100, g: 50, b: 10}, dummy: {1: 'yes', 2: 'no'}});
await controller.publishEntityState('bulb', {dummy: {1: 'yes', 2: 'no'}, color: {r: 100, g: 50, b: 10}, state: 'ON', test: undefined, test1: null, color_temp: 370, brightness: 50});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/state", "ON", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "50", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/color_temp", "370", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/color", '100,50,10', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-1", 'yes', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-2", 'no', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-1", "yes", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-2", "no", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/test1", '', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/test", '', {"qos": 0, "retain": true}, expect.any(Function));
});
it('Publish entity state attribute_json output', async () => {
@@ -460,7 +465,7 @@ describe('Controller', () => {
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "200", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/color_temp", "370", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/linkquality", "99", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"ON","brightness":200,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":200,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": true}, expect.any(Function));
});
@@ -474,7 +479,7 @@ describe('Controller', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/state", "ON", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "200", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"ON","brightness":200}', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":200}), {"qos": 0, "retain": true}, expect.any(Function));
});
it('Publish entity state attribute_json output filtered (device_options)', async () => {
@@ -487,7 +492,7 @@ describe('Controller', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/state", "ON", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "200", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"ON","brightness":200}', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":200}), {"qos": 0, "retain": true}, expect.any(Function));
});
it('Publish entity state with device information', async () => {
@@ -496,12 +501,12 @@ describe('Controller', () => {
MQTT.publish.mockClear();
await controller.publishEntityState('bulb', {state: 'ON'});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"device":{"friendlyName":"bulb","model":"LED1545G12","ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369,"type":"Router","manufacturerID":4476,"powerSource":"Mains (single phase)","dateCode":null}}', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"device":{"friendlyName":"bulb","model":"LED1545G12","ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369,"type":"Router","manufacturerID":4476,"powerSource":"Mains (single phase)","dateCode":null, "softwareBuildID": null}}), {"qos": 0, "retain": true}, expect.any(Function));
// Unsupported device should have model "unknown"
await controller.publishEntityState('unsupported2', {state: 'ON'});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/unsupported2', '{"state":"ON","device":{"friendlyName":"unsupported2","model":"unknown","ieeeAddr":"0x0017880104e45529","networkAddress":6536,"type":"EndDevice","manufacturerID":0,"powerSource":"Battery","dateCode":null}}', {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/unsupported2', stringify({"state":"ON","device":{"friendlyName":"unsupported2","model":"unknown","ieeeAddr":"0x0017880104e45529","networkAddress":6536,"type":"EndDevice","manufacturerID":0,"powerSource":"Battery","dateCode":null, "softwareBuildID": null}}), {"qos": 0, "retain": false}, expect.any(Function));
});
it('Should publish entity state without retain', async () => {
@@ -510,7 +515,7 @@ describe('Controller', () => {
MQTT.publish.mockClear();
await controller.publishEntityState('bulb', {state: 'ON'});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": false}, expect.any(Function));
});
it('Should publish entity state with retain', async () => {
@@ -519,7 +524,7 @@ describe('Controller', () => {
MQTT.publish.mockClear();
await controller.publishEntityState('bulb', {state: 'ON'});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": true}, expect.any(Function));
});
it('Should publish entity state with expiring retention', async () => {
@@ -530,7 +535,7 @@ describe('Controller', () => {
MQTT.publish.mockClear();
await controller.publishEntityState('bulb', {state: 'ON'});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": true, "properties": {messageExpiryInterval: 37}}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": true, "properties": {messageExpiryInterval: 37}}, expect.any(Function));
});
it('Publish entity state no empty messages', async () => {
@@ -551,12 +556,21 @@ describe('Controller', () => {
await controller.publishEntityState('bulb', {brightness: 200});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", JSON.stringify({state: "ON"}), {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", JSON.stringify({state: "ON", brightness: 200}), {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({state: "ON"}), {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({state: "ON", brightness: 200}), {"qos": 0, "retain": true}, expect.any(Function));
await controller.stop();
expect(data.stateExists()).toBeFalsy();
});
it('Shouldnt crash when it cannot save state', async () => {
data.removeState();
await controller.start();
logger.error.mockClear();
controller.state.file = "/";
await controller.state.save();
expect(logger.error).toHaveBeenCalledWith(`Failed to write state to '/' (EISDIR: illegal operation on a directory, open '/')`);
});
it('Publish should not cache when set', async () => {
settings.set(['advanced', 'cache_state'], false);
data.writeEmptyState();
@@ -566,8 +580,8 @@ describe('Controller', () => {
await controller.publishEntityState('bulb', {brightness: 200});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", "{\"state\":\"ON\"}", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", "{\"brightness\":200}", {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON"}), {"qos": 0, "retain": true}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"brightness":200}), {"qos": 0, "retain": true}, expect.any(Function));
});
it('Publish should not do anything for unknown entity', async () => {
@@ -595,4 +609,24 @@ describe('Controller', () => {
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', { retain: false, qos: 0 }, expect.any(Function));
});
it('Start controller with force_disable_retain', async () => {
settings.set(['mqtt', 'force_disable_retain'], true);
await controller.start();
await flushPromises();
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));
});
});
+7 -1
View File
@@ -67,7 +67,13 @@ describe('Loads external converters', () => {
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'],
definition: {
foo: 'bar'
}
});
});
+213
View File
@@ -0,0 +1,213 @@
const data = require('./stub/data');
require('./stub/logger');
require('./stub/zigbeeHerdsman');
const MQTT = require('./stub/mqtt');
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 = {
implementation: {
listen: jest.fn(),
on: (event, handler) => {mockHTTP.events[event] = handler},
close: jest.fn().mockImplementation((cb) => cb()),
},
variables: {},
events: {},
};
const mockWSocket = {
close: jest.fn(),
};
const mockWS = {
implementation: {
clients: [],
on: (event, handler) => {mockWS.events[event] = handler},
handleUpgrade: jest.fn().mockImplementation((request, socket, head, cb) => {
cb(mockWSocket)
}),
emit: jest.fn(),
},
variables: {},
events: {},
};
const mockNodeStatic = {
implementation: jest.fn(),
variables: {},
events: {},
};
jest.mock('http', () => ({
createServer: jest.fn().mockImplementation((onRequest) => {
mockHTTP.variables.onRequest = onRequest;
return mockHTTP.implementation;
}),
}));
jest.mock("serve-static", () =>
jest.fn().mockImplementation((path) => {
mockNodeStatic.variables.path = path
return mockNodeStatic.implementation
})
);
jest.mock('zigbee2mqtt-frontend', () => ({
getPath: () => 'my/dummy/path',
}));
jest.mock('ws', () => ({
OPEN: 'open',
Server: jest.fn().mockImplementation(() => {
return mockWS.implementation;
}),
}));
describe('Frontend', () => {
let controller;
beforeEach(async () => {
mockWS.implementation.clients = [];
data.writeDefaultConfiguration();
data.writeDefaultState();
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();
await controller.start();
expect(mockNodeStatic.variables.path).toBe("my/dummy/path");
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
const mockWSClient = {
implementation: {
close: jest.fn(),
},
events: {},
};
mockWS.implementation.clients.push(mockWSClient.implementation);
await controller.stop();
expect(mockWSClient.implementation.close).toHaveBeenCalledTimes(1);
expect(mockHTTP.implementation.close).toHaveBeenCalledTimes(1);
});
it('Websocket interaction', async () => {
controller = new Controller();
await controller.start();
// Connect
const mockWSClient = {
implementation: {
on: (event, handler) => {mockWSClient.events[event] = handler},
send: jest.fn(),
readyState: 'open',
},
events: {},
};
mockWS.implementation.clients.push(mockWSClient.implementation);
await mockWS.events.connection(mockWSClient.implementation);
expect(JSON.parse(mockWSClient.implementation.send.mock.calls[0])).toStrictEqual({topic: 'bridge/state', payload: 'online'});
expect(JSON.parse(mockWSClient.implementation.send.mock.calls[12])).toStrictEqual({topic:"remote", payload:{brightness:255, update:{state: "idle"}, update_available: false}});
// Message
MQTT.publish.mockClear();
mockWSClient.implementation.send.mockClear();
mockWSClient.events.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}))
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({state: 'ON'}),
{ retain: false, qos: 0 },
expect.any(Function)
);
mockWSClient.events.message(undefined);
mockWSClient.events.message("");
mockWSClient.events.message(null);
await flushPromises();
// Received message on socket
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(4);
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON'}}));
// Shouldnt set when not ready
mockWSClient.implementation.send.mockClear();
mockWSClient.implementation.readyState = 'close';
mockWSClient.events.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}))
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(0);
// Send last seen on connect
mockWSClient.implementation.send.mockClear();
mockWSClient.implementation.readyState = 'open';
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}});
});
it('onReques/onUpgrade', async () => {
controller = new Controller();
await controller.start();
const mockSocket = {destroy: jest.fn()};
mockWS.implementation.handleUpgrade.mockClear();
mockHTTP.events.upgrade({url: 'http://localhost:8080/api'}, mockSocket, 3);
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(1);
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledWith({"url": "http://localhost:8080/api"}, mockSocket, 3, expect.any(Function));
mockWS.implementation.handleUpgrade.mock.calls[0][3](99);
expect(mockWS.implementation.emit).toHaveBeenCalledWith('connection', 99, {"url": "http://localhost:8080/api"});
mockHTTP.variables.onRequest(1, 2);
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
expect(mockNodeStatic.implementation).toHaveBeenCalledWith(1, 2, expect.any(Function));
});
it('Static server', async () => {
controller = new Controller();
await controller.start();
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();
await controller.start();
const mockSocket = {destroy: jest.fn()};
mockWS.implementation.handleUpgrade.mockClear();
mockHTTP.events.upgrade({url: '/api'}, mockSocket, mockWSocket);
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(1);
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
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});
});
});
+97 -69
View File
@@ -1,11 +1,15 @@
const data = require('./stub/data');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const stringify = require('json-stable-stringify-without-jsonify');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
zigbeeHerdsman.returnDevices.push('0x00124b00120144ae');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b3');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b2');
zigbeeHerdsman.returnDevices.push('0x0017880104e45542');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b4');
zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b7');
const MQTT = require('./stub/mqtt');
const Controller = require('../lib/controller');
const flushPromises = () => new Promise(setImmediate);
@@ -16,7 +20,6 @@ describe('Groups', () => {
beforeEach(() => {
data.writeEmptyState();
settings.set(['experimental', 'new_api'], true);
controller = new Controller();
Object.values(zigbeeHerdsman.groups).forEach((g) => g.members = []);
data.writeDefaultConfiguration();
@@ -46,6 +49,19 @@ describe('Groups', () => {
expect(zigbeeHerdsman.groups.group_1.members).toStrictEqual([]);
});
it('Apply group updates remove handle fail', async () => {
const endpoint = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
endpoint.removeFromGroup.mockImplementationOnce(() => {throw new Error("failed!")});
const group = zigbeeHerdsman.groups.group_1;
group.members.push(endpoint);
settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false,}});
logger.error.mockClear();
await controller.start();
await flushPromises();
expect(logger.error).toHaveBeenCalledWith(`Failed to remove 'bulb_color' from 'group_1'`);
expect(zigbeeHerdsman.groups.group_1.members).toStrictEqual([endpoint]);
});
it('Move to non existing group', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
@@ -94,7 +110,7 @@ describe('Groups', () => {
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/1`]);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group_1"}}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group_1"}}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Legacy api: Add to group with slashes via MQTT', async () => {
@@ -109,13 +125,7 @@ describe('Groups', () => {
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group/with/slashes').devices).toStrictEqual([`${device.ieeeAddr}/1`]);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"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');
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));
});
it('Legacy api: Add to group via MQTT with postfix', async () => {
@@ -158,7 +168,7 @@ describe('Groups', () => {
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_remove","message":{"friendly_name":"bulb_color","group":"group_1"}}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_remove","message":{"friendly_name":"bulb_color","group":"group_1"}}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Legacy api: Remove from group via MQTT when in zigbee but not in settings', async () => {
@@ -229,7 +239,7 @@ describe('Groups', () => {
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_remove_all","message":{"friendly_name":"wall_switch_double"}}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_remove_all","message":{"friendly_name":"wall_switch_double"}}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Remove from group all deprecated', async () => {
@@ -279,8 +289,26 @@ describe('Groups', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON","linkquality":10}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function));
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 not republish identical optimistic group states', async () => {
const device1 = zigbeeHerdsman.devices.bulb_2;
const device2 = zigbeeHerdsman.devices.bulb_color_2;
const group = zigbeeHerdsman.groups.group_tradfri_remote;
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await zigbeeHerdsman.events.message({data: {onOff: 1}, cluster: 'genOnOff', device: device1, endpoint: device1.getEndpoint(1), type: 'attributeReport', linkquality: 10});
await zigbeeHerdsman.events.message({data: {onOff: 1}, cluster: 'genOnOff', device: device2, endpoint: device2.getEndpoint(1), type: 'attributeReport', linkquality: 10});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_tradfri_remote", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_2", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color_2", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_with_tradfri", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should publish state change of all members when a group changes its state', async () => {
@@ -293,11 +321,11 @@ describe('Groups', () => {
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function));
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 () => {
@@ -310,11 +338,11 @@ describe('Groups', () => {
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON', brightness: 100}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON', brightness: 100}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON","brightness":100}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON","brightness":100}), {"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('Shouldnt publish group state change when a group is not optimistic', async () => {
@@ -332,7 +360,7 @@ describe('Groups', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON","linkquality":10}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should publish state change of another group with shared device when a group changes its state', async () => {
@@ -349,12 +377,12 @@ describe('Groups', () => {
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function));
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));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should not publish state change off if any lights within are still on when changed via device', async () => {
@@ -371,14 +399,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF'}));
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({state:"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should not publish state change off if any lights within are still on when changed via shared group', async () => {
@@ -396,15 +424,15 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_2/set', JSON.stringify({state: 'OFF'}));
await MQTT.events.message('zigbee2mqtt/group_2/set', stringify({state: 'OFF'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should publish state change off if all lights within turn off', async () => {
@@ -421,17 +449,17 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF'}));
await MQTT.events.message('zigbee2mqtt/bulb/set', JSON.stringify({state: 'OFF'}));
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await MQTT.events.message('zigbee2mqtt/bulb/set', stringify({state: 'OFF'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"OFF","brightness":0}', {"retain": true, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"OFF"}), {"retain": true, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"OFF"}), {"retain": false, qos: 0}, expect.any(Function));
});
it('Should publish state change off even when missing current state', async () => {
@@ -448,17 +476,17 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
MQTT.publish.mockClear();
controller.state.state = {};
await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF'}));
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function));
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));
});
it('Add to group via MQTT', async () => {
@@ -470,14 +498,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({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',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -492,14 +520,14 @@ describe('Groups', () => {
endpoint.addToGroup.mockImplementationOnce(() => {throw new Error('timeout')});
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
expect(MQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"error","error":"Failed to add from group (timeout)"}),
stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"error","error":"Failed to add from group (timeout)"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -513,14 +541,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group/with/slashes', device: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group/with/slashes', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group/with/slashes').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',
JSON.stringify({"data":{"device":"bulb_color","group":"group/with/slashes"},"status":"ok"}),
stringify({"data":{"device":"bulb_color","group":"group/with/slashes"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -533,14 +561,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'wall_switch_double/right'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'wall_switch_double/right'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]);
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',
JSON.stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -553,16 +581,16 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'wall_switch_double/right'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'wall_switch_double/right'}));
await flushPromises();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/3'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: '0x0017880104e45542/3'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]);
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',
JSON.stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -576,14 +604,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
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/remove',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -597,14 +625,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual(['dummy']);
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/remove',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -618,14 +646,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/3'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: '0x0017880104e45542/3'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
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/remove',
JSON.stringify({"data":{"device":"0x0017880104e45542/3","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"0x0017880104e45542/3","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -639,14 +667,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'wall_switch_double/3'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'wall_switch_double/3'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
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/remove',
JSON.stringify({"data":{"device":"wall_switch_double/3","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"wall_switch_double/3","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -660,14 +688,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/right'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: '0x0017880104e45542/right'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
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/remove',
JSON.stringify({"data":{"device":"0x0017880104e45542/right","group":"group_1"},"status":"ok"}),
stringify({"data":{"device":"0x0017880104e45542/right","group":"group_1"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -681,14 +709,14 @@ describe('Groups', () => {
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove_all', JSON.stringify({device: '0x0017880104e45542/right'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove_all', stringify({device: '0x0017880104e45542/right'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(settings.getGroup('group_1').devices).toStrictEqual([]);
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/remove_all',
JSON.stringify({"data":{"device":"0x0017880104e45542/right"},"status":"ok"}),
stringify({"data":{"device":"0x0017880104e45542/right"},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -698,12 +726,12 @@ describe('Groups', () => {
await flushPromises();
logger.error.mockClear();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1_not_existing', device: 'bulb_color'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1_not_existing', device: 'bulb_color'}));
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
JSON.stringify({"data":{"device":"bulb_color","group":"group_1_not_existing"},"status":"error","error":"Group 'group_1_not_existing' does not exist"}),
stringify({"data":{"device":"bulb_color","group":"group_1_not_existing"},"status":"error","error":"Group 'group_1_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -713,13 +741,13 @@ describe('Groups', () => {
await flushPromises();
logger.error.mockClear();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color_not_existing'}));
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color_not_existing'}));
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
JSON.stringify({"data":{"device":"bulb_color_not_existing","group":"group_1"},"status":"error","error":"Device 'bulb_color_not_existing' does not exist"}),
stringify({"data":{"device":"bulb_color_not_existing","group":"group_1"},"status":"error","error":"Device 'bulb_color_not_existing' does not exist"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
});
});
+529 -89
View File
@@ -1,5 +1,6 @@
const data = require('./stub/data');
const settings = require('../lib/util/settings');
const stringify = require('json-stable-stringify-without-jsonify');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const flushPromises = () => new Promise(setImmediate);
@@ -9,10 +10,12 @@ 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}`;
this.version = `Zigbee2MQTT ${this.version.version}`;
jest.useRealTimers();
data.writeDefaultConfiguration();
settings._reRead();
@@ -21,19 +24,6 @@ describe('HomeAssistant extension', () => {
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').devices.forEach((d) => {
if (!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: () => {}});
@@ -76,12 +66,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -101,12 +91,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/humidity/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -126,12 +116,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/pressure/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -151,12 +141,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/battery/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -176,12 +166,120 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/linkquality/config',
JSON.stringify(payload),
stringify(payload),
{ 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,
"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),
);
@@ -217,12 +315,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -242,12 +340,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/humidity/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -267,12 +365,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/pressure/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -323,14 +421,14 @@ describe('HomeAssistant extension', () => {
'model': 'custom model',
'manufacturer': 'From Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'expire_after': 90,
'icon': 'mdi:test',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -349,19 +447,71 @@ describe('HomeAssistant extension', () => {
'model': 'custom model',
'manufacturer': 'Not from Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'expire_after': 30,
'icon': 'mdi:test',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/humidity/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Should discover devices with overriden user configuration affecting type and object_id', async () => {
settings.set(['devices', '0x0017880104e45541'], {
friendly_name: 'my_switch',
homeassistant: {
switch: {
type: 'light',
object_id: 'light'
},
light: {
type: 'this should be ignored',
name: 'my_light_name_override'
}
},
})
controller = new Controller(false);
await controller.start();
let payload;
await flushPromises();
payload = {
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
"command_topic": "zigbee2mqtt/my_switch/set",
"device": {
"identifiers": [
"zigbee2mqtt_0x0017880104e45541"
],
"manufacturer": "Xiaomi",
"model": "Aqara single key wired wall switch without neutral wire. Doesn't work as a router and doesn't support power meter (QBKG04LM)",
"name": "my_switch",
"sw_version": this.version
},
"json_attributes_topic": "zigbee2mqtt/my_switch",
"name": "my_light_name_override",
"payload_off": "OFF",
"payload_on": "ON",
"state_topic": "zigbee2mqtt/my_switch",
"unique_id": "0x0017880104e45541_light_zigbee2mqtt",
"value_template": "{{ value_json.state }}"
}
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/0x0017880104e45541/light/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Shouldnt discover devices when homeassistant null is set in device options', async () => {
settings.set(['devices', '0x0017880104e45522'], {
homeassistant: null,
@@ -403,7 +553,7 @@ describe('HomeAssistant extension', () => {
"smart"
],
"json_attributes_topic":"zigbee2mqtt/fan",
"name":"fan_fan",
"name":"fan",
"unique_id":"0x0017880104e45548_fan_zigbee2mqtt",
"device":{
"identifiers":[
@@ -414,12 +564,74 @@ describe('HomeAssistant extension', () => {
"model":"Universal wink enabled white ceiling fan premier remote control (99432)",
"manufacturer":"Hampton Bay"
},
"availability_topic":"zigbee2mqtt/bridge/state"
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/fan/0x0017880104e45548/fan/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Should discover thermostat devices', async () => {
controller = new Controller(false);
await controller.start();
let payload;
await flushPromises();
payload = {
"action_template":"{% set values = {'idle':'off','heat':'heating','cool':'cooling','fan only':'fan'} %}{{ values[value_json.running_state] }}",
"action_topic":"zigbee2mqtt/TS0601_thermostat",
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
"away_mode_command_topic":"zigbee2mqtt/TS0601_thermostat/set/away_mode",
"away_mode_state_template":"{{ value_json.away_mode }}",
"away_mode_state_topic":"zigbee2mqtt/TS0601_thermostat",
"current_temperature_template":"{{ value_json.local_temperature }}",
"current_temperature_topic":"zigbee2mqtt/TS0601_thermostat",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017882104a44559"
],
"manufacturer":"TuYa",
"model":"Radiator valve with thermostat (TS0601_thermostat)",
"name":"TS0601_thermostat",
"sw_version": this.version,
},
"hold_command_topic":"zigbee2mqtt/TS0601_thermostat/set/preset",
"hold_modes":[
"schedule",
"manual",
"boost",
"complex",
"comfort",
"eco"
],
"hold_state_template":"{{ value_json.preset }}",
"hold_state_topic":"zigbee2mqtt/TS0601_thermostat",
"json_attributes_topic":"zigbee2mqtt/TS0601_thermostat",
"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":[
"heat", "auto", "off"
],
"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 }}",
"temperature_state_topic":"zigbee2mqtt/TS0601_thermostat",
"temperature_unit":"C",
"unique_id":"0x0017882104a44559_climate_zigbee2mqtt"
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/climate/0x0017882104a44559/climate/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -433,28 +645,28 @@ describe('HomeAssistant extension', () => {
await flushPromises();
payload = {
command_topic: 'zigbee2mqtt/smart_vent/set',
position_topic: 'zigbee2mqtt/smart_vent',
set_position_topic: 'zigbee2mqtt/smart_vent/set',
command_topic: 'zigbee2mqtt/smart vent/set',
position_topic: 'zigbee2mqtt/smart vent',
set_position_topic: 'zigbee2mqtt/smart vent/set',
set_position_template: '{ "position": {{ position }} }',
value_template: '{{ value_json.position }}',
json_attributes_topic: 'zigbee2mqtt/smart_vent',
name: 'smart_vent_cover',
json_attributes_topic: 'zigbee2mqtt/smart vent',
name: 'smart vent',
unique_id: '0x0017880104e45551_cover_zigbee2mqtt',
device:
{
identifiers: [ 'zigbee2mqtt_0x0017880104e45551' ],
name: 'smart_vent',
name: 'smart vent',
sw_version: this.version,
model: 'Smart vent (SV01)',
manufacturer: 'Keen Home'
},
availability_topic: 'zigbee2mqtt/bridge/state'
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/cover/0x0017880104e45551/cover/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -483,12 +695,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'my_custom_discovery_topic/sensor/0x0017880104e45522/temperature/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -548,12 +760,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
JSON.stringify(payloadHA),
stringify(payloadHA),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -569,8 +781,6 @@ 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 () => {
@@ -587,13 +797,13 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
'{"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',
'{"brightness":255,"update_available":false}',
stringify({"brightness":255,"update_available":false,"update":{"state":"idle"}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -613,13 +823,13 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
'{"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',
'{"brightness":255,"update_available":false}',
stringify({"brightness":255,"update_available":false,"update":{"state":"idle"}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -676,12 +886,12 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/weather_sensor/availability',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -741,8 +951,10 @@ describe('HomeAssistant extension', () => {
controller = new Controller(false);
await controller.start();
await flushPromises();
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45522/action_double/config', stringify({topic: 'zigbee2mqtt/weather_sensor/action'}));
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/rename', '{"old": "weather_sensor", "new": "weather_sensor_renamed"}');
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "weather_sensor", "to": "weather_sensor_renamed","homeassistant_rename":true}));
await flushPromises();
const payload = {
@@ -760,12 +972,82 @@ describe('HomeAssistant extension', () => {
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability_topic': 'zigbee2mqtt/bridge/state',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
null,
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45522/action_double/config',
stringify({
"automation_type":"trigger",
"type":"action",
"subtype":"double",
"payload":"double",
"topic":"zigbee2mqtt/weather_sensor_renamed/action",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017880104e45522"
],
"name":"weather_sensor_renamed",
"sw_version": this.version,
"model":"Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)",
"manufacturer":"Xiaomi"
}
}),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Shouldnt refresh discovery when device is renamed and homeassistant_rename is false', async () => {
controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "weather_sensor", "to": "weather_sensor_renamed","homeassistant_rename":false}));
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
null,
{ retain: true, qos: 0 },
expect.any(Function),
);
const payload = {
'unit_of_measurement': '°C',
'device_class': 'temperature',
'value_template': '{{ value_json.temperature }}',
'state_topic': 'zigbee2mqtt/weather_sensor_renamed',
'json_attributes_topic': 'zigbee2mqtt/weather_sensor_renamed',
'name': 'weather_sensor_renamed_temperature',
'unique_id': '0x0017880104e45522_temperature_zigbee2mqtt',
'device': {
'identifiers': ['zigbee2mqtt_0x0017880104e45522'],
'name': 'weather_sensor_renamed',
'sw_version': this.version,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -781,7 +1063,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":[
@@ -792,12 +1074,12 @@ describe('HomeAssistant extension', () => {
"model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)",
"manufacturer":"IKEA"
},
"availability_topic":"zigbee2mqtt/bridge/state"
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/binary_sensor/0x000b57fffec6a5b2/update_available/config',
JSON.stringify(payload),
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
@@ -816,11 +1098,35 @@ describe('HomeAssistant extension', () => {
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.WXKG11LM;
const payload = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
const payload1 = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload1);
await flushPromises();
const discoverPayload = {
const discoverPayloadAction = {
"automation_type":"trigger",
"type":"action",
"subtype":"single",
"payload":"single",
"topic":"zigbee2mqtt/button/action",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017880104e45520"
],
"name":"button",
"sw_version": this.version,
"model":"Aqara wireless switch (WXKG11LM)",
"manufacturer":"Xiaomi"
}
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/action_single/config',
stringify(discoverPayloadAction),
{ retain: true, qos: 0 },
expect.any(Function),
);
const discoverPayloadClick = {
"automation_type":"trigger",
"type":"click",
"subtype":"single",
@@ -839,11 +1145,18 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/click_single/config',
JSON.stringify(discoverPayload),
stringify(discoverPayloadClick),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button/action',
'single',
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button/click',
'single',
@@ -853,35 +1166,93 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
JSON.stringify({click: "single", linkquality: 10}),
stringify({action: "single", click: "single"}),
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
JSON.stringify({linkquality: 10, click: ""}),
stringify({action: ""}),
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({click: ""}),
{ retain: false, qos: 0 },
expect.any(Function),
);
// Should only discover it once
MQTT.publish.mockClear();
await zigbeeHerdsman.events.message(payload);
await zigbeeHerdsman.events.message(payload1);
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/click_single/config',
JSON.stringify(discoverPayload),
'homeassistant/device_automation/0x0017880104e45520/action_single/config',
stringify(discoverPayloadAction),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).not.toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/click_single/config',
stringify(discoverPayloadClick),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button/action',
'single',
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button/click',
'single',
{ retain: false, qos: 0 },
expect.any(Function),
);
// Shouldn't rediscover when already discovered in previous session
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant')._clearDiscoveredTrigger();
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45520/action_double/config', stringify({topic: 'zigbee2mqtt/button/action'}));
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45520/action_double/config', stringify({topic: 'zigbee2mqtt/button/action'}));
await flushPromises();
MQTT.publish.mockClear();
const payload2 = {data: {'32768': 2}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload2);
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith('homeassistant/device_automation/0x0017880104e45520/action_double/config', expect.any(String), expect.any(Object), expect.any(Function));
// Should rediscover when already discovered in previous session but with diferent name
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant')._clearDiscoveredTrigger();
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45520/action_double/config', stringify({topic: 'zigbee2mqtt/button_other_name/action'}));
await flushPromises();
MQTT.publish.mockClear();
await zigbeeHerdsman.events.message(payload2);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('homeassistant/device_automation/0x0017880104e45520/action_double/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,
friendly_name: 'weather_sensor',
retain: false,
})
controller = new Controller(false);
await controller.start();
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).toContain('homeassistant/sensor/0x0017880104e45520/action/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/battery/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/linkquality/config');
});
it('Should disable Home Assistant legacy triggers', async () => {
@@ -898,16 +1269,17 @@ describe('HomeAssistant extension', () => {
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.WXKG11LM;
settings.set(['devices', device.ieeeAddr, 'legacy'], false);
const payload = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
const discoverPayload = {
"automation_type":"trigger",
"type":"click",
"type":"action",
"subtype":"single",
"payload":"single",
"topic":"zigbee2mqtt/button/click",
"topic":"zigbee2mqtt/button/action",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017880104e45520"
@@ -920,14 +1292,14 @@ describe('HomeAssistant extension', () => {
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/click_single/config',
JSON.stringify(discoverPayload),
'homeassistant/device_automation/0x0017880104e45520/action_single/config',
stringify(discoverPayload),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button/click',
'zigbee2mqtt/button/action',
'single',
{ retain: false, qos: 0 },
expect.any(Function),
@@ -935,7 +1307,7 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
JSON.stringify({click: "single", linkquality: 10}),
stringify({action: "single"}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -949,11 +1321,11 @@ describe('HomeAssistant extension', () => {
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', JSON.stringify({state: 'ON', brightness: 20}));
await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', stringify({state: 'ON', brightness: 20}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB', JSON.stringify({state_l2:"ON", brightness_l2:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l2', JSON.stringify({state:"ON", brightness:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l1', JSON.stringify({}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB', stringify({state_l2:"ON", brightness_l2:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l2', stringify({state:"ON", brightness:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l1', stringify({}), {"qos": 0, "retain": false}, expect.any(Function));
});
it('Shouldnt crash in onPublishEntityState on group publish', async () => {
@@ -963,30 +1335,31 @@ describe('HomeAssistant extension', () => {
logger.error.mockClear();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
expect(logger.error).toHaveBeenCalledTimes(0);
});
it('Should counter an action/click payload with an empty payload', async () => {
it('Should counter an action payload with an empty payload', async () => {
controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.WXKG11LM;
settings.set(['devices', device.ieeeAddr, 'legacy'], false);
const data = {onOff: 1}
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'single', linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single'});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/button');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({click: '', linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({action: ''});
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('homeassistant/device_automation/0x0017880104e45520/click_single/config');
expect(MQTT.publish.mock.calls[3][0]).toStrictEqual('zigbee2mqtt/button/click');
expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('homeassistant/device_automation/0x0017880104e45520/action_single/config');
expect(MQTT.publish.mock.calls[3][0]).toStrictEqual('zigbee2mqtt/button/action');
});
it('Load Home Assistant mapping from external converters', async () => {
@@ -1010,6 +1383,73 @@ describe('HomeAssistant extension', () => {
command_topic: true,
},
};
expect(ha._getMapping()['external_converters_device']).toStrictEqual([homeassistantSwitch]);
expect(ha._getMapping()['external_converters_device']).toEqual([homeassistantSwitch]);
});
it('Should clear outdated configs', async () => {
controller = new Controller(false);
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'}]}));
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'}]}));
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'}]}));
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'}]}));
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));
// Non-existing device but invalid payload -> clear
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/sensor/0x123/temperature/config', '1}3');
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(0);
// Existing device, device automation -> don't clear
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).toHaveBeenCalledTimes(0);
// Device automation of different instance -> don't clear
MQTT.publish.mockClear();
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));
});
});
+32 -31
View File
@@ -2,6 +2,7 @@ const data = require('../stub/data');
const logger = require('../stub/logger');
const zigbeeHerdsman = require('../stub/zigbeeHerdsman');
const MQTT = require('../stub/mqtt');
const stringify = require('json-stable-stringify-without-jsonify');
const path = require('path');
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
const settings = require('../../lib/util/settings');
@@ -29,7 +30,7 @@ describe('Bridge legacy', () => {
it('Should publish bridge configuration on startup', async () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/config',
JSON.stringify({"version":this.version.version,"commit":this.version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1, "revision": 20190425}},"log_level":'info',"permit_join":false}),
stringify({"version":this.version.version,"commit":this.version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1, "revision": 20190425}},"network":{"panID":5674,"extendedPanID":[0,11,22],"channel":15},"log_level":'info',"permit_join":false}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -56,7 +57,7 @@ describe('Bridge legacy', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: "device_whitelisted", "message": {friendly_name: "bulb_color"}}),
stringify({type: "device_whitelisted", "message": {friendly_name: "bulb_color"}}),
{ retain: false, qos: 0 },
expect.any(Function)
);
@@ -67,7 +68,7 @@ describe('Bridge legacy', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: "device_whitelisted", "message": {friendly_name: "bulb"}}),
stringify({type: "device_whitelisted", "message": {friendly_name: "bulb"}}),
{ retain: false, qos: 0 },
expect.any(Function)
);
@@ -85,12 +86,12 @@ describe('Bridge legacy', () => {
expect(settings.getDevice('bulb_color')).toStrictEqual(
{"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": false}
);
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {retain: true}}));
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {retain: true}}));
await flushPromises();
expect(settings.getDevice('bulb_color')).toStrictEqual(
{"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": true}
);
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', optionswrong: {retain: true}}));
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', optionswrong: {retain: true}}));
await flushPromises();
expect(settings.getDevice('bulb_color')).toStrictEqual(
{"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": true}
@@ -100,17 +101,17 @@ describe('Bridge legacy', () => {
expect(settings.getDevice('bulb_color')).toStrictEqual(
{"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": true}
);
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {random_setting: true}}));
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {random_setting: true}}));
await flushPromises();
expect(settings.getDevice('bulb_color')).toStrictEqual(
{"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "random_setting": true, "retain": true}
);
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {options: {random_1: true}}}));
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {options: {random_1: true}}}));
await flushPromises();
expect(settings.getDevice('bulb_color')).toStrictEqual(
{"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "random_setting": true, "retain": true, options: {random_1: true}}
);
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {options: {random_2: false}}}));
MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {options: {random_2: false}}}));
await flushPromises();
expect(settings.getDevice('bulb_color')).toStrictEqual(
{"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "random_setting": true, "retain": true, options: {random_1: true, random_2: false}}
@@ -177,7 +178,7 @@ describe('Bridge legacy', () => {
const payload = JSON.parse(MQTT.publish.mock.calls[0][1]);
expect(payload.length).toStrictEqual(Object.values(zigbeeHerdsman.devices).length);
expect(payload[1]).toStrictEqual({"ieeeAddr": "0x00124b00120144ae", "type": "Coordinator", "dateCode": "20190425", "friendly_name": "Coordinator", networkAddress: 0, softwareBuildID: "z-Stack", lastSeen: 100});
expect(payload[2]).toStrictEqual({"dateCode": null, "friendly_name": "bulb", "ieeeAddr": "0x000b57fffec6a5b2", "lastSeen": 1000, "manufacturerID": 4476, "model": "LED1545G12", "modelID": "TRADFRI bulb E27 WS opal 980lm", "networkAddress": 40369, "powerSource": "Mains (single phase)", "type": "Router", "description": "TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white", "vendor": "IKEA"});
expect(payload[2]).toStrictEqual({"dateCode": null, "friendly_name": "bulb", "ieeeAddr": "0x000b57fffec6a5b2", "lastSeen": 1000, "manufacturerID": 4476, "model": "LED1545G12", "modelID": "TRADFRI bulb E27 WS opal 980lm", "networkAddress": 40369, "powerSource": "Mains (single phase)", "softwareBuildID": null, "type": "Router", "description": "TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white", "vendor": "IKEA"});
Date.now = now;
});
@@ -187,25 +188,25 @@ describe('Bridge legacy', () => {
await flushPromises();
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/log');
const payload = JSON.parse(MQTT.publish.mock.calls[0][1]);
expect(payload).toStrictEqual({"message": [{"ID": 1, "friendly_name": "group_1", "retain": false, 'devices': [], optimistic: true}, {"ID": 2, "friendly_name": "group_2", "retain": false, "devices": [], optimistic: true}, {"ID": 11, "friendly_name": "group_with_tradfri", "retain": false, "devices": ['bulb_2'], optimistic: true}, {"ID": 15071, "friendly_name": "group_tradfri_remote", "retain": false, "devices": ['bulb_color_2', 'bulb_2'], optimistic: true}], "type": "groups"});
expect(payload).toStrictEqual({"message":[{"ID":1,"devices":[],"friendly_name":"group_1","optimistic":true,"retain":false},{"ID":2,"devices":[],"friendly_name":"group_2","optimistic":true,"retain":false},{"ID":11,"devices":["bulb_2"],"friendly_name":"group_with_tradfri","optimistic":true,"retain":false},{"ID":12,"devices":["TS0601_thermostat"],"friendly_name":"thermostat_group","optimistic":true,"retain":false},{"ID":14,"devices":["power_plug"],"friendly_name":"switch_group","optimistic":true,"retain":false},{"ID":15071,"devices":["bulb_color_2","bulb_2"],"friendly_name":"group_tradfri_remote","optimistic":true,"retain":false}],"type":"groups"});
});
it('Should allow rename devices', async () => {
const bulb_color2 = {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color2", "friendly_name": "bulb_color2", "retain": false};
MQTT.publish.mockClear();
expect(settings.getDevice('bulb_color')).toStrictEqual({"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": false});
MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'bulb_color', new: 'bulb_color2'}));
MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'bulb_color', new: 'bulb_color2'}));
await flushPromises();
expect(settings.getDevice('bulb_color')).toStrictEqual(null);
expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'device_renamed', message: {from: 'bulb_color', to: 'bulb_color2'}}),
stringify({type: 'device_renamed', message: {from: 'bulb_color', to: 'bulb_color2'}}),
{qos: 0, retain: false},
expect.any(Function)
);
MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'bulb_color2', newmalformed: 'bulb_color3'}));
MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'bulb_color2', newmalformed: 'bulb_color3'}));
await flushPromises();
expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2);
@@ -213,7 +214,7 @@ describe('Bridge legacy', () => {
await flushPromises();
expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2);
MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'bulb_color', new: 'bulb_color3'}));
MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'bulb_color', new: 'bulb_color3'}));
await flushPromises();
expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2);
});
@@ -221,12 +222,12 @@ describe('Bridge legacy', () => {
it('Should allow rename groups', async () => {
MQTT.publish.mockClear();
expect(settings.getGroup(1)).toStrictEqual({"ID": 1, devices: [], friendlyName: "group_1", "friendly_name": "group_1", optimistic: true, retain: false});
MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'group_1', new: 'group_1_renamed'}));
MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'group_1', new: 'group_1_renamed'}));
await flushPromises();
expect(settings.getGroup(1)).toStrictEqual({"ID": 1, devices: [], friendlyName: "group_1_renamed", "friendly_name": "group_1_renamed", optimistic: true, retain: false});
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'group_renamed', message: {from: 'group_1', to: 'group_1_renamed'}}),
stringify({type: 'group_renamed', message: {from: 'group_1', to: 'group_1_renamed'}}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -243,7 +244,7 @@ describe('Bridge legacy', () => {
expect(settings.getDevice('0x000b57fffec6a5b2').friendlyName).toStrictEqual('bulb_new_name');
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'device_renamed', message: {from: 'bulb', to: 'bulb_new_name'}}),
stringify({type: 'device_renamed', message: {from: 'bulb', to: 'bulb_new_name'}}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -265,7 +266,7 @@ describe('Bridge legacy', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'group_added', message: 'new_group'}),
stringify({type: 'group_added', message: 'new_group'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -283,7 +284,7 @@ describe('Bridge legacy', () => {
expect(zigbeeHerdsman.createGroup).toHaveBeenCalledWith(3);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'group_added', message: 'new_group'}),
stringify({type: 'group_added', message: 'new_group'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -298,7 +299,7 @@ describe('Bridge legacy', () => {
expect(zigbeeHerdsman.createGroup).toHaveBeenCalledWith(42);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'group_added', message: 'new_group'}),
stringify({type: 'group_added', message: 'new_group'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -321,7 +322,7 @@ describe('Bridge legacy', () => {
expect(group.removeFromNetwork).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'group_removed', message: 'group_1'}),
stringify({type: 'group_removed', message: 'group_1'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -335,7 +336,7 @@ describe('Bridge legacy', () => {
expect(group.removeFromDatabase).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'group_removed', message: 'group_1'}),
stringify({type: 'group_removed', message: 'group_1'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -370,7 +371,7 @@ describe('Bridge legacy', () => {
expect(settings.getDevice('bulb_color')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'device_removed', message: 'bulb_color'}),
stringify({type: 'device_removed', message: 'bulb_color'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -392,7 +393,7 @@ describe('Bridge legacy', () => {
expect(settings.getDevice('bulb_color')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'device_force_removed', message: 'bulb_color'}),
stringify({type: 'device_force_removed', message: 'bulb_color'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -413,7 +414,7 @@ describe('Bridge legacy', () => {
expect(settings.getDevice('bulb_color')).toBeNull();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/log',
JSON.stringify({type: 'device_banned', message: 'bulb_color'}),
stringify({type: 'device_banned', message: 'bulb_color'}),
{qos: 0, retain: false},
expect.any(Function)
);
@@ -456,22 +457,22 @@ describe('Bridge legacy', () => {
});
it('Should allow to touchlink factory reset (OK)', async () => {
zigbeeHerdsman.touchlinkFactoryReset.mockClear();
zigbeeHerdsman.touchlinkFactoryResetFirst.mockClear();
zigbeeHerdsman.touchlinkFactoryReset.mockReturnValueOnce(true);
zigbeeHerdsman.touchlinkFactoryResetFirst.mockReturnValueOnce(true);
MQTT.events.message('zigbee2mqtt/bridge/config/touchlink/factory_reset', '');
await flushPromises();
expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1);
expect(logger.info).toHaveBeenCalledWith('Successfully factory reset device through Touchlink');
});
it('Should allow to touchlink factory reset (FAILS)', async () => {
zigbeeHerdsman.touchlinkFactoryReset.mockClear();
zigbeeHerdsman.touchlinkFactoryResetFirst.mockClear();
zigbeeHerdsman.touchlinkFactoryReset.mockReturnValueOnce(false);
zigbeeHerdsman.touchlinkFactoryResetFirst.mockReturnValueOnce(false);
MQTT.events.message('zigbee2mqtt/bridge/config/touchlink/factory_reset', '');
await flushPromises();
expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith('Failed to factory reset device through Touchlink');
});
});
+9
View File
@@ -24,12 +24,14 @@ describe('Logger', () => {
it('Create log directory', () => {
const logger = require('../lib/util/logger.js');
logger.logOutput();
const dirs = fs.readdirSync(dir.name);
expect(dirs.length).toBe(1);
});
it('Should cleanup', () => {
const logger = require('../lib/util/logger.js');
logger.logOutput();
for (const d of fs.readdirSync(dir.name)) {
rimraf.sync(path.join(dir.name, d));
@@ -46,6 +48,7 @@ describe('Logger', () => {
it('Should not cleanup when there is no timestamp set', () => {
const logger = require('../lib/util/logger.js');
logger.logOutput();
for (let i = 30; i < 40; i++) {
fs.mkdirSync(path.join(dir.name, `log_${i}`));
}
@@ -58,12 +61,14 @@ describe('Logger', () => {
it('Set and get log level', () => {
const logger = require('../lib/util/logger.js');
logger.logOutput();
logger.setLevel('debug');
expect(logger.getLevel()).toBe('debug');
});
it('Logger should be console and file by default', () => {
const logger = require('../lib/util/logger.js');
logger.logOutput();
const pipes = logger._readableState.pipes;
expect(pipes.length).toBe(2);
expect(pipes[0].constructor.name).toBe('Console');
@@ -75,6 +80,7 @@ describe('Logger', () => {
it('Logger can be file only', () => {
settings.set(['advanced', 'log_output'], ['file']);
const logger = require('../lib/util/logger.js');
logger.logOutput();
const pipes = logger._readableState.pipes;
expect(pipes.length).toBe(2);
expect(pipes[0].constructor.name).toBe('Console');
@@ -86,6 +92,7 @@ describe('Logger', () => {
it('Logger can be console only', () => {
settings.set(['advanced', 'log_output'], ['console']);
const logger = require('../lib/util/logger.js');
logger.logOutput();
const pipes = logger._readableState.pipes;
expect(pipes.constructor.name).toBe('Console');
expect(pipes.silent).toBe(false);
@@ -94,6 +101,7 @@ describe('Logger', () => {
it('Logger can be nothing', () => {
settings.set(['advanced', 'log_output'], []);
const logger = require('../lib/util/logger.js');
logger.logOutput();
const pipes = logger._readableState.pipes;
expect(pipes.constructor.name).toBe('Console');
expect(pipes.silent).toBe(true);
@@ -102,6 +110,7 @@ describe('Logger', () => {
it('Should allow to disable log rotation', () => {
settings.set(['advanced', 'log_rotation'], false);
const logger = require('../lib/util/logger.js');
logger.logOutput();
const pipes = logger._readableState.pipes;
expect(pipes[1].constructor.name).toBe('File');
expect(pipes[1].maxFiles).toBeNull();
+35 -60
View File
@@ -1,15 +1,18 @@
const data = require('./stub/data');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
const {coordinator, bulb, bulb_color, WXKG02LM, CC2530_ROUTER, unsupported_router} = zigbeeHerdsman.devices;
const stringify = require('json-stable-stringify-without-jsonify');
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');
@@ -26,7 +29,8 @@ describe('Networkmap', () => {
data.writeDefaultConfiguration();
settings._reRead();
data.writeEmptyState();
settings.set(['experimental', 'new_api'], true);
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();
await controller.start();
mocksClear.forEach((m) => m.mockClear());
@@ -36,22 +40,26 @@ describe('Networkmap', () => {
beforeEach(async () => {
mocksClear.forEach((m) => m.mockClear());
await flushPromises();
const device = zigbeeHerdsman.devices.bulb_color;
device.lastSeen = 1000;
external_converter_device.lastSeen = 1000;
});
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},
@@ -67,7 +75,7 @@ describe('Networkmap', () => {
bulb_color.routingTable = () => {return {table: []}};
CC2530_ROUTER.lqi = () => {return {neighbors: [
{ieeeAddr: WXKG02LM.ieeeAddr, 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: []}};
@@ -84,26 +92,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":[
{"lastSeen": 1000,"ieeeAddr":coordinator.ieeeAddr,"friendlyName":"Coordinator","type":"Coordinator","networkAddress":0,"modelID":null,"failed":[]},
{"lastSeen": 1000,"ieeeAddr":bulb.ieeeAddr,"friendlyName":"bulb","type":"Router","networkAddress":40369,"modelID":"TRADFRI bulb E27 WS opal 980lm","failed":[]},
{"lastSeen": 1000,"ieeeAddr":bulb_color.ieeeAddr,"friendlyName":"bulb_color","type":"Router","networkAddress":40399,"modelID":"LLC020","failed":[]},
{"lastSeen": 1000,"ieeeAddr":WXKG02LM.ieeeAddr,"friendlyName":"button_double_key","type":"EndDevice","networkAddress":6538,"modelID":"lumi.sensor_86sw2.es1"},
{"lastSeen": 1000,"ieeeAddr":unsupported_router.ieeeAddr,"friendlyName":"0x0017880104e45525","type":"Router","networkAddress":6536,"modelID":"notSupportedModelID","manufacturerName": "Boef","failed":['lqi', 'routingTable']},
{"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), 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_xy), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"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);
/**
@@ -149,11 +138,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 undefined (external_converter_device)|9 seconds ago}"];
"0x0017880104e45511" -> "0x00124b00120144ae" [penwidth=1, weight=0, color="#994444", label="92"]
}`;
const expectedLines = expected.split('\n');
@@ -181,6 +172,16 @@ describe('Networkmap', () => {
const expected = `' paste into: https://www.planttext.com/
@startuml
card 0x0017880104e45511 [
0x0017880104e45511
---
0x0017880104e45511 (1114)
---
external undefined (external_converter_device)
---
9 seconds ago
]
card 0x0017880104e45525 [
0x0017880104e45525
---
@@ -216,7 +217,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
]
@@ -241,6 +242,7 @@ describe('Networkmap', () => {
0x000b57fffec6a5b3 --> 0x00124b00120144ae: 120
0x000b57fffec6a5b2 --> 0x00124b00120144ae: 92
0x0017880104e45511 --> 0x00124b00120144ae: 92
0x000b57fffec6a5b3 --> 0x000b57fffec6a5b2: 110
0x0017880104e45559 --> 0x000b57fffec6a5b2: 100
0x0017880104e45521 --> 0x0017880104e45559: 130
@@ -258,41 +260,14 @@ describe('Networkmap', () => {
it('Should output raw networkmap', async () => {
mock();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', JSON.stringify({type: 'raw', routes: true}));
MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'raw', routes: true}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
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":[
{"lastSeen": 1000,"ieeeAddr":coordinator.ieeeAddr,"friendlyName":"Coordinator","type":"Coordinator","networkAddress":0,"modelID":null,"failed":[]},
{"lastSeen": 1000,"ieeeAddr":bulb.ieeeAddr,"friendlyName":"bulb","type":"Router","networkAddress":40369,"modelID":"TRADFRI bulb E27 WS opal 980lm","failed":[]},
{"lastSeen": null,"ieeeAddr":bulb_color.ieeeAddr,"friendlyName":"bulb_color","type":"Router","networkAddress":40399,"modelID":"LLC020","failed":[]},
{"lastSeen": 1000,"ieeeAddr":WXKG02LM.ieeeAddr,"friendlyName":"button_double_key","type":"EndDevice","networkAddress":6538,"modelID":"lumi.sensor_86sw2.es1"},
{"lastSeen": 1000,"ieeeAddr":unsupported_router.ieeeAddr,"friendlyName":"0x0017880104e45525","type":"Router","networkAddress":6536,"modelID":"notSupportedModelID","manufacturerName": "Boef","failed":['lqi', 'routingTable']},
{"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), 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_xy), effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"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]);
actual.data.value = JSON.parse(actual.data.value);
expect(actual).toStrictEqual(expected);
});
@@ -303,7 +278,7 @@ describe('Networkmap', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/networkmap',
JSON.stringify({"data":{},"status":"error","error":"Type 'not_existing' not supported, allowed are: raw,graphviz,plantuml"}),
stringify({"data":{},"status":"error","error":"Type 'not_existing' not supported, allowed are: raw,graphviz,plantuml"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
+47 -22
View File
@@ -6,6 +6,7 @@ const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const flushPromises = () => new Promise(setImmediate);
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const stringify = require('json-stable-stringify-without-jsonify');
describe('OTA update', () => {
let controller;
@@ -18,7 +19,7 @@ describe('OTA update', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings._reRead();
settings.set(['experimental', 'new_api'], true);
settings.set(['advanced', 'ikea_ota_use_test_url'], true);
data.writeEmptyState();
controller = new Controller();
await controller.start();
@@ -26,13 +27,6 @@ describe('OTA update', () => {
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];
@@ -47,7 +41,7 @@ describe('OTA update', () => {
device.save.mockClear();
mapped.ota.updateToLatest.mockImplementationOnce((a, b, onUpdate) => {
onUpdate(0, null);
onUpdate(10, 3600);
onUpdate(10, 3600.2123);
});
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', 'bulb');
@@ -58,15 +52,36 @@ describe('OTA update', () => {
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(`Finished update of 'bulb', from '{"softwareBuildID":1,"dateCode":"20190101"}' to '{"softwareBuildID":2,"dateCode":"20190102"}'`);
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');
expect(device.softwareBuildID).toBe(2);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"update_available":false,"update":{"state":"updating","progress":0}}),
{retain: true, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"update_available":false,"update":{"state":"updating","progress":10,"remaining":3600}}),
{retain: true, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"update_available":false,"update":{"state":"idle"}}),
{retain: true, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
JSON.stringify({"data":{"id": "bulb","from":{"software_build_id":1,"date_code":"20190101"},"to":{"software_build_id":2,"date_code":"20190102"}},"status":"ok"}),
stringify({"data":{"id": "bulb","from":{"software_build_id":1,"date_code":"20190101"},"to":{"software_build_id":2,"date_code":"20190102"}},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/devices',
expect.any(String),
{ retain: true, qos: 0 },
expect.any(Function)
);
});
it('Should handle when OTA update fails', async () => {
@@ -80,11 +95,16 @@ describe('OTA update', () => {
throw new Error('Update failed');
});
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', JSON.stringify({id: "bulb"}));
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', stringify({id: "bulb"}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"update_available":true,"update":{"state":"available"}}),
{retain: true, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
JSON.stringify({"data":{"id": "bulb"},"status":"error","error":"Update of 'bulb' failed (Update failed)"}),
stringify({"data":{"id": "bulb"},"status":"error","error":"Update of 'bulb' failed (Update failed)"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -101,7 +121,7 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
JSON.stringify({"data":{"id": "bulb","updateAvailable":false},"status":"ok"}),
stringify({"data":{"id": "bulb","updateAvailable":false},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
@@ -113,7 +133,7 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
JSON.stringify({"data":{"id": "bulb","updateAvailable":true},"status":"ok"}),
stringify({"data":{"id": "bulb","updateAvailable":true},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -130,7 +150,7 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
JSON.stringify({"data":{"id": "bulb"},"status":"error","error": `Failed to check if update available for 'bulb' (RF singals disturbed because of dogs barking)`}),
stringify({"data":{"id": "bulb"},"status":"error","error": `Failed to check if update available for 'bulb' (RF singals disturbed because of dogs barking)`}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -140,7 +160,7 @@ describe('OTA update', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
JSON.stringify({"data":{"id": "not_existing_deviceooo"},"status":"error","error": `Device 'not_existing_deviceooo' does not exist`}),
stringify({"data":{"id": "not_existing_deviceooo"},"status":"error","error": `Device 'not_existing_deviceooo' does not exist`}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -150,7 +170,7 @@ describe('OTA update', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
JSON.stringify({"data":{"id": "ZNLDP12LM"},"status":"error","error": `Device 'ZNLDP12LM' does not support OTA updates`}),
stringify({"data":{"id": "ZNLDP12LM"},"status":"error","error": `Device 'ZNLDP12LM' does not support OTA updates`}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -173,7 +193,7 @@ describe('OTA update', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
JSON.stringify({"data":{"id": "bulb"},"status":"error","error": `Update or check for update already in progress for 'bulb'`}),
stringify({"data":{"id": "bulb"},"status":"error","error": `Update or check for update already in progress for 'bulb'`}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -190,7 +210,7 @@ describe('OTA update', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
JSON.stringify({"data":{"id":"bulb","from":null,"to":null},"status":"ok"}),
stringify({"data":{"id":"bulb","from":null,"to":null},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -222,7 +242,12 @@ describe('OTA update', () => {
mapped.ota.isUpdateAvailable.mockReturnValueOnce(false);
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(logger.info).not.toHaveBeenCalledWith(`Update available for 'bulb'`)
expect(logger.info).not.toHaveBeenCalledWith(`Update available for 'bulb'`);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"update_available":true,"update":{"state":"available"}}),
{retain: true, qos: 0}, expect.any(Function)
);
});
it('Should respond with NO_IMAGE_AVAILABLE when not supporting OTA', async () => {
@@ -272,7 +297,7 @@ describe('OTA update', () => {
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(`Finished update of 'bulb', from '{"softwareBuildID":1,"dateCode":"20190101"}' to '{"softwareBuildID":2,"dateCode":"20190102"}'`);
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);
expect(device.dateCode).toBe('20190102');
+356 -150
View File
File diff suppressed because it is too large Load Diff
+91 -46
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');
const MQTT = require('./stub/mqtt');
const settings = require('../lib/util/settings');
@@ -19,41 +20,41 @@ describe('Receive', () => {
controller = new Controller();
await controller.start();
mocksClear.forEach((m) => m.mockClear());
delete zigbeeHerdsman.devices.WXKG11LM.linkquality;
});
it('Should handle a zigbee message', async () => {
const device = zigbeeHerdsman.devices.WXKG11LM;
device.linkquality = 10;
const data = {onOff: 1}
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'single', linkquality: 10});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single', click: 'single', linkquality: 10}), {retain: false, qos: 0}, expect.any(Function));
});
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);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'left', linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'left', action: 'single_left'});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
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);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'right', linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({click: 'right', action: 'single_right'});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -65,10 +66,39 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -0.85, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -0.85});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": false});
});
it('Should allow to invert cover', async () => {
const device = zigbeeHerdsman.devices.J1;
// Non-inverted (open = 100, close = 0)
await zigbeeHerdsman.events.message({data: {currentPositionLiftPercentage: 90, currentPositionTiltPercentage: 80}, cluster: 'closuresWindowCovering', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 10, tilt: 20}), {retain: false, qos: 0}, expect.any(Function));
// Inverted
MQTT.publish.mockClear();
settings.set(['devices', device.ieeeAddr, 'invert_cover'], true);
await zigbeeHerdsman.events.message({data: {currentPositionLiftPercentage: 90, currentPositionTiltPercentage: 80}, cluster: 'closuresWindowCovering', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 90, tilt: 80}), {retain: false, qos: 0}, expect.any(Function));
});
it('Should allow to disable the legacy integration', async () => {
const device = zigbeeHerdsman.devices.WXKG11LM;
settings.set(['devices', device.ieeeAddr, 'legacy'], false);
const data = {onOff: 1}
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single'}), {retain: false, qos: 0}, expect.any(Function));
});
it('Should debounce messages', async () => {
jest.useFakeTimers();
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
@@ -89,7 +119,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": false});
});
@@ -115,7 +145,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": true});
});
@@ -135,11 +165,11 @@ describe('Receive', () => {
await flushPromises();
jest.advanceTimersByTime(50);
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, pressure: 2, linkquality: 13});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, pressure: 2});
jest.runAllTimers();
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({temperature: 0.07, pressure: 2, humidity: 0.03, linkquality: 13});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({temperature: 0.07, pressure: 2, humidity: 0.03});
});
it('Shouldnt republish old state', async () => {
@@ -148,12 +178,12 @@ describe('Receive', () => {
const device = zigbeeHerdsman.devices.bulb;
settings.set(['devices', device.ieeeAddr, 'debounce'], 0.1);
await zigbeeHerdsman.events.message({data: {onOff: 0}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10});
await MQTT.events.message('zigbee2mqtt/bulb/set', JSON.stringify({state: 'ON'}));
await MQTT.events.message('zigbee2mqtt/bulb/set', stringify({state: 'ON'}));
await flushPromises();
jest.runAllTimers();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({state: 'ON', linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({state: 'ON'});
});
it('Should handle a zigbee message with 1 precision', async () => {
@@ -165,7 +195,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -0.8, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -0.8});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": false});
});
@@ -178,7 +208,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -1, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -1});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": false});
});
@@ -191,7 +221,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -0.8, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -0.8});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": false});
});
@@ -205,7 +235,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -1, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: -1});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 1, "retain": false});
});
@@ -218,61 +248,61 @@ describe('Receive', () => {
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({"battery":91,"voltage":2985,"temperature":23.5,"humidity":46.08,"pressure":915.5,"linkquality":10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({"battery":91,"voltage":2985,"temperature":23.5,"humidity":46.08,"pressure":915.5});
MQTT.publish.mockClear();
payload = {data: {"16":9354,"20":-1,"measuredValue":915}, cluster: 'msPressureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({"battery":91,"voltage":2985,"temperature":23.5,"humidity":46.08,"pressure":935.4,"linkquality":10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({"battery":91,"voltage":2985,"temperature":23.5,"humidity":46.08,"pressure":935.4});
});
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);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 100, voltage: 3010, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 100, voltage: 3010});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
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);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 35, voltage: 2850, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 35, voltage: 2850});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
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);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 14, voltage: 2650, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 14, voltage: 2650});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
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);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 0, voltage: 2000, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 0, voltage: 2000});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -284,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, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'battery': 100, 'illuminance': 381, "illuminance_lux": 381, 'voltage': 3045});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -298,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};
@@ -311,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};
@@ -324,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};
@@ -336,13 +366,18 @@ describe('Receive', () => {
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should not handle messages forwarded Xiaomi messages', async () => {
it('Should handle forwarded Xiaomi messages', async () => {
const device = zigbeeHerdsman.devices.ZNCZ02LM;
const data = {onOff: 1};
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10, groupID: 599};
const payload = {data: {measuredValue: -85}, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10, groupID: 6539};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/weather_sensor',
stringify({temperature: -0.85}),
{"qos": 1, "retain": false},
expect.any(Function),
)
});
it('Should handle messages from Xiaomi router devices', async () => {
@@ -351,10 +386,20 @@ describe('Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 20};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/power_plug');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'state': 'ON', linkquality: 20});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/power_plug',
stringify({state: 'ON'}),
{ retain: false, qos: 0 },
expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/switch_group',
stringify({'state': 'ON'}),
{ retain: false, qos: 0 },
expect.any(Function)
);
});
it('Should not handle messages from coordinator', async () => {
@@ -373,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);
});
@@ -397,7 +442,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'click': 'brightness_stop', linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'click': 'brightness_stop', action: 'brightness_stop'});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -415,10 +460,10 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'click': 'brightness_stop'});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'click': 'brightness_stop', action: 'brightness_stop'});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toMatchObject({'click': 'brightness_stop'});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toMatchObject({'click': 'brightness_stop', action: 'brightness_stop'});
expect(JSON.parse(MQTT.publish.mock.calls[1][1]).elapsed).toBe(50);
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
Date.now = oldNow;
@@ -444,7 +489,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/SP600_NEW');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({energy: 0.66, power: 49.6, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({energy: 0.66, power: 49.6});
MQTT.publish.mockClear();
const SP600_OLD = zigbeeHerdsman.devices.SP600_OLD;
@@ -452,6 +497,6 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/SP600_OLD');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({energy: 6.648, power: 496, linkquality: 10});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({energy: 6.648, power: 496});
});
});
+58 -2
View File
@@ -24,6 +24,9 @@ describe('Report', () => {
function expectOnOffBrightnessColorReport(endpoint, colorXY) {
const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
const device = endpoint.getDevice();
expect(device.meta.reporting).toBe(1);
expect(endpoint.unbind).toHaveBeenCalledTimes(0);
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', coordinatorEndpoint);
expect(endpoint.bind).toHaveBeenCalledWith('genLevelCtrl', coordinatorEndpoint);
@@ -38,12 +41,32 @@ describe('Report', () => {
}
}
function expectOnOffBrightnessColorReportDisabled(endpoint, colorXY) {
const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1);
const device = endpoint.getDevice();
expect(device.meta.reporting).toBe(undefined);
expect(endpoint.unbind).toHaveBeenCalledTimes(3);
expect(endpoint.bind).toHaveBeenCalledTimes(0);
expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', coordinatorEndpoint);
expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', coordinatorEndpoint);
expect(endpoint.unbind).toHaveBeenCalledWith('lightingColorCtrl', coordinatorEndpoint);
expect(endpoint.configureReporting).toHaveBeenCalledTimes(3);
expect(endpoint.configureReporting).toHaveBeenCalledWith('genOnOff', [{"attribute": "onOff", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 0, "reportableChange": 0}]);
expect(endpoint.configureReporting).toHaveBeenCalledWith('genLevelCtrl', [{"attribute": "currentLevel", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}]);
if (colorXY) {
expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentX", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentY", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}]);
} else {
expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 3, "reportableChange": 1}]);
}
}
mockClear = (device) => {
for (const endpoint of device.endpoints) {
endpoint.read.mockClear();
endpoint.write.mockClear();
endpoint.configureReporting.mockClear();
endpoint.bind.mockClear();
endpoint.unbind.mockClear();
}
}
@@ -52,6 +75,11 @@ describe('Report', () => {
settings._reRead();
data.writeEmptyState();
settings.set(['advanced', 'report'], true);
for (const device of Object.values(zigbeeHerdsman.devices)) {
mockClear(device);
delete device.meta.reporting;
}
controller = new Controller();
await controller.start();
mocksClear.forEach((m) => m.mockClear());
@@ -64,6 +92,31 @@ describe('Report', () => {
expectOnOffBrightnessColorReport(endpoint, true);
});
it('Should not configure reporting on startup when disabled', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
mockClear(device);
delete device.meta.report;
settings.set(['advanced', 'report'], false);
controller = new Controller();
await controller.start();
await flushPromises();
expect(device.meta.reporting).toBe(undefined);
expect(endpoint.bind).toHaveBeenCalledTimes(0);
});
it('Should disable reporting on startup when enabled earlier', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
device.meta.reporting = 1;
const endpoint = device.getEndpoint(1);
settings.set(['advanced', 'report'], false);
mockClear(device);
controller = new Controller();
await controller.start();
await flushPromises();
expectOnOffBrightnessColorReportDisabled(endpoint, true);
});
it('Should configure reporting when receicing message from device which has not been setup yet', async () => {
const device = zigbeeHerdsman.devices.bulb;
const endpoint = device.getEndpoint(1);
@@ -142,7 +195,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};
@@ -214,7 +267,7 @@ describe('Report', () => {
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);
expect(zigbeeHerdsman.devices.bulb_2.getEndpoint(1).read).toHaveBeenCalledTimes(0);
});
it('Should not configure reporting for the ZNLDP12LM closuresWindowCovering as it is ignored', async () => {
@@ -234,6 +287,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();
@@ -255,5 +310,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;
});
});
+157 -37
View File
@@ -6,9 +6,17 @@ 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');
const objectAssignDeep = require(`object-assign-deep`);
const minimalConfig = {
permit_join: true,
homeassistant: true,
mqtt: {base_topic: 'zigbee2mqtt', server: 'localhost'},
};
describe('Settings', () => {
const write = (file, json, reread=true) => {
@@ -21,11 +29,19 @@ describe('Settings', () => {
const remove = (file) => {
if (fs.existsSync(file)) fs.unlinkSync(file);
}
const clearEnvironmentVariables = () => {
Object.keys(process.env).forEach((key) => {
if(key.indexOf('ZIGBEE2MQTT_CONFIG_') >= 0) {
delete process.env[key];
}
});
}
beforeEach(() => {
remove(configurationFile);
remove(devicesFile);
remove(groupsFile);
clearEnvironmentVariables();
});
it('Should return default settings', () => {
@@ -47,6 +63,29 @@ describe('Settings', () => {
expect(s).toStrictEqual(expected);
});
it('Should apply environment variables', () => {
process.env['ZIGBEE2MQTT_CONFIG_SERIAL_DISABLE_LED'] = 'true';
process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_SOFT_RESET_TIMEOUT'] = 1;
process.env['ZIGBEE2MQTT_CONFIG_EXPERIMENTAL_OUTPUT'] = 'csvtest';
process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_AVAILABILITY_BLOCKLIST'] = '["0x43597f0dac781b1e", "x223b0aef2ae8d1b0"]';
process.env['ZIGBEE2MQTT_CONFIG_MAP_OPTIONS_GRAPHVIZ_COLORS_FILL'] = '{"enddevice": "#ff0000", "coordinator": "#00ff00", "router": "#0000ff"}';
process.env['ZIGBEE2MQTT_CONFIG_MQTT_BASE_TOPIC'] = 'testtopic';
write(configurationFile, {});
const s = settings.get();
const expected = objectAssignDeep.noMutate({}, settings._getDefaults());
expected.devices = {};
expected.groups = {};
expected.serial.disable_led = true;
expected.advanced.soft_reset_timeout = 1;
expected.experimental.output = 'csvtest';
expected.advanced.availability_blocklist = ['0x43597f0dac781b1e', 'x223b0aef2ae8d1b0'];
expected.map_options.graphviz.colors.fill = {enddevice: '#ff0000', coordinator: '#00ff00', router: '#0000ff'};
expected.mqtt.base_topic = 'testtopic';
expect(s).toStrictEqual(expected);
});
it('Should add devices', () => {
write(configurationFile, {});
settings.addDevice('0x12345678');
@@ -115,6 +154,7 @@ describe('Settings', () => {
const expected = {
include_device_information: false,
force_disable_retain: false,
password: "mysecretpassword",
server: "my.mqtt.server",
user: "mysecretusername",
@@ -158,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',
@@ -190,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',
@@ -505,40 +612,63 @@ describe('Settings', () => {
it('Should not allow any string values for network_key', () => {
write(configurationFile, {
...minimalConfig,
advanced: {network_key: 'NOT_GENERATE'},
});
settings._reRead();
expect(() => {
settings.validate();
}).toThrowError(`advanced.network_key: should be array or 'GENERATE' (is 'NOT_GENERATE')`);
const error = `advanced.network_key: should be array or 'GENERATE' (is 'NOT_GENERATE')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Should allow retention configuration with MQTT v5', () => {
write(configurationFile, {
...minimalConfig,
mqtt: {base_topic: 'zigbee2mqtt', server: 'localhost', version: 5},
devices: {'0x0017880104e45519': {friendly_name: 'tain', retention: 900}},
});
settings._reRead();
expect(settings.validate()).toEqual([]);
});
it('Should not allow retention configuration without MQTT v5', () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'tain', retention: 900}},
});
settings._reRead();
expect(() => {
settings.validate();
}).toThrowError('MQTT retention requires protocol version 5');
const error = 'MQTT retention requires protocol version 5';
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Should not allow non-existing entities in availability_blocklist', () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'tain'}},
advanced: {availability_blocklist: ['0x0017880104e45519', 'non_existing']},
});
settings._reRead();
expect(() => {
settings.validate();
}).toThrowError(`Non-existing entity 'non_existing' specified in 'availability_blocklist'`);
const error = `Non-existing entity 'non_existing' specified in 'availability_blocklist'`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Validate should if settings does not conform to scheme', () => {
write(configurationFile, {
...minimalConfig,
advanced: null,
});
settings._reRead();
const error = `advanced should be object`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Should ban devices', () => {
@@ -563,74 +693,64 @@ describe('Settings', () => {
\t wrong
`)
let error;
try {
settings._reRead();
} catch (e) {
error = e;
}
expect(error.message).toContain(`Your YAML file '${configurationFile}' is invalid`);
settings._clear();
const error = `Your YAML file: '${configurationFile}' is invalid (use https://jsonformatter.org/yaml-validator to find and fix the issue)`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Should throw error when yaml file does not exist', () => {
let error;
try {
settings._reRead();
} catch (e) {
error = e;
}
expect(error.message).toContain(`no such file or directory, open`);
settings._clear();
const error = `ENOENT: no such file or directory, open '${configurationFile}'`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration shouldnt be valid when duplicate friendly_name are used', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'myname', retain: false}},
groups: {'1': {friendly_name: 'myname', retain: false}},
});
settings._reRead();
expect(() => {
settings.validate();
}).toThrowError(`Duplicate friendly_name 'myname' found`);
const error = `Duplicate friendly_name 'myname' found`;
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();
expect(() => {
settings.validate();
}).toThrowError(`Friendly name cannot end with a "/DIGIT" ('myname/123')`);
const error = `Friendly name cannot end with a "/DIGIT" ('myname/123')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration shouldnt be valid when friendly_name contains a MQTT wildcard', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'myname#', retain: false}},
});
settings._reRead();
expect(() => {
settings.validate();
}).toThrowError(`MQTT wildcard (+ and #) not allowed in friendly_name ('myname#')`);
const error = `MQTT wildcard (+ and #) not allowed in friendly_name ('myname#')`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration shouldnt be valid when friendly_name is a postfix', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'left', retain: false}},
});
settings._reRead();
expect(() => {
settings.validate();
}).toThrowError(`Following friendly_name are not allowed: '${utils.getEndpointNames()}'`);
const error = `Following friendly_name are not allowed: '${utils.getEndpointNames()}'`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration shouldnt be valid when duplicate friendly_name are used', async () => {
+24 -4
View File
@@ -2,6 +2,7 @@ const tmp = require('tmp');
const yaml = require('../../lib/util/yaml');
const path = require('path');
const fs = require('fs');
const stringify = require('json-stable-stringify-without-jsonify');
const mockDir = tmp.dirSync().name;
const mockDirStorage = tmp.dirSync().name;
@@ -109,7 +110,7 @@ function writeDefaultConfiguration() {
},
'0x0017880104e45551': {
retain: false,
friendly_name: 'smart_vent'
friendly_name: 'smart vent'
},
'0x0017880104e45552': {
retain: false,
@@ -154,7 +155,16 @@ function writeDefaultConfiguration() {
},
'0x0017880104e44559': {
friendly_name: '3157100_thermostat',
}
},
'0x0017880104a44559': {
friendly_name: 'J1_cover',
},
'0x0017882104a44559': {
friendly_name: 'TS0601_thermostat',
},
'0x0017882194e45543': {
friendly_name: 'QS-Zigbee-D02-TRIAC-2C-LN',
},
},
groups: {
'1': {
@@ -175,6 +185,16 @@ function writeDefaultConfiguration() {
retain: false,
devices: ['bulb_2']
},
'12': {
friendly_name: 'thermostat_group',
retain: false,
devices: ['TS0601_thermostat'],
},
'14': {
friendly_name: 'switch_group',
retain: false,
devices: ['power_plug'],
}
},
external_converters: [],
};
@@ -183,7 +203,7 @@ function writeDefaultConfiguration() {
}
function writeEmptyState() {
fs.writeFileSync(stateFile, JSON.stringify({}));
fs.writeFileSync(stateFile, stringify({}));
}
function removeState() {
@@ -209,7 +229,7 @@ function writeDefaultState() {
},
}
fs.writeFileSync(path.join(mockDir, 'state.json'), JSON.stringify(state));
fs.writeFileSync(path.join(mockDir, 'state.json'), stringify(state));
}
jest.mock('../../lib/util/data', () => ({
+18 -5
View File
@@ -1,14 +1,27 @@
let level = 'info';
const transports = [];
let transportsEnabled = false;
const callTransports = (level, message) => {
if (transportsEnabled) {
for (const transport of transports) {
transport.log({level, message}, () => {});
}
}
}
const mock = {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
trace: jest.fn(),
info: jest.fn().mockImplementation((msg) => callTransports('info', msg)),
warn: jest.fn().mockImplementation((msg) => callTransports('warn', msg)),
error: jest.fn().mockImplementation((msg) => callTransports('error', msg)),
debug: jest.fn().mockImplementation((msg) => callTransports('debug', msg)),
cleanup: jest.fn(),
logOutput: jest.fn(),
add: (transport) => transports.push(transport),
setLevel: (newLevel) => {level = newLevel},
getLevel: () => level,
setTransportsEnabled: (value) => {transportsEnabled = value},
};
jest.mock('../../lib/util/logger', () => (mock));
+2 -1
View File
@@ -4,6 +4,7 @@ const mock = {
publish: jest.fn().mockImplementation((topic, payload, options, cb) => cb()),
end: jest.fn(),
subscribe: jest.fn(),
reconnecting: false,
on: (type, handler) => {
if (type === 'connect') {
handler();
@@ -20,5 +21,5 @@ jest.mock('mqtt', () => {
});
module.exports = {
events, ...mock, connect: mockConnect,
events, ...mock, connect: mockConnect, mock,
};
+45 -13
View File
@@ -1,6 +1,11 @@
const events = {};
const assert = require('assert');
function getKeyByValue(object, value, fallback) {
const key = Object.keys(object).find((k) => object[k] === value);
return key != null ? key : fallback;
}
class Group {
constructor(groupID, members) {
this.groupID = groupID;
@@ -24,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;
@@ -36,8 +41,19 @@ class Endpoint {
this.write = jest.fn();
this.bind = jest.fn();
this.unbind = jest.fn();
this.save = jest.fn();
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);
this.getOutputClusters = () => outputClusters.map((c) => {
return {ID: c, name: getKeyByValue(clusters, c)};
}).filter((c) => c.name);
this.supportsInputCluster = (cluster) => {
assert(clusters[cluster] !== undefined, `Undefined '${cluster}'`);
return this.inputClusters.includes(clusters[cluster]);
@@ -57,12 +73,13 @@ class Endpoint {
return Object.values(devices).find(d => d.ieeeAddr === deviceIeeeAddress);
}
this.removeFromGroup = (group) => {
this.removeFromGroup = jest.fn();
this.removeFromGroup.mockImplementation((group) => {
const index = group.members.indexOf(this);
if (index != -1) {
group.members.splice(index, 1);
}
}
});
this.removeFromAllGroups = () => {
Object.values(groups).forEach((g) => this.removeFromGroup(g))
@@ -75,7 +92,7 @@ class Endpoint {
}
class Device {
constructor(type, ieeeAddr, networkAddress, manufacturerID, endpoints, interviewCompleted, powerSource = null, modelID = null, interviewing=false, manufacturerName, dateCode= null) {
constructor(type, ieeeAddr, networkAddress, manufacturerID, endpoints, interviewCompleted, powerSource = null, modelID = null, interviewing=false, manufacturerName, dateCode= null, softwareBuildID=null) {
this.type = type;
this.ieeeAddr = ieeeAddr;
this.dateCode = dateCode;
@@ -83,6 +100,7 @@ class Device {
this.manufacturerID = manufacturerID;
this.endpoints = endpoints;
this.powerSource = powerSource;
this.softwareBuildID = softwareBuildID;
this.interviewCompleted = interviewCompleted;
this.modelID = modelID;
this.interviewing = interviewing;
@@ -103,31 +121,32 @@ class Device {
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");
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_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', [], {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 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"),
'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]}]), 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: 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", 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"),
'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], [])], true, "Mains (single phase)", "lumi.plug"),
'ZNCZ02LM': new Device('Router', '0x0017880104e45524', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45524')], true, "Mains (single phase)", "lumi.plug"),
'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'),
'GLEDOPTO1112': new Device('Router', '0x0017880104e45543', 6540, 4151, [new Endpoint(11, [0], [], '0x0017880104e45543'), new Endpoint(13, [0], [], '0x0017880104e45543')], true, "Mains (single phase)", 'GL-C-008'),
'GLEDOPTO111213': new Device('Router', '0x0017880104e45544', 6540,4151, [new Endpoint(11, [0], []), new Endpoint(13, [0], []), new Endpoint(12, [0], [])], true, "Mains (single phase)", 'GL-C-008'),
'HGZB04D': new Device('Router', '0x0017880104e45545', 6540,4151, [new Endpoint(1, [0], [])], true, "Mains (single phase)", 'FB56+ZSC05HG1.0'),
'HGZB04D': new Device('Router', '0x0017880104e45545', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45545')], true, "Mains (single phase)", 'FB56+ZSC05HG1.0'),
'ZNCLDJ11LM': new Device('Router', '0x0017880104e45547', 6540,4151, [new Endpoint(1, [0], []), new Endpoint(2, [0], [])], true, "Mains (single phase)", 'lumi.curtain'),
'HAMPTON99432': new Device('Router', '0x0017880104e45548', 6540,4151, [new Endpoint(1, [0], []), new Endpoint(2, [0], [])], true, "Mains (single phase)", 'HDC52EastwindFan'),
'HS2WD': new Device('Router', '0x0017880104e45549', 6540,4151, [new Endpoint(1, [0], [])], true, "Mains (single phase)", 'WarningDevice'),
@@ -148,18 +167,28 @@ const devices = {
'GL-S-007ZS': new Device('Router', '0x0017880104e45526', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45526')], true, "Mains (single phase)", 'GL-S-007ZS'),
'U202DST600ZB': new Device('Router', '0x0017880104e43559', 6540,4151, [new Endpoint(10, [0, 6], [], '0x0017880104e43559'), new Endpoint(11, [0, 6], [], '0x0017880104e43559')], true, "Mains (single phase)", 'U202DST600ZB'),
'3157100': new Device('Router', '0x0017880104e44559', 6542,4151, [new Endpoint(1, [], [], '0x0017880104e44559')], true, "Mains (single phase)", '3157100'),
'J1': new Device('Router', '0x0017880104a44559', 6543,4151, [new Endpoint(1, [], [], '0x0017880104a44559')], true, "Mains (single phase)", 'J1 (5502)'),
'TS0601_thermostat': TS0601_thermostat,
'external_converter_device': new Device('EndDevice', '0x0017880104e45511', 1114, 'external', [], false, null, 'external_converter_device' ),
'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 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]])
'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]]),
'default_bind_group': new Group(901, []),
}
const mock = {
setTransmitPower: jest.fn(),
touchlinkFactoryReset: jest.fn(),
touchlinkFactoryResetFirst: jest.fn(),
touchlinkScan: jest.fn(),
touchlinkIdentify: jest.fn(),
start: jest.fn(),
permitJoin: jest.fn(),
getCoordinatorVersion: jest.fn().mockReturnValue({type: 'z-Stack', meta: {version: 1, revision: 20190425}}),
@@ -178,6 +207,9 @@ const mock = {
getDeviceByIeeeAddr: jest.fn().mockImplementation((ieeeAddr) => {
return Object.values(devices).filter((d) => returnDevices.length === 0 || returnDevices.includes(d.ieeeAddr)).find((d) => d.ieeeAddr === ieeeAddr);
}),
getDeviceByNetworkAddress: jest.fn().mockImplementation((networkAddress) => {
return Object.values(devices).filter((d) => returnDevices.length === 0 || returnDevices.includes(d.ieeeAddr)).find((d) => d.networkAddress === networkAddress);
}),
getGroups: jest.fn().mockImplementation((query) => {
return Object.values(groups);
}),
+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;
+9 -2
View File
@@ -1,6 +1,13 @@
#!/bin/bash -e
echo "Stopping zigbee2mqtt..."
if [ -d data-backup ]; then
echo "ERROR: Backup directory exists. May be previous restoring was failed?"
echo "1. Save 'data-backup' and 'data' dirs to safe location to make possibility to restore config later."
echo "2. Manually delete 'data-backup' dir and try again."
exit 1
fi
echo "Stopping Zigbee2MQTT..."
sudo systemctl stop zigbee2mqtt
echo "Creating backup of configuration..."
@@ -17,7 +24,7 @@ echo "Restore configuration..."
cp -R data-backup/* data
rm -rf data-backup
echo "Starting zigbee2mqtt..."
echo "Starting Zigbee2MQTT..."
sudo systemctl start zigbee2mqtt
echo "Done!"