Compare commits

..
203 Commits
Author SHA1 Message Date
Koen Kanters 6f1460e47b 1.25.0 2022-04-01 08:26:35 +02:00
Koen Kanters 91239291ec Undev 2022-04-01 08:26:33 +02:00
Koen Kanters beccc18ba7 Merge branch 'master' into dev 2022-03-31 22:02:47 +02:00
github-actions[bot]andKoenkk 2901df37fb Update zigbee-herdsman-converters to 14.0.467 (#12008)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-31 19:28:24 +00:00
github-actions[bot]andnurikk 1c1ca6faea Update zigbee2mqtt-frontend to 0.6.80 (#12001)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-03-31 07:49:42 +00:00
github-actions[bot]andKoenkk 85308daf5f Update zigbee-herdsman-converters to 14.0.466 (#11991)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-30 15:31:23 +00:00
github-actions[bot]andKoenkk 4631f6fc22 Update zigbee-herdsman-converters to 14.0.465 (#11984)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-29 19:05:17 +00:00
github-actions[bot]andKoenkk 95c698758d Update zigbee-herdsman-converters to 14.0.464 (#11974)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-28 18:32:35 +00:00
github-actions[bot]andKoenkk 95c48309a0 Update dependencies (#11962)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-27 09:55:27 +02:00
github-actions[bot]andKoenkk 2e42f50d6a Update zigbee-herdsman-converters to 14.0.463 (#11961)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-26 22:05:47 +00:00
github-actions[bot]andKoenkk 62a7b5115c Update zigbee-herdsman-converters to 14.0.462 (#11959)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-26 14:01:19 +00:00
github-actions[bot]andKoenkk b805dadceb Update zigbee-herdsman-converters to 14.0.461 (#11947)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-24 18:18:15 +00:00
Artem Draft f95c0bb048 Handle cover motor_state in HA (#11926)
* Handle cover motor_state in HA

* lint
2022-03-23 17:19:06 +01:00
github-actions[bot]andKoenkk 0202399ff6 Update zigbee-herdsman-converters to 14.0.460 (#11927)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-22 18:44:49 +00:00
github-actions[bot]andKoenkk e4d4f04a05 Update zigbee-herdsman-converters to 14.0.459 (#11920)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-21 16:39:48 +00:00
github-actions[bot]andKoenkk efe768d24a Update zigbee-herdsman-converters to 14.0.458 (#11912)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-20 15:51:12 +00:00
github-actions[bot]andKoenkk 0a868b451f Update dependencies (#11906)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-20 16:12:41 +01:00
Koen Kanters a73caa3e66 Update issue template 2022-03-19 16:44:09 +01:00
Koen Kanters 02bc325fc2 Fix configure failing when rejoining device. #10973 2022-03-19 16:41:22 +01:00
github-actions[bot]andKoenkk 847e7f8652 Update zigbee-herdsman-converters to 14.0.457 (#11901)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-19 14:22:43 +00:00
github-actions[bot]andKoenkk 0cd02c7495 Update zigbee-herdsman-converters to 14.0.456 (#11898)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-18 16:11:22 +00:00
github-actions[bot]andKoenkk cae83c90b9 Update zigbee-herdsman-converters to 14.0.455 (#11888)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-17 18:33:40 +00:00
Koen Kanters 2524e030fa Fix device reconnect resulting in brightness 1 when device is off. #11864 2022-03-17 19:21:35 +01:00
github-actions[bot]andKoenkk 652d4018dd Update zigbee-herdsman-converters to 14.0.454 (#11877)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-16 19:28:58 +00:00
github-actions[bot]andKoenkk 5b730575e4 Update zigbee-herdsman-converters to 14.0.452 (#11857)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-15 16:00:14 +00:00
github-actions[bot]andKoenkk 83f8250358 Update zigbee-herdsman-converters to 14.0.451 (#11845)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-14 17:12:06 +00:00
Koen Kanters 8bf06901c2 Availability for groups (#11626)
* Availability for groups. https://github.com/Koenkk/zigbee2mqtt/issues/9176

* Updates

* Fix
2022-03-14 15:51:02 +01:00
github-actions[bot]andKoenkk 4a0cd427de Update zigbee-herdsman-converters to 14.0.450 (#11839)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-13 18:20:54 +00:00
github-actions[bot]andKoenkk 23aacc4c00 Update dependencies (#11824)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-13 18:51:28 +01:00
github-actions[bot]andnurikk e7aef4c208 Update zigbee2mqtt-frontend to 0.6.79 (#11833)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-03-13 13:27:05 +00:00
github-actions[bot]andKoenkk 3cf3366070 Update zigbee-herdsman-converters to 14.0.449 (#11814)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-12 09:21:24 +00:00
Artem Draft 8f1787ed4a Expose cover running state to Home Assistant (#11789)
* handle cover running state in HA

* add running if the expose

* find running in definitionExposes
2022-03-12 10:14:25 +01:00
Koen Kanters b515c3213f Node 16 (#11804)
* Node 16

* More Node 16
2022-03-11 20:39:25 +01:00
github-actions[bot]andKoenkk 1aea3194bb Update zigbee-herdsman-converters to 14.0.448 (#11801)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-11 16:18:36 +00:00
github-actions[bot]andKoenkk 8f98a2f6d0 Update zigbee-herdsman-converters to 14.0.447 (#11787)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-10 17:27:39 +00:00
github-actions[bot]andKoenkk 52d151e70f Update zigbee-herdsman-converters to 14.0.446 (#11764)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-08 18:34:43 +00:00
github-actions[bot]andKoenkk 42f229b19e Update zigbee-herdsman-converters to 14.0.445 (#11753)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-07 17:52:33 +00:00
Koen Kanters e5d6bd1d68 Don't cache no_occupancy_since. https://github.com/Koenkk/zigbee2mqtt/issues/11183 2022-03-07 18:38:36 +01:00
github-actions[bot]andKoenkk c49f546256 Update zigbee-herdsman-converters to 14.0.444 (#11737)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-06 20:41:29 +00:00
github-actions[bot]andKoenkk 141f9f6d04 Update dependencies (#11726)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-06 20:13:20 +01:00
github-actions[bot]andnurikk a73a5b2cd4 Update zigbee2mqtt-frontend to 0.6.78 (#11733)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-03-06 08:39:44 +00:00
github-actions[bot]andKoenkk f7c6207d4f Update zigbee-herdsman-converters to 14.0.443 (#11716)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-05 15:36:48 +00:00
github-actions[bot]andKoenkk 9592147972 Update zigbee-herdsman-converters to 14.0.442 (#11696)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-03 17:24:06 +00:00
github-actions[bot]andnurikk a1cc8049b1 Update zigbee2mqtt-frontend to 0.6.77 (#11691)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-03-03 14:57:17 +00:00
st4ro 625236721f new device class GARAGE for garage door opener (#11680) 2022-03-02 21:31:15 +01:00
Andrew Berry 364a6dd2aa Only colorize output if logging to a tty (#11645) 2022-03-02 20:07:22 +01:00
github-actions[bot]andKoenkk 3535b2c6c2 Update zigbee-herdsman-converters to 14.0.439 (#11662)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-01 18:44:56 +00:00
Koen Kanters 91d597e53d Dev 2022-03-01 17:03:45 +01:00
Koen Kanters 7a2ddf24d3 1.24.0 2022-03-01 17:02:51 +01:00
Koen Kanters d123ea3e61 Undev 2022-03-01 17:02:10 +01:00
github-actions[bot]andKoenkk eda6e7ca37 Update zigbee-herdsman-converters to 14.0.438 (#11660)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-01 16:01:10 +00:00
Koen Kanters b50e40c9ca Update generateNewSupportedDevices.js 2022-03-01 16:50:26 +01:00
github-actions[bot]andnurikk 1eb3287029 Update zigbee2mqtt-frontend to 0.6.76 (#11656)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-03-01 03:45:41 +00:00
github-actions[bot]andKoenkk bc8cd8df94 Update zigbee-herdsman-converters to 14.0.437 (#11655)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-28 17:07:31 +00:00
github-actions[bot]andKoenkk 8751eca171 Update zigbee-herdsman-converters to 14.0.436 (#11634)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-27 12:57:56 +00:00
github-actions[bot]andKoenkk 107b2726b2 Update dependencies (#11630)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-27 13:38:54 +01:00
github-actions[bot]andKoenkk 987205c004 Update zigbee-herdsman-converters to 14.0.435 (#11624)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-26 11:55:57 +00:00
github-actions[bot]andKoenkk 4f7ee6898a Update zigbee-herdsman-converters to 14.0.434 (#11620)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-25 19:12:14 +00:00
github-actions[bot]andKoenkk 67ffcebe69 Update zigbee-herdsman-converters to 14.0.433 (#11614)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-25 16:16:56 +00:00
github-actions[bot]andKoenkk afe94a7cba Update zigbee-herdsman-converters to 14.0.432 (#11596)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-24 16:10:04 +00:00
a5db6ef0c4 Update zigbee-herdsman-converters to 14.0.431 (#11585)
* Update zigbee-herdsman-converters to 14.0.431

* Fix tests

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-02-23 18:59:33 +00:00
github-actions[bot]andKoenkk 5dc369c5a4 Update zigbee-herdsman to 0.14.20 (#11584)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-23 18:23:39 +00:00
github-actions[bot]andKoenkk 65bd5423e8 Update zigbee-herdsman to 0.14.19 (#11583)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-23 18:17:16 +00:00
github-actions[bot]andKoenkk 9c33c90650 Update zigbee-herdsman-converters to 14.0.430 (#11569)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-22 15:54:08 +00:00
github-actions[bot]andKoenkk caafc64fbf Update zigbee-herdsman-converters to 14.0.429 (#11551)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-21 20:56:31 +00:00
github-actions[bot]andKoenkk 9e35f22ac4 Update zigbee-herdsman-converters to 14.0.427 (#11550)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-21 16:51:07 +00:00
github-actions[bot]andnurikk 23b7898a43 Update zigbee2mqtt-frontend to 0.6.75 (#11549)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-02-21 16:45:42 +00:00
github-actions[bot]andKoenkk 0fdbb9153d Update zigbee-herdsman to 0.14.18 (#11546)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-21 16:31:22 +00:00
github-actions[bot]andnurikk a345181812 Update zigbee2mqtt-frontend to 0.6.74 (#11534)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-02-21 01:16:43 +00:00
github-actions[bot]andKoenkk 7f4f48309e Update zigbee-herdsman to 0.14.17 (#11513)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-20 14:28:17 +00:00
github-actions[bot]andKoenkk f1eb605bef Update zigbee-herdsman-converters to 14.0.426 (#11512)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-20 14:01:14 +00:00
TobiasandKoen Kanters 9cfc38223e Add device and groups descriptions (#11493)
* Add device and groups descriptions

* Improvements

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-02-20 14:45:53 +01:00
github-actions[bot]andKoenkk abdb2b20c2 Update dependencies (#11499)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-20 14:22:20 +01:00
github-actions[bot]andnurikk 6d521147fa Update zigbee2mqtt-frontend to 0.6.73 (#11506)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-02-20 12:02:22 +00:00
github-actions[bot]andnurikk df97c79b78 Update zigbee2mqtt-frontend to 0.6.72 (#11504)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-02-20 05:43:33 +00:00
github-actions[bot]andKoenkk f6f507cda0 Update zigbee-herdsman-converters to 14.0.425 (#11486)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-19 09:04:10 +00:00
github-actions[bot]andKoenkk ba1843ff74 Update zigbee-herdsman-converters to 14.0.424 (#11478)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-18 15:33:45 +00:00
github-actions[bot]andKoenkk aace02ccda Update zigbee-herdsman-converters to 14.0.423 (#11457)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-16 20:51:36 +00:00
Koen Kanters 7ec6a2b483 Allow to specify options device/configure_reporting. https://github.com/Koenkk/zigbee2mqtt/issues/11442 2022-02-16 21:32:59 +01:00
84043702c1 Update zigbee-herdsman-converters to 14.0.422 (#11441)
* Update zigbee-herdsman-converters to 14.0.422

* fix tests

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-02-15 19:12:49 +00:00
github-actions[bot]andKoenkk 825308df20 Update zigbee-herdsman to 0.14.16 (#11440)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-15 19:03:43 +00:00
github-actions[bot]andKoenkk e0694a444e Update zigbee-herdsman to 0.14.15 (#11438)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-15 18:48:02 +00:00
github-actions[bot]andKoenkk f284a5d2d5 Update zigbee-herdsman-converters to 14.0.421 (#11402)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-13 14:46:03 +00:00
github-actions[bot]andKoenkk 5849a90030 Update dependencies (#11396)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-13 15:03:23 +01:00
github-actions[bot]andKoenkk b518e3e200 Update zigbee-herdsman-converters to 14.0.420 (#11389)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-12 16:09:56 +00:00
github-actions[bot]andKoenkk bccd7b390c Update zigbee-herdsman-converters to 14.0.419 (#11380)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-12 09:52:42 +00:00
4933b9080e Update zigbee-herdsman-converters to 14.0.418 (#11366)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-02-11 18:25:00 +00:00
github-actions[bot]andKoenkk cd9fd2d5c2 Update zigbee-herdsman to 0.14.14 (#11365)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-11 18:00:39 +00:00
Koen Kanters 9602574cd2 Add winston-transport dependency. https://github.com/Koenkk/zigbee2mqtt/issues/11356 2022-02-11 18:52:00 +01:00
github-actions[bot]andKoenkk 711e2d9cfc Update zigbee-herdsman-converters to 14.0.417 (#11346)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-10 18:46:56 +00:00
github-actions[bot]andKoenkk d8ecc6f26e Update zigbee-herdsman-converters to 14.0.416 (#11331)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-09 18:26:33 +00:00
github-actions[bot]andKoenkk 7fe803a28c Update zigbee-herdsman-converters to 14.0.415 (#11310)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-08 19:03:03 +00:00
6394d0f42e Update zigbee-herdsman-converters to 14.0.414 (#11284)
* Update zigbee-herdsman-converters to 14.0.414

* Fix tests

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-02-07 17:11:04 +00:00
github-actions[bot]andKoenkk 52607d03cf Update zigbee-herdsman to 0.14.13 (#11275)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-07 09:13:59 +00:00
github-actions[bot]andKoenkk 5dd8873fcd Update zigbee-herdsman-converters to 14.0.413 (#11266)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-06 19:52:50 +00:00
github-actions[bot]andKoenkk 02e18b9d5b Update zigbee-herdsman-converters to 14.0.412 (#11258)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-06 13:26:16 +00:00
github-actions[bot]andKoenkk 42bb73e263 Update dependencies (#11254)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-06 14:11:24 +01:00
Ryan Kim(True-World) 39bf36b0b5 delete device class of alram(enum) (#11236) 2022-02-06 13:50:55 +01:00
github-actions[bot]andKoenkk b5e8566f58 Update zigbee-herdsman-converters to 14.0.411 (#11233)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-05 09:39:34 +00:00
Ryan Kim(True-World)andKoen Kanters 21567ab810 Add more Home Assistant device classes (#11225)
* add some device class

device class.

* fix lint

* Update homeassistant.ts

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-02-05 09:20:35 +00:00
ValentinandKoen Kanters f9c4b4e926 HA Discover when user change option (#11210)
* HA Discover when user change option

* Fix unit tests

* Update homeassistant.ts

* Update homeassistant.ts

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-02-05 10:12:13 +01:00
github-actions[bot]andKoenkk 8357da270d Update zigbee-herdsman to 0.14.12 (#11228)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-04 17:31:55 +00:00
github-actions[bot]andKoenkk 4def85b3ca Update zigbee-herdsman-converters to 14.0.410 (#11227)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-04 17:24:23 +00:00
Koen Kanters 0e11ae07d9 Recompute device definition when device changes modelID. https://github.com/Koenkk/zigbee-herdsman-converters/issues/3016 2022-02-04 18:02:47 +01:00
github-actions[bot]andKoenkk 9fb1eb40d3 Update zigbee-herdsman-converters to 14.0.409 (#11209)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-03 16:17:09 +00:00
github-actions[bot]andKoenkk ddcb8e12d5 Update zigbee-herdsman-converters to 14.0.408 (#11196)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-02 19:01:18 +00:00
github-actions[bot]andKoenkk 3470681ea7 Update zigbee-herdsman-converters to 14.0.407 (#11195)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-02 18:52:28 +00:00
Koen Kanters 41ece365aa Allow to configure availablility payloads to be a JSON object. #10802 2022-02-02 19:28:44 +01:00
Koen Kanters 73f02bfee3 Fix crash when availability is enabled per device. #11184 2022-02-02 17:39:45 +01:00
github-actions[bot]andKoenkk fae8e96229 Update zigbee-herdsman to 0.14.11 (#11185)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-02 10:40:49 +00:00
Koen Kanters 7f9b198722 Promote to dev 2022-02-01 19:13:28 +01:00
Koen Kanters 6ea61bae2c 1.23.0 2022-02-01 19:12:56 +01:00
Koen Kanters 5c915e5fd7 Undev 2022-02-01 19:12:11 +01:00
Koen Kanters b1f50701b2 Merge branch 'dev' 2022-02-01 19:11:47 +01:00
github-actions[bot]andKoenkk 273fc61099 Update zigbee-herdsman-converters to 14.0.406 (#11168)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-02-01 16:08:31 +00:00
github-actions[bot]andKoenkk 59ef38f076 Update zigbee-herdsman-converters to 14.0.405 (#11151)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-31 16:59:43 +00:00
Steven RoebertandKoen Kanters 9171ef35da Allowing to use HA device name override as base for entities (#11107)
* When overriding the home assistant name, use that name as the device name and as a base for each entity name

* Update homeassistant.ts

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-31 17:46:42 +01:00
Koen Kanters 912360c1f0 Fix availability passive device timeout config. https://github.com/Koenkk/zigbee2mqtt/pull/10437 2022-01-31 17:28:08 +01:00
github-actions[bot]andKoenkk 04290274b8 Update zigbee-herdsman to 0.14.10 (#11150)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-31 15:13:10 +00:00
github-actions[bot]andKoenkk 9fc413dea9 Update zigbee-herdsman to 0.14.9 (#11147)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-31 10:26:31 +00:00
github-actions[bot]andnurikk 7863f600c8 Update zigbee2mqtt-frontend to 0.6.71 (#11130)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-01-30 17:19:43 +00:00
github-actions[bot]andnurikk 0bdbf7194b Update zigbee2mqtt-frontend to 0.6.70 (#11122)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-01-30 15:07:02 +00:00
github-actions[bot]andKoenkk 81131c32a8 Update zigbee-herdsman-converters to 14.0.404 (#11120)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-30 13:27:41 +00:00
github-actions[bot]andKoenkk 26ae01a0e4 Update dependencies (#11108)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-30 14:12:07 +01:00
Ilya Kirov 93921c6be9 Fix endpoint match (again) (#11083)
https://github.com/Koenkk/zigbee2mqtt/pull/10898
https://github.com/Koenkk/zigbee2mqtt/issues/11068
2022-01-29 09:58:23 +01:00
github-actions[bot]andKoenkk 2721cc1bbf Update zigbee-herdsman-converters to 14.0.403 (#11078)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-28 16:32:45 +00:00
github-actions[bot]andKoenkk 9120ccf403 Update zigbee-herdsman to 0.14.8 (#11077)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-28 16:26:37 +00:00
Ilya Kirov@RubenKelevra <cyrond@gmail.com>Koen Kanters
b92131cad6 Revert "Fix endpoint match" (#11072)
* GitHub issue templates: : Remove leading "[Problem]" in the title and the requirements to fill out all fields (#10779)

* remove leading "[Problem] " in the title

We already set a tag for this

* remove the need to enter all field in a bug report

there are certain cases where this isn't needed and filling them out with blank texts just to remove the text with an edit after filing feels dumb.

* Revert "Fix endpoint match (#10898)"

This reverts commit 5b918d9e3474c313878ff4275b551f79f39c85e2.

Co-authored-by: @RubenKelevra <cyrond@gmail.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-28 15:45:05 +00:00
github-actions[bot]andKoenkk 0b06483ce0 Update zigbee-herdsman-converters to 14.0.402 (#11063)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-27 19:00:39 +00:00
github-actions[bot]andKoenkk 6a4fba82df Update zigbee-herdsman-converters to 14.0.401 (#11060)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-27 16:09:43 +00:00
github-actions[bot]andKoenkk 1a2943d45e Update zigbee-herdsman-converters to 14.0.400 (#11030)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-26 16:52:47 +00:00
Joshua SpenceandKoen Kanters 1a70b80282 Flush logger before exiting process (#10905)
* Wait for async logs

* Fix tests

* Better solution

Based on https://stackoverflow.com/a/59260151

* Fix lint/test coverage

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-26 15:57:51 +00:00
github-actions[bot]andKoenkk c4c02d2e2d Update zigbee-herdsman-converters to 14.0.399 (#11011)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-25 16:06:24 +00:00
github-actions[bot]andKoenkk 5b9b7e4804 Update zigbee-herdsman to 0.14.7 (#11003)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-25 11:33:24 +00:00
github-actions[bot]andKoenkk 8e8c3d7b49 Update zigbee-herdsman-converters to 14.0.398 (#10996)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-24 15:59:58 +00:00
github-actions[bot]andKoenkk f0f7662f9e Update zigbee-herdsman-converters to 14.0.397 (#10987)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-23 20:17:22 +00:00
github-actions[bot]andKoenkk 5ef7b673d8 Update zigbee-herdsman to 0.14.6 (#10985)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-23 20:05:07 +00:00
github-actions[bot]andnurikk b6d26e351c Update zigbee2mqtt-frontend to 0.6.69 (#10976)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-01-23 16:56:05 +00:00
github-actions[bot]andKoenkk a10306b247 Update zigbee-herdsman-converters to 14.0.396 (#10970)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-23 13:36:08 +00:00
github-actions[bot]andKoenkk e7e23a1092 Update dependencies (#10961)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-23 13:25:38 +01:00
github-actions[bot]andKoenkk d5de1c6248 Update zigbee-herdsman to 0.14.5 (#10950)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-22 14:12:23 +00:00
github-actions[bot]andKoenkk 14dd4800f1 Update zigbee-herdsman-converters to 14.0.395 (#10949)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-22 13:56:37 +00:00
Koen Kanters 3451c71a21 Updates for https://github.com/Koenkk/zigbee-herdsman-converters/pull/3708 2022-01-22 10:54:01 +01:00
Koen Kanters 8e72b3bf64 Add preferredColorMode option. https://github.com/Koenkk/zigbee-herdsman-converters/pull/3708 2022-01-22 08:48:37 +01:00
github-actions[bot]andKoenkk 3ba185815f Update zigbee-herdsman-converters to 14.0.394 (#10941)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-21 19:39:06 +00:00
Koen Kanters 64f83d74b7 Clear old availability topic when device is renamed. #5244 2022-01-21 20:21:18 +01:00
github-actions[bot]andKoenkk ef2f28019b Update zigbee-herdsman to 0.14.4 (#10939)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-21 19:11:48 +00:00
github-actions[bot]andKoenkk e446c81677 Update zigbee-herdsman-converters to 14.0.393 (#10917)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-20 19:09:12 +00:00
Ilya Kirov d87347923b Fix endpoint match (#10898)
https://github.com/Koenkk/zigbee2mqtt/discussions/9504

For this case topic is 'state_top_left', 'state_bottom_left' and etc..,
Old pattern it is match as: [state_top_left, state_top, left]
New pattern it is match as: [state_top_left, state, top_left]
2022-01-19 22:15:23 +01:00
github-actions[bot]andKoenkk a867b719dc Update zigbee-herdsman-converters to 14.0.392 (#10895)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-19 17:00:46 +00:00
github-actions[bot]andKoenkk da3817bd40 Update zigbee-herdsman to 0.14.3 (#10893)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-19 16:36:11 +00:00
github-actions[bot]andKoenkk 7f98caca22 Update zigbee-herdsman to 0.14.2 (#10887)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-19 07:39:24 +00:00
github-actions[bot]andKoenkk 107ac3659f Update zigbee-herdsman-converters to 14.0.391 (#10876)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-18 16:25:25 +00:00
github-actions[bot]andKoenkk 48dfd581c1 Update zigbee-herdsman-converters to 14.0.390 (#10859)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-17 17:07:52 +00:00
github-actions[bot]andKoenkk 8e53d745cd Update zigbee-herdsman-converters to 14.0.389 (#10840)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-16 21:50:06 +00:00
github-actions[bot]andKoenkk 6877c591b8 Update zigbee-herdsman to 0.14.1 (#10839)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-16 21:44:33 +00:00
Koen Kanters d0ceaba6f2 Fix OTA (#10833)
* dump

* updates

* Updates
2022-01-16 18:25:53 +01:00
0d60777565 Update zigbee-herdsman-converters to 14.0.388 (#10838)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-16 18:16:10 +01:00
github-actions[bot]andKoenkk 074c70be54 Update zigbee-herdsman to 0.14.0 (#10837)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-16 18:15:06 +01:00
Koen Kanters b6a7ade6f2 Publish bridge/state online when Home Assistant comes online. https://github.com/Koenkk/zigbee2mqtt/issues/9629 2022-01-16 14:56:58 +01:00
github-actions[bot]andKoenkk d6ee1a6450 Update zigbee-herdsman-converters to 14.0.387 (#10827)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-16 08:59:30 +00:00
github-actions[bot]andKoenkk 3617ba2b1d Update dependencies (#10826)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-16 09:11:18 +01:00
github-actions[bot]andKoenkk ec8e7f57fa Update zigbee-herdsman-converters to 14.0.386 (#10821)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-15 19:14:46 +00:00
github-actions[bot]andKoenkk 87d33febcf Update zigbee-herdsman-converters to 14.0.385 (#10803)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-15 10:20:46 +00:00
Valentin 43e7ca7ad6 Expose optional user settings to configure() (#10791) 2022-01-14 16:53:22 +01:00
github-actions[bot]andKoenkk 4fd98c2aca Update zigbee-herdsman-converters to 14.0.384 (#10790)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-14 15:35:44 +00:00
@RubenKelevra 1a0a9a6eab GitHub issue templates: : Remove leading "[Problem]" in the title and the requirements to fill out all fields (#10779)
* remove leading "[Problem] " in the title

We already set a tag for this

* remove the need to enter all field in a bug report

there are certain cases where this isn't needed and filling them out with blank texts just to remove the text with an edit after filing feels dumb.
2022-01-14 15:59:10 +01:00
github-actions[bot]andKoenkk 57852fcddf Update zigbee-herdsman-converters to 14.0.383 (#10769)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-13 18:48:27 +00:00
c1b0a13e10 Update zigbee-herdsman to 0.13.194 (#10739)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-12 18:20:42 +00:00
github-actions[bot]andKoenkk 3835b792f0 Update zigbee-herdsman-converters to 14.0.382 (#10738)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-12 18:15:43 +00:00
github-actions[bot]andKoenkk 00124215a8 Update dependencies (#10723)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-11 18:32:11 +00:00
github-actions[bot]andKoenkk cac384c3fb Update zigbee-herdsman to 0.13.193 (#10699)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-10 18:33:00 +00:00
github-actions[bot]andKoenkk 0b3727d296 Update zigbee-herdsman-converters to 14.0.380 (#10696)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-10 17:59:01 +00:00
Koen Kanters 30177b0db4 Restructure settings (#10437)
* -

* deep copy schema

* -

* -

* -

* -

* -

* -

* -

* -

* -

* -

* -

* -
2022-01-09 22:28:44 +01:00
Koen Kanters ee6b035108 Support homeassistant_rename for groups. https://github.com/Koenkk/zigbee2mqtt/discussions/10672 2022-01-09 20:39:15 +01:00
Oleksandr MasliuchenkoandKoen Kanters 6ac8a6237e Add support for local OTA firmware images (#10676)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-09 20:38:58 +01:00
github-actions[bot]andKoenkk 44ee2f5f2b Update zigbee-herdsman-converters to 14.0.379 (#10684)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-09 19:29:16 +00:00
github-actions[bot]andKoenkk 9d6009ec99 Update zigbee-herdsman-converters to 14.0.378 (#10669)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-09 09:46:42 +00:00
github-actions[bot]andKoenkk cd60eb5069 Update zigbee-herdsman to 0.13.192 (#10668)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-09 08:48:46 +00:00
github-actions[bot]andKoenkk a10687ecaa Update dependencies (#10667)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-09 09:26:41 +01:00
github-actions[bot]andKoenkk 89693df2e1 Update zigbee-herdsman to 0.13.191 (#10659)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-08 16:30:44 +00:00
Koen Kanters e783117a3a Xiaomi retransmitted messages handling has been moved to zigbee-herdsman. https://github.com/Koenkk/zigbee2mqtt/issues/10656 2022-01-08 17:25:20 +01:00
github-actions[bot]andKoenkk e6b041436a Update zigbee-herdsman-converters to 14.0.376 (#10654)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-08 14:38:40 +00:00
github-actions[bot]andKoenkk 3d4d7dc910 Update zigbee-herdsman-converters to 14.0.375 (#10650)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-07 22:21:24 +00:00
github-actions[bot]andKoenkk 811ef1dcb5 Update zigbee-herdsman-converters to 14.0.374 (#10646)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-07 16:31:15 +00:00
github-actions[bot]andKoenkk 208a552c02 Update zigbee-herdsman-converters to 14.0.373 (#10628)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-06 20:57:04 +00:00
github-actions[bot]andKoenkk 74cf20b172 Update zigbee-herdsman-converters to 14.0.372 (#10622)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-06 17:34:01 +00:00
Koen Kanters dbcd768e1d Log out of memory error when initial build fails. #10555 2022-01-06 17:59:10 +01:00
github-actions[bot]andKoenkk 43ded111fb Update zigbee-herdsman-converters to 14.0.371 (#10600)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-05 18:56:05 +00:00
Oleksandr MasliuchenkoandKoen Kanters 16842a3e72 Add posibility to have local OTA index (#10141)
* Add a configuration option to override OTA index file

* Cleanup

* Code review corrections

* Move zigbee_ota_override_index_location to ota section of the configuration file

* Suport both local and remote (HTTP) OTA override index files

* Updates

* updates

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-04 20:29:17 +01:00
Koen Kanters e88ceeec2e Merge remote-tracking branch 'origin/master' into dev 2022-01-04 20:23:20 +01:00
github-actions[bot]andKoenkk f51a7883fe Update zigbee-herdsman-converters to 14.0.370 (#10570)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-04 19:17:53 +00:00
github-actions[bot]andnurikk 4f887a1a3b Update zigbee2mqtt-frontend to 0.6.68 (#10539)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-01-03 15:38:54 +00:00
github-actions[bot]andKoenkk 23bdc18608 Update zigbee-herdsman-converters to 14.0.369 (#10523)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-02 21:42:02 +00:00
github-actions[bot]andKoenkk 101e0192bd Update zigbee-herdsman to 0.13.190 (#10522)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-02 21:30:24 +00:00
github-actions[bot]andKoenkk 24d9065d26 Update zigbee-herdsman-converters to 14.0.368 (#10521)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-02 21:07:03 +00:00
05d545945d Update zigbee-herdsman-converters to 14.0.367 (#10502)
* Update zigbee-herdsman-converters to 14.0.367

* Update publish.test.js

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-02 12:54:30 +00:00
github-actions[bot]andKoenkk 089ff8f370 Update zigbee-herdsman to 0.13.189 (#10503)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-02 10:39:13 +00:00
Koen Kanters 301ba14b00 Update new_device_support.yaml 2022-01-02 10:44:47 +01:00
ValentinandKoen Kanters 9d120d225d Automatically expose entitity as energy when Wh/kWh exists (#10133)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-01-02 09:39:34 +00:00
github-actions[bot]andKoenkk 38b07fb8ca Update dependencies (#10495)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-02 10:33:26 +01:00
Rotzbua e8caddaf63 Fix typo: suppress (#10490)
* Fix typo: suppress

* Fix typo: discovered
2022-01-02 07:11:39 +00:00
Koen Kanters 15f1df8fa4 Promote to dev 2022-01-01 10:47:30 +01:00
47 changed files with 5604 additions and 5112 deletions
@@ -50,5 +50,5 @@ body:
- type: input
attributes:
label: Color temperature range
description: In case this light supports color temperature, provide the color temperature range. [How to do this](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html#_3-1-retrieving-color-temperature-range-only-required-for-lights-which-support-color-temperature). This currently requires the latest [dev branch](https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html).
placeholder: '{"colorTempPhysicalMin":153,"colorTempPhysicalMax":454}'
description: In case this light supports color temperature, provide the color temperature range. [How to do this](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html#_3-1-retrieving-color-temperature-range-only-required-for-lights-which-support-color-temperature).
placeholder: '{"colorTempPhysicalMin":153,"colorTempPhysicalMax":454}'
+4 -5
View File
@@ -1,6 +1,5 @@
name: Problem report
description: Create a report to help us improve
title: "[Problem]: "
labels: [problem]
body:
- type: markdown
@@ -26,19 +25,19 @@ body:
label: What did you expect to happen?
placeholder: I expected that ...
validations:
required: true
required: false
- type: textarea
id: reproduce
attributes:
label: How to reproduce it (minimal and precise)
placeholder: First do this, than this..
validations:
required: true
required: false
- type: input
id: z2m_version
attributes:
label: Zigbee2MQTT version
description: Can be found in the frontend -> settings -> about -> Zigbee2MQTT version
description: Can be found in the frontend -> settings -> about -> Zigbee2MQTT version. Are you running Zigbee2MQTT 1.18.1? Then read [this](https://github.com/Koenkk/zigbee2mqtt/releases/tag/1.19.0).
placeholder: '1.22.1'
validations:
required: true
@@ -65,4 +64,4 @@ body:
description: After enabling [debug logging](https://www.zigbee2mqtt.io/guide/usage/debug.html) the log can be found under `data/log`. Attach the file below
placeholder: Click here and drag the file into it or click on "Attach files by.." below
validations:
required: true
required: false
+2 -3
View File
@@ -7,10 +7,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org/
node-version: 16
- name: Get NodeJS Version
id: nodeversion
run: |
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
with:
node-version: 14
node-version: 16
- run: rm -rf npm-shrinkwrap.json
- run: npx npm-check-updates -u
- run: npm install && npm shrinkwrap --dev
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
with:
node-version: 14
node-version: 16
- run: npm install zigbee2mqtt-frontend@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v3
id: cpr
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
with:
node-version: 14
node-version: 16
- run: npm install zigbee-herdsman@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v3
id: cpr
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
with:
node-version: 14
node-version: 16
- run: npm install zigbee-herdsman-converters@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v3
id: cpr
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:14-alpine3.12 as base
FROM node:16-alpine3.15 as base
WORKDIR /app
RUN apk add --no-cache tzdata eudev tini
+4
View File
@@ -47,6 +47,10 @@ async function build(reason) {
exec('npm run build', {cwd: __dirname}, async (err, stdout, stderr) => {
if (err) {
process.stdout.write(', failed\n');
if (err.code === 134) {
process.stderr.write(
'\n\nBuild failed; ran out-of-memory, free some memory (RAM) and start again\n\n');
}
reject(err);
} else {
process.stdout.write(', finished\n');
+16 -10
View File
@@ -104,13 +104,14 @@ class Controller {
logger.error('Check https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html for possible solutions'); /* eslint-disable-line max-len */
logger.error('Exiting...');
logger.error(error.stack);
this.exitCallback(1);
await this.exit(1);
}
// Disable some legacy options on new network creation
if (startResult === 'reset') {
settings.set(['advanced', 'homeassistant_legacy_entity_attributes'], false);
settings.set(['advanced', 'legacy_api'], false);
settings.set(['advanced', 'legacy_availability_payload'], false);
settings.set(['device_options', 'legacy'], false);
this.enableDisableExtension(false, 'BridgeLegacy');
}
@@ -145,7 +146,7 @@ class Controller {
logger.error(`MQTT failed to connect: ${error.message}`);
logger.error('Exiting...');
await this.zigbee.stop();
this.exitCallback(1);
await this.exit(1);
}
// Call extensions
@@ -197,13 +198,18 @@ class Controller {
try {
await this.zigbee.stop();
logger.info('Stopped Zigbee2MQTT');
this.exitCallback(0);
await this.exit(0);
} catch (error) {
logger.error('Failed to stop Zigbee2MQTT');
this.exitCallback(1);
await this.exit(1);
}
}
async exit(code: number): Promise<void> {
await logger.end();
this.exitCallback(code);
}
@bind async onZigbeeAdapterDisconnected(): Promise<void> {
logger.error('Adapter disconnected, stopping');
await this.stop();
@@ -222,11 +228,11 @@ class Controller {
}
const options: MQTTOptions = {
retain: utils.getObjectProperty(entity.settings, 'retain', false) as boolean,
qos: utils.getObjectProperty(entity.settings, 'qos', 0) as 0 | 1 | 2,
retain: utils.getObjectProperty(entity.options, 'retain', false) as boolean,
qos: utils.getObjectProperty(entity.options, 'qos', 0) as 0 | 1 | 2,
};
const retention = utils.getObjectProperty(entity.settings, 'retention', false);
const retention = utils.getObjectProperty(entity.options, 'retention', false);
if (retention !== false) {
options.properties = {messageExpiryInterval: retention as number};
}
@@ -259,12 +265,12 @@ class Controller {
}
// filter mqtt message attributes
if (entity.settings.filtered_attributes) {
entity.settings.filtered_attributes.forEach((a) => delete message[a]);
if (entity.options.filtered_attributes) {
entity.options.filtered_attributes.forEach((a) => delete message[a]);
}
if (Object.entries(message).length) {
const output = settings.get().experimental.output;
const output = settings.get().advanced.output;
if (output === 'attribute_and_json' || output === 'json') {
await this.mqtt.publish(entity.name, stringify(message), options);
}
+9 -2
View File
@@ -27,10 +27,17 @@ export default class EventBus {
this.on('permitJoinChanged', callback, key);
}
public emitDeviceRenamed(data: eventdata.DeviceRenamed): void {
public emitPublishAvailability(): void {
this.emitter.emit('publishAvailability');
}
public onPublishAvailability(key: ListenerKey, callback: () => void): void {
this.on('publishAvailability', callback, key);
}
public emitEntityRenamed(data: eventdata.EntityRenamed): void {
this.emitter.emit('deviceRenamed', data);
}
public onDeviceRenamed(key: ListenerKey, callback: (data: eventdata.DeviceRenamed) => void): void {
public onEntityRenamed(key: ListenerKey, callback: (data: eventdata.EntityRenamed) => void): void {
this.on('deviceRenamed', callback, key);
}
+69 -40
View File
@@ -5,6 +5,12 @@ import * as settings from '../util/settings';
import debounce from 'debounce';
import bind from 'bind-decorator';
const retrieveOnReconnect = [
{keys: ['state']},
{keys: ['brightness'], condition: (state: KeyValue): boolean => state.state === 'ON'},
{keys: ['color', 'color_temp'], condition: (state: KeyValue): boolean => state.state === 'ON'},
];
export default class Availability extends Extension {
private timers: {[s: string]: NodeJS.Timeout} = {};
private availabilityCache: {[s: string]: boolean} = {};
@@ -13,17 +19,14 @@ export default class Availability extends Extension {
private pingQueueExecuting = false;
private getTimeout(device: Device): number {
if (typeof device.settings.availability === 'object' && device.settings.availability?.timeout != null) {
return utils.minutes(device.settings.availability.timeout);
if (typeof device.options.availability === 'object' && device.options.availability?.timeout != null) {
return utils.minutes(device.options.availability.timeout);
}
const key = this.isActiveDevice(device) ? 'active' : 'passive';
const availabilitySettings = settings.get().availability;
if (typeof availabilitySettings === 'object' && availabilitySettings[key]?.timeout != null) {
return utils.minutes(availabilitySettings[key]?.timeout);
}
return key === 'active' ? utils.minutes(10) : utils.hours(25);
let value = settings.get().availability?.[key]?.timeout;
if (value == null) value = key == 'active' ? 10 : 1500;
return utils.minutes(value);
}
private isActiveDevice(device: Device): boolean {
@@ -31,9 +34,14 @@ export default class Availability extends Extension {
device.zh.powerSource === 'Mains (single phase)';
}
private isAvailable(device: Device): boolean {
const ago = Date.now() - device.zh.lastSeen;
return ago < this.getTimeout(device);
private isAvailable(entity: Device | Group): boolean {
if (entity.isDevice()) {
const ago = Date.now() - entity.zh.lastSeen;
return ago < this.getTimeout(entity);
} else {
return entity.membersDevices().length === 0 ||
entity.membersDevices().map((d) => this.availabilityCache[d.ieeeAddr]).includes(true);
}
}
private resetTimer(device: Device): void {
@@ -95,58 +103,78 @@ export default class Availability extends Extension {
}
override async start(): Promise<void> {
this.eventBus.onDeviceRenamed(this, (data) => this.publishAvailability(data.device, false, true));
this.eventBus.onEntityRenamed(this, (data) => {
if (utils.isAvailabilityEnabledForEntity(data.entity, settings.get())) {
this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 0});
this.publishAvailability(data.entity, false, true);
}
});
this.eventBus.onDeviceRemoved(this, (data) => clearTimeout(this.timers[data.ieeeAddr]));
this.eventBus.onDeviceLeave(this, (data) => clearTimeout(this.timers[data.ieeeAddr]));
this.eventBus.onDeviceAnnounce(this, (data) => this.retrieveState(data.device));
this.eventBus.onLastSeenChanged(this, this.onLastSeenChanged);
this.eventBus.onPublishAvailability(this, this.publishAvailabilityForAllEntities);
this.eventBus.onGroupMembersChanged(this, (data) => this.publishAvailability(data.group, false));
this.publishAvailabilityForAllEntities();
}
for (const device of this.zigbee.devices(false)) {
if (utils.isAvailabilityEnabledForDevice(device, settings.get())) {
@bind private publishAvailabilityForAllEntities(): void {
for (const entity of [...this.zigbee.devices(false), ...this.zigbee.groups()]) {
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
// Publish initial availablility
this.publishAvailability(device, true);
this.publishAvailability(entity, true, false, true);
this.resetTimer(device);
if (entity.isDevice()) {
this.resetTimer(entity);
// If an active device is initially unavailable, ping it.
if (this.isActiveDevice(device) && !this.isAvailable(device)) {
this.addToPingQueue(device);
// If an active device is initially unavailable, ping it.
if (this.isActiveDevice(entity) && !this.isAvailable(entity)) {
this.addToPingQueue(entity);
}
}
}
}
}
private publishAvailability(device: Device, logLastSeen: boolean, forcePublish=false): void {
if (logLastSeen) {
const ago = Date.now() - device.zh.lastSeen;
if (this.isActiveDevice(device)) {
logger.debug(`Active device '${device.name}' was last seen ` +
private publishAvailability(entity: Device | Group, logLastSeen: boolean,
forcePublish=false, skipGroups=false): void {
if (logLastSeen && entity.isDevice()) {
const ago = Date.now() - entity.zh.lastSeen;
if (this.isActiveDevice(entity)) {
logger.debug(`Active device '${entity.name}' was last seen ` +
`'${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
} else {
logger.debug(
`Passive device '${device.name}' was last seen '${(ago / utils.hours(1)).toFixed(2)}' hours ago.`);
`Passive device '${entity.name}' was last seen '${(ago / utils.hours(1)).toFixed(2)}' hours ago.`);
}
}
const available = this.isAvailable(device);
if (!forcePublish && this.availabilityCache[device.ieeeAddr] == available) {
const available = this.isAvailable(entity);
if (!forcePublish && this.availabilityCache[entity.ID] == available) {
return;
}
if (device.ieeeAddr in this.availabilityCache && available &&
this.availabilityCache[device.ieeeAddr] === false) {
logger.debug(`Device '${device.name}' reconnected`);
this.retrieveState(device);
if (entity.isDevice() && entity.ieeeAddr in this.availabilityCache && available &&
this.availabilityCache[entity.ieeeAddr] === false) {
logger.debug(`Device '${entity.name}' reconnected`);
this.retrieveState(entity);
}
const topic = `${device.name}/availability`;
const payload = available ? 'online' : 'offline';
this.availabilityCache[device.ieeeAddr] = available;
const topic = `${entity.name}/availability`;
const payload = utils.availabilityPayload(available ? 'online' : 'offline', settings.get());
this.availabilityCache[entity.ID] = available;
this.mqtt.publish(topic, payload, {retain: true, qos: 0});
if (!skipGroups && entity.isDevice()) {
this.zigbee.groups().filter((g) => g.hasMember(entity))
.filter((g) => utils.isAvailabilityEnabledForEntity(g, settings.get()))
.forEach((g) => this.publishAvailability(g, false, forcePublish));
}
}
@bind private onLastSeenChanged(data: eventdata.LastSeenChanged): void {
if (utils.isAvailabilityEnabledForDevice(data.device, settings.get())) {
if (utils.isAvailabilityEnabledForEntity(data.device, settings.get())) {
// Remove from ping queue, not necessary anymore since we know the device is online.
this.removeFromPingQueue(data.device);
this.resetTimer(data.device);
@@ -165,17 +193,18 @@ export default class Availability extends Extension {
* device can send multiple times after each other.
*/
if (device.definition && !device.zh.interviewing && !this.retrieveStateDebouncers[device.ieeeAddr]) {
this.retrieveStateDebouncers[device.ieeeAddr] = debounce(() => {
this.retrieveStateDebouncers[device.ieeeAddr] = debounce(async () => {
logger.debug(`Retrieving state of '${device.name}' after reconnect`);
// Color and color temperature converters do both, only needs to be called once.
const keySet = [['state'], ['brightness'], ['color', 'color_temp']];
for (const keys of keySet) {
const converter = device.definition.toZigbee.find((c) => c.key.find((k) => keys.includes(k)));
converter?.convertGet?.(device.endpoint(), keys[0],
for (const item of retrieveOnReconnect) {
if (item.condition && this.state.get(device) && !item.condition(this.state.get(device))) continue;
const converter = device.definition.toZigbee.find((c) => c.key.find((k) => item.keys.includes(k)));
await converter?.convertGet?.(device.endpoint(), item.keys[0],
{message: this.state.get(device) || {}, mapped: device.definition})
.catch((e) => {
logger.error(`Failed to read state of '${device.name}' after reconnect (${e.message})`);
});
await utils.sleep(500);
}
}, utils.seconds(2));
}
+10 -7
View File
@@ -64,7 +64,7 @@ export default class Bridge extends Extension {
this.zigbee2mqttVersion = await utils.getZigbee2MQTTVersion();
this.coordinatorVersion = await this.zigbee.getCoordinatorVersion();
this.eventBus.onDeviceRenamed(this, () => this.publishInfo());
this.eventBus.onEntityRenamed(this, () => this.publishInfo());
this.eventBus.onGroupMembersChanged(this, () => this.publishGroups());
this.eventBus.onDevicesChanged(this, () => this.publishDevices() && this.publishInfo());
this.eventBus.onPermitJoinChanged(this, () => !this.zigbee.isStopping() && this.publishInfo());
@@ -392,9 +392,9 @@ export default class Bridge extends Extension {
const ID = message.id;
const entity = this.getEntity(entityType, ID);
const oldOptions = objectAssignDeep({}, cleanup(entity.settings));
const oldOptions = objectAssignDeep({}, cleanup(entity.options));
settings.changeEntityOptions(ID, message.options);
const newOptions = cleanup(entity.settings);
const newOptions = cleanup(entity.options);
await this.publishInfo();
logger.info(`Changed config for ${entityType} ${ID}`);
@@ -419,7 +419,7 @@ export default class Bridge extends Extension {
await endpoint.configureReporting(message.cluster, [{
attribute: message.attribute, minimumReportInterval: message.minimum_report_interval,
maximumReportInterval: message.maximum_report_interval, reportableChange: message.reportable_change,
}]);
}], message.options);
this.publishDevices();
@@ -448,16 +448,17 @@ export default class Bridge extends Extension {
const homeAssisantRename = message.hasOwnProperty('homeassistant_rename') ?
message.homeassistant_rename : false;
const entity = this.getEntity(entityType, from);
const oldFriendlyName = entity.settings.friendly_name;
const oldFriendlyName = entity.options.friendly_name;
settings.changeFriendlyName(from, to);
// Clear retained messages
this.mqtt.publish(oldFriendlyName, '', {retain: true});
this.eventBus.emitEntityRenamed({entity: entity, homeAssisantRename, from: oldFriendlyName, to});
if (entity instanceof Device) {
this.publishDevices();
this.eventBus.emitDeviceRenamed({device: entity, homeAssisantRename, from: oldFriendlyName, to});
} else {
this.publishGroups();
this.publishInfo();
@@ -649,6 +650,7 @@ export default class Bridge extends Extension {
network_address: device.zh.networkAddress,
supported: !!device.definition,
friendly_name: device.name,
description: device.options.description,
definition: this.getDefinitionPayload(device),
power_source: device.zh.powerSource,
software_build_id: device.zh.softwareBuildID,
@@ -670,6 +672,7 @@ export default class Bridge extends Extension {
return {
id: g.ID,
friendly_name: g.ID === 901 ? 'default_bind_group' : g.name,
description: g.options.description,
scenes: this.getScenes(g.zh),
members: g.zh.members.map((e) => {
return {ieee_address: e.getDevice().ieeeAddr, endpoint: e.ID};
@@ -682,7 +685,7 @@ export default class Bridge extends Extension {
getDefinitionPayload(device: Device): DefinitionPayload {
if (!device.definition) return null;
let icon = device.settings.icon ? device.settings.icon : device.definition.icon;
let icon = device.options.icon ? device.options.icon : device.definition.icon;
if (icon) {
icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(device.zh.modelID));
icon = icon.replace('${model}', utils.sanitizeImageParameter(device.definition.model));
+3 -2
View File
@@ -85,7 +85,7 @@ export default class Configure extends Extension {
private async configure(device: Device, event: 'started' | 'zigbee_event' | 'reporting_disabled' | 'mqtt_message',
force=false, thowError=false): Promise<void> {
if (!force) {
if (!device.definition?.configure || device.zh.interviewing) {
if (!device.definition?.configure || !device.zh.interviewCompleted) {
return;
}
@@ -112,7 +112,8 @@ export default class Configure extends Extension {
logger.info(`Configuring '${device.name}'`);
try {
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger);
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger,
device.options);
logger.info(`Successfully configured '${device.name}'`);
device.zh.meta.configured = zhc.getConfigureKey(device.definition);
device.zh.save();
+3 -3
View File
@@ -17,9 +17,9 @@ import bind from 'bind-decorator';
*/
export default class Frontend extends Extension {
private mqttBaseTopic = settings.get().mqtt.base_topic;
private host = settings.get().frontend.host || '0.0.0.0';
private port = settings.get().frontend.port || 8080;
private authToken = settings.get().frontend.auth_token || false;
private host = settings.get().frontend.host;
private port = settings.get().frontend.port;
private authToken = settings.get().frontend.auth_token;
private retainedMessages = new Map();
private server: http.Server;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
+1 -1
View File
@@ -123,7 +123,7 @@ export default class Groups extends Extension {
if (Object.keys(payload).length) {
const entity = data.entity;
const groups = this.zigbee.groups().filter((g) => {
return g.settings && (!g.settings.hasOwnProperty('optimistic') || g.settings.optimistic);
return g.options && (!g.options.hasOwnProperty('optimistic') || g.options.optimistic);
});
if (entity instanceof Device) {
+128 -45
View File
@@ -65,16 +65,16 @@ export default class HomeAssistant extends Extension {
private discovered: {[s: string]:
{topics: Set<string>, mockProperties: Set<MockProperty>, objectIDs: Set<string>}} = {};
private discoveredTriggers : {[s: string]: Set<string>}= {};
private discoveryTopic = settings.get().advanced.homeassistant_discovery_topic;
private statusTopic = settings.get().advanced.homeassistant_status_topic;
private entityAttributes = settings.get().advanced.homeassistant_legacy_entity_attributes;
private discoveryTopic = settings.get().homeassistant.discovery_topic;
private statusTopic = settings.get().homeassistant.status_topic;
private entityAttributes = settings.get().homeassistant.legacy_entity_attributes;
private zigbee2MQTTVersion: string;
constructor(zigbee: Zigbee, mqtt: MQTT, state: State, publishEntityState: PublishEntityState,
eventBus: EventBus, enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
restartCallback: () => void, addExtension: (extension: Extension) => Promise<void>) {
super(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension, restartCallback, addExtension);
if (settings.get().experimental.output === 'attribute') {
if (settings.get().advanced.output === 'attribute') {
throw new Error('Home Assistant integration is not possible with attribute output!');
}
}
@@ -88,13 +88,14 @@ export default class HomeAssistant extends Extension {
this.eventBus.onDeviceRemoved(this, this.onDeviceRemoved);
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onDeviceRenamed(this, this.onDeviceRenamed);
this.eventBus.onEntityRenamed(this, this.onEntityRenamed);
this.eventBus.onPublishEntityState(this, this.onPublishEntityState);
this.eventBus.onGroupMembersChanged(this, this.onGroupMembersChanged);
this.eventBus.onDeviceAnnounce(this, this.onZigbeeEvent);
this.eventBus.onDeviceJoined(this, this.onZigbeeEvent);
this.eventBus.onDeviceInterview(this, this.onZigbeeEvent);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
this.eventBus.onEntityOptionsChanged(this, (data) => this.discover(data.entity, true));
this.mqtt.subscribe(this.statusTopic);
this.mqtt.subscribe(defaultStatusTopic);
@@ -104,6 +105,9 @@ export default class HomeAssistant extends Extension {
for (const entity of [...this.zigbee.devices(false), ...this.zigbee.groups()]) {
this.discover(entity, true);
}
// Send availability messages, this is required if the legacy_availability_payload option has been changed.
this.eventBus.emitPublishAvailability();
}
private exposeToConfig(exposes: zhc.DefinitionExpose[], entityType: 'device' | 'group',
@@ -127,6 +131,11 @@ export default class HomeAssistant extends Extension {
const hasBrightness = exposes.find((expose) => expose.features.find((e) => e.name === 'brightness'));
const hasColorTemp = exposes.find((expose) => expose.features.find((e) => e.name === 'color_temp'));
const state = firstExpose.features.find((f) => f.name === 'state');
// Prefer HS over XY when at least one of the lights in the group prefers HS over XY.
// A light prefers HS over XY when HS is earlier in the feature array than HS.
const preferHS = exposes.map((e) => [e.features.findIndex((ee) => ee.name === 'color_xy'),
e.features.findIndex((ee) => ee.name === 'color_hs')])
.filter((d) => d[0] !== -1 && d[1] !== -1 && d[1] < d[0]).length !== 0;
const discoveryEntry: DiscoveryEntry = {
type: 'light',
@@ -143,8 +152,8 @@ export default class HomeAssistant extends Extension {
};
const colorModes = [
hasColorXY ? 'xy' : null,
!hasColorXY && hasColorHS ? 'hs' : null,
hasColorXY && !preferHS ? 'xy' : null,
(!hasColorXY || preferHS) && hasColorHS ? 'hs' : null,
hasColorTemp ? 'color_temp' : null,
].filter((c) => c);
@@ -371,6 +380,8 @@ export default class HomeAssistant extends Extension {
} else if (firstExpose.type === 'cover') {
const position = exposes.find((expose) => expose.features.find((e) => e.name === 'position'));
const tilt = exposes.find((expose) => expose.features.find((e) => e.name === 'tilt'));
const motorState = definitionExposes?.find((e) => e.type === 'enum' && e.name === 'motor_state' &&
e.access === ACCESS_STATE);
const discoveryEntry: DiscoveryEntry = {
type: 'cover',
@@ -389,6 +400,43 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.command_topic = true;
discoveryEntry.discovery_payload.state_topic = !position;
discoveryEntry.discovery_payload.command_topic_prefix = endpoint;
// For curtains that have `motor_state` lookup a possible state names and make this
// available for discovery. If the curtains only support the `running` value,
// then we use it anyway. The movement direction is calculated (assumed) in this case.
if (motorState) {
const openingLookup = ['opening', 'open', 'forward', 'up', 'rising'];
const closingLookup = ['closing', 'close', 'backward', 'back', 'reverse', 'down', 'declining'];
const stoppedLookup = ['stopped', 'stop', 'pause', 'paused'];
const openingState = motorState.values.find((s) => openingLookup.includes(s.toLowerCase()));
const closingState = motorState.values.find((s) => closingLookup.includes(s.toLowerCase()));
const stoppedState = motorState.values.find((s) => stoppedLookup.includes(s.toLowerCase()));
if (openingState) {
discoveryEntry.discovery_payload.state_opening = openingState;
}
if (closingState) {
discoveryEntry.discovery_payload.state_closing = closingState;
}
if (stoppedState) {
discoveryEntry.discovery_payload.state_stopped = stoppedState;
}
if (openingState || closingState || stoppedState) {
discoveryEntry.discovery_payload.state_topic = true;
discoveryEntry.discovery_payload.value_template = `{{ value_json.motor_state }}`;
}
} else {
const running = definitionExposes?.find((e) => e.type === 'binary' && e.name === 'running');
if (running) {
discoveryEntry.discovery_payload.state_topic = true;
discoveryEntry.discovery_payload.value_template = `{% if not value_json.running %} ` +
`stopped {% else %} {% if value_json.position > 0 %} closing {% else %} ` +
`opening {% endif %} {% endif %}`;
}
}
}
if (!position && !tilt) {
@@ -490,6 +538,7 @@ export default class HomeAssistant extends Extension {
color_sync: {entity_category: 'config', icon: 'mdi:sync-circle'},
consumer_connected: {entity_category: 'diagnostic', device_class: 'connectivity'},
contact: {device_class: 'door'},
garage_door_contact: {device_class: 'garage'},
eco_mode: {entity_category: 'config', icon: 'mdi:leaf'},
expose_pin: {entity_category: 'config', icon: 'mdi:pin'},
gas: {device_class: 'gas'},
@@ -552,6 +601,12 @@ export default class HomeAssistant extends Extension {
}
} else if (firstExpose.type === 'numeric') {
const lookup: {[s: string]: KeyValue} = {
ac_frequency: {device_class: 'frequency', enabled_by_default: false, entity_category: 'diagnostic',
state_class: 'measurement'},
alarm_humidity_max: {device_class: 'humidity', icon: 'mdi:water-plus'},
alarm_humidity_min: {device_class: 'humidity', icon: 'mdi:water-minus'},
alarm_temperature_max: {device_class: 'temperature', icon: 'mdi:thermometer-high'},
alarm_temperature_min: {device_class: 'temperature', icon: 'mdi:thermometer-low'},
angle: {icon: 'angle-acute'},
angle_axis: {icon: 'angle-acute'},
aqi: {device_class: 'aqi', state_class: 'measurement'},
@@ -613,8 +668,11 @@ export default class HomeAssistant extends Extension {
occupancy_timeout: {entity_category: 'config', icon: 'mdi:timer'},
pm10: {device_class: 'pm10', state_class: 'measurement'},
pm25: {device_class: 'pm25', state_class: 'measurement'},
people: {state_class: 'measurement', icon: 'mdi:account-multiple'},
position: {icon: 'mdi:valve', state_class: 'measurement'},
power: {device_class: 'power', entity_category: 'diagnostic', state_class: 'measurement'},
power_factor: {device_class: 'power_factor', enabled_by_default: false,
entity_category: 'diagnostic', state_class: 'measurement'},
precision: {entity_category: 'config', icon: 'mdi:decimal-comma-increase'},
pressure: {device_class: 'pressure', state_class: 'measurement'},
presence_timeout: {entity_category: 'config', icon: 'mdi:timer'},
@@ -653,6 +711,13 @@ export default class HomeAssistant extends Extension {
z_axis: {icon: 'mdi:axis-z-arrow'},
};
const extraAttrs = {};
// If a variable includes Wh, mark it as energy
if (firstExpose.unit && ['Wh', 'kWh'].includes(firstExpose.unit)) {
Object.assign(extraAttrs, {device_class: 'energy', state_class: 'total_increasing'});
}
const allowsSet = firstExpose.access & ACCESS_SET;
const discoveryEntry: DiscoveryEntry = {
@@ -664,13 +729,14 @@ export default class HomeAssistant extends Extension {
enabled_by_default: !allowsSet,
...(firstExpose.unit && {unit_of_measurement: firstExpose.unit}),
...lookup[firstExpose.name],
...extraAttrs,
},
};
discoveryEntries.push(discoveryEntry);
/**
* If numeric attribute has SET access then expose as SELECT entity too.
* Note: currently both sensor and number are discoverd, this is to avoid
* Note: currently both sensor and number are discovered, this is to avoid
* breaking changes for sensors already existing in HA (legacy).
*/
if (allowsSet) {
@@ -696,6 +762,8 @@ export default class HomeAssistant extends Extension {
} else if (firstExpose.type === 'enum') {
const lookup: {[s: string]: KeyValue} = {
action: {icon: 'mdi:gesture-double-tap'},
alarm_humidity: {icon: 'mdi:water-percent-alert'},
alarm_temperature: {icon: 'mdi:thermometer-alert'},
backlight_auto_dim: {entity_category: 'config', icon: 'mdi:brightness-auto'},
backlight_mode: {entity_category: 'config', icon: 'mdi:lightbulb'},
color_power_on_behavior: {entity_category: 'config', icon: 'mdi:palette'},
@@ -714,6 +782,7 @@ export default class HomeAssistant extends Extension {
sensitivity: {entity_category: 'config', icon: 'mdi:tune'},
sensors_type: {entity_category: 'config', icon: 'mdi:tune'},
sound_volume: {entity_category: 'config', icon: 'mdi:volume-high'},
status: {icon: 'mdi:state-machine'},
switch_type: {entity_category: 'config', icon: 'mdi:tune'},
thermostat_unit: {entity_category: 'config', icon: 'mdi:thermometer'},
volume: {entity_category: 'config', icon: 'mdi: volume-high'},
@@ -734,7 +803,7 @@ export default class HomeAssistant extends Extension {
/**
* If enum attribute has SET access then expose as SELECT entity too.
* Note: currently both sensor and select are discoverd, this is to avoid
* Note: currently both sensor and select are discovered, this is to avoid
* breaking changes for sensors already existing in HA (legacy).
*/
if ((firstExpose.access & ACCESS_SET)) {
@@ -827,7 +896,7 @@ export default class HomeAssistant extends Extension {
* can use Home Assistant entities in automations.
* https://github.com/Koenkk/zigbee2mqtt/issues/959#issuecomment-480341347
*/
if (settings.get().advanced.homeassistant_legacy_triggers) {
if (settings.get().homeassistant.legacy_triggers) {
const keys = ['action', 'click'].filter((k) => data.message[k]);
for (const key of keys) {
this.publishEntityState(data.entity, {[key]: ''});
@@ -850,25 +919,25 @@ export default class HomeAssistant extends Extension {
}
}
@bind onDeviceRenamed(data: eventdata.DeviceRenamed): void {
logger.debug(`Refreshing Home Assistant discovery topic for '${data.device.ieeeAddr}'`);
@bind onEntityRenamed(data: eventdata.EntityRenamed): void {
logger.debug(`Refreshing Home Assistant discovery topic for '${data.entity.name}'`);
// Clear before rename so Home Assistant uses new friendly_name
// https://github.com/Koenkk/zigbee2mqtt/issues/4096#issuecomment-674044916
if (data.homeAssisantRename) {
for (const config of this.getConfigs(data.device)) {
const topic = this.getDiscoveryTopic(config, data.device);
for (const config of this.getConfigs(data.entity)) {
const topic = this.getDiscoveryTopic(config, data.entity);
this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic, false, false);
}
}
this.discover(data.device, true);
this.discover(data.entity, true);
if (this.discoveredTriggers[data.device.ieeeAddr]) {
for (const config of this.discoveredTriggers[data.device.ieeeAddr]) {
if (data.entity.isDevice() && this.discoveredTriggers[data.entity.ieeeAddr]) {
for (const config of this.discoveredTriggers[data.entity.ieeeAddr]) {
const key = config.substring(0, config.indexOf('_'));
const value = config.substring(config.indexOf('_') + 1);
this.publishDeviceTriggerDiscover(data.device, key, value, true);
this.publishDeviceTriggerDiscover(data.entity, key, value, true);
}
}
}
@@ -880,8 +949,9 @@ export default class HomeAssistant extends Extension {
let configs: DiscoveryEntry[] = [];
if (isDevice) {
for (const expose of entity.exposes()) {
configs.push(...this.exposeToConfig([expose], 'device', entity.definition, entity.exposes()));
const exposes = entity.exposes(); // avoid calling it hundred of times/s
for (const expose of exposes) {
configs.push(...this.exposeToConfig([expose], 'device', entity.definition, exposes));
}
for (const mapping of legacyMapping) {
@@ -970,19 +1040,19 @@ export default class HomeAssistant extends Extension {
configs.push(updateAvailableSensor);
}
if (isDevice && entity.settings.hasOwnProperty('legacy') && !entity.settings.legacy) {
if (isDevice && entity.options.hasOwnProperty('legacy') && !entity.options.legacy) {
configs = configs.filter((c) => c !== sensorClick);
}
if (!settings.get().advanced.homeassistant_legacy_triggers) {
if (!settings.get().homeassistant.legacy_triggers) {
configs = configs.filter((c) => c.object_id !== 'action' && c.object_id !== 'click');
}
// deep clone of the config objects
configs = JSON.parse(JSON.stringify(configs));
if (entity.settings.homeassistant) {
const s = entity.settings.homeassistant;
if (entity.options.homeassistant) {
const s = entity.options.homeassistant;
configs = configs.filter((config) => !s.hasOwnProperty(config.object_id) || s[config.object_id] != null);
configs.forEach((config) => {
const configOverride = s[config.object_id];
@@ -1001,14 +1071,14 @@ export default class HomeAssistant extends Extension {
}
private discover(entity: Device | Group, force=false): void {
// Check if already discoverd and check if there are configs.
// Check if already discovered and check if there are configs.
const discoverKey = this.getDiscoverKey(entity);
const discover = force || !this.discovered[discoverKey];
if (entity.isGroup()) {
if (!discover || entity.zh.members.length === 0) return;
} else if (!discover || !entity.definition || entity.zh.interviewing ||
(entity.settings.hasOwnProperty('homeassistant') && !entity.settings.homeassistant)) {
(entity.options.hasOwnProperty('homeassistant') && !entity.options.homeassistant)) {
return;
}
@@ -1043,9 +1113,11 @@ export default class HomeAssistant extends Extension {
payload.json_attributes_topic = stateTopic;
}
// Set (unique) name, separate by space if friendlyName contains space.
const nameSeparator = entity.name.includes('_') ? '_' : ' ';
payload.name = entity.name;
const devicePayload = this.getDevicePayload(entity);
// Set (unique) name, separate by space if device name contains space.
const nameSeparator = devicePayload.name.includes('_') ? '_' : ' ';
payload.name = devicePayload.name;
if (config.object_id.startsWith(config.type) && config.object_id.includes('_')) {
payload.name += `${nameSeparator}${config.object_id.split(/_(.+)/)[1]}`;
} else if (!config.object_id.startsWith(config.type)) {
@@ -1053,22 +1125,24 @@ export default class HomeAssistant extends Extension {
}
// Set unique_id
payload.unique_id = `${entity.settings.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
payload.unique_id = `${entity.options.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
// Attributes for device registry
payload.device = this.getDevicePayload(entity);
payload.device = devicePayload;
// Availability payload
payload.availability = [{topic: `${settings.get().mqtt.base_topic}/bridge/state`}];
const availabilityEnabled =
entity.isDevice() && utils.isAvailabilityEnabledForDevice(entity, settings.get());
/* istanbul ignore next */
if (availabilityEnabled) {
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
payload.availability_mode = 'all';
payload.availability.push({topic: `${baseTopic}/availability`});
}
if (!settings.get().advanced.legacy_availability_payload) {
payload.availability.forEach((a: KeyValue) => a.value_template = '{{ value_json.state }}');
}
const commandTopicPrefix = payload.command_topic_prefix ? `${payload.command_topic_prefix}/` : '';
delete payload.command_topic_prefix;
const commandTopicPostfix = payload.command_topic_postfix ? `/${payload.command_topic_postfix}` : '';
@@ -1171,11 +1245,13 @@ export default class HomeAssistant extends Extension {
}
// Override configuration with user settings.
if (entity.settings.hasOwnProperty('homeassistant')) {
const add = (obj: KeyValue): void => {
if (entity.options.hasOwnProperty('homeassistant')) {
const add = (obj: KeyValue, ignoreName: boolean): void => {
Object.keys(obj).forEach((key) => {
if (['type', 'object_id'].includes(key)) {
return;
} else if (ignoreName && key === 'name') {
return;
} else if (['number', 'string', 'boolean'].includes(typeof obj[key]) ||
Array.isArray(obj[key])) {
payload[key] = obj[key];
@@ -1189,10 +1265,10 @@ export default class HomeAssistant extends Extension {
});
};
add(entity.settings.homeassistant);
add(entity.options.homeassistant, true);
if (entity.settings.homeassistant.hasOwnProperty(config.object_id)) {
add(entity.settings.homeassistant[config.object_id]);
if (entity.options.homeassistant.hasOwnProperty(config.object_id)) {
add(entity.options.homeassistant[config.object_id], false);
}
}
@@ -1210,7 +1286,7 @@ export default class HomeAssistant extends Extension {
const discoveryMatch = data.topic.match(discoveryRegex);
const isDeviceAutomation = discoveryMatch && discoveryMatch[1] === 'device_automation';
if (discoveryMatch) {
// Clear outdated discovery configs and remember already discoverd device_automations
// Clear outdated discovery configs and remember already discovered device_automations
let message: KeyValue = null;
try {
message = JSON.parse(data.message);
@@ -1252,7 +1328,7 @@ export default class HomeAssistant extends Extension {
`${this.discoveryTopic}/${this.getDiscoveryTopic(c, entity)}` === data.topic);
}
// Device was flagged to be excluded from homeassistant discovery
clear = clear || (entity.settings.hasOwnProperty('homeassistant') && !entity.settings.homeassistant);
clear = clear || (entity.options.hasOwnProperty('homeassistant') && !entity.options.homeassistant);
if (clear) {
logger.debug(`Clearing Home Assistant config '${data.topic}'`);
@@ -1281,9 +1357,16 @@ export default class HomeAssistant extends Extension {
private getDevicePayload(entity: Device | Group): KeyValue {
const identifierPostfix = entity.isGroup() ?
`zigbee2mqtt_${this.getEncodedBaseTopic()}` : 'zigbee2mqtt';
// Allow device name to be overriden by homeassistant config
let deviceName = entity.name;
if (typeof entity.options.homeassistant?.name === 'string') {
deviceName = entity.options.homeassistant.name;
}
const payload: KeyValue = {
identifiers: [`${identifierPostfix}_${entity.settings.ID}`],
name: entity.name,
identifiers: [`${identifierPostfix}_${entity.options.ID}`],
name: deviceName,
sw_version: `Zigbee2MQTT ${this.zigbee2MQTTVersion}`,
};
@@ -1331,8 +1414,8 @@ export default class HomeAssistant extends Extension {
}
private async publishDeviceTriggerDiscover(device: Device, key: string, value: string, force=false): Promise<void> {
const haConfig = device.settings.homeassistant;
if (device.settings.hasOwnProperty('homeassistant') && (haConfig == null ||
const haConfig = device.options.homeassistant;
if (device.options.hasOwnProperty('homeassistant') && (haConfig == null ||
(haConfig.hasOwnProperty('device_automation') && typeof haConfig === 'object' &&
haConfig.device_automation == null))) {
return;
+3 -3
View File
@@ -51,7 +51,7 @@ export default class BridgeLegacy extends Extension {
try {
const entity = settings.getDevice(message);
assert(entity, `Entity '${message}' does not exist`);
settings.whitelistDevice(entity.ID.toString());
settings.addDeviceToPasslist(entity.ID.toString());
logger.info(`Whitelisted '${entity.friendly_name}'`);
this.mqtt.publish(
'bridge/log',
@@ -217,7 +217,7 @@ export default class BridgeLegacy extends Extension {
logger.info(`Successfully renamed - ${from} to ${to} `);
const entity = this.zigbee.resolveEntity(to);
if (entity.isDevice()) {
this.eventBus.emitDeviceRenamed({homeAssisantRename: false, from, to, device: entity});
this.eventBus.emitEntityRenamed({homeAssisantRename: false, from, to, entity});
}
this.mqtt.publish(
@@ -336,7 +336,7 @@ export default class BridgeLegacy extends Extension {
}
if (action === 'ban') {
settings.banDevice(ieeeAddr);
settings.blockDevice(ieeeAddr);
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ export default class OnEvent extends Extension {
zhc.onEvent(type, data, device.zh);
if (device.definition?.onEvent) {
await device.definition.onEvent(type, data, device.zh, device.settings);
await device.definition.onEvent(type, data, device.zh, device.options);
}
}
}
+39 -11
View File
@@ -3,9 +3,24 @@ import logger from '../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import utils from '../util/utils';
import tradfriOTA from 'zigbee-herdsman-converters/lib/ota/tradfri';
import zigbeeOTA from 'zigbee-herdsman-converters/lib/ota/zigbeeOTA';
import Extension from './extension';
import bind from 'bind-decorator';
import Device from '../model/device';
import dataDir from '../util/data';
import * as URI from 'uri-js';
import path from 'path';
function isValidUrl(url: string): boolean {
let parsed;
try {
parsed = URI.parse(url);
} catch (_) {
// istanbul ignore next
return false;
}
return parsed.scheme === 'http' || parsed.scheme === 'https';
}
type UpdateState = 'updating' | 'idle' | 'available';
interface UpdatePayload {
@@ -24,13 +39,27 @@ export default class OTAUpdate extends Extension {
override async start(): Promise<void> {
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
if (settings.get().advanced.ikea_ota_use_test_url) {
if (settings.get().ota.ikea_ota_use_test_url) {
tradfriOTA.useTestURL();
}
// Let zigbeeOTA module know if the override index file is provided
let overrideOTAIndex = settings.get().ota.zigbee_ota_override_index_location;
if (overrideOTAIndex) {
// If the file name is not a full path, then treat it as a relative to the data directory
if (!isValidUrl(overrideOTAIndex) && !path.isAbsolute(overrideOTAIndex)) {
overrideOTAIndex = dataDir.joinPath(overrideOTAIndex);
}
zigbeeOTA.useIndexOverride(overrideOTAIndex);
}
// In order to support local firmware files we need to let zigbeeOTA know where the data directory is
zigbeeOTA.setDataDir(dataDir.getPath());
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state.
// remove them.
for (const device of this.zigbee.devices(false)) {
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state.
// remove them.
this.removeProgressAndRemainingFromState(device);
}
}
@@ -92,11 +121,11 @@ export default class OTAUpdate extends Extension {
}
}
private async readSoftwareBuildIDAndDateCode(device: Device, sendWhenActive: boolean):
private async readSoftwareBuildIDAndDateCode(device: Device, sendWhen: 'active' | 'immediate'):
Promise<{softwareBuildID: string, dateCode: string}> {
try {
const endpoint = device.zh.endpoints.find((e) => e.supportsInputCluster('genBasic'));
const result = await endpoint.read('genBasic', ['dateCode', 'swBuildId'], {sendWhenActive});
const result = await endpoint.read('genBasic', ['dateCode', 'swBuildId'], {sendWhen});
return {softwareBuildID: result.swBuildId, dateCode: result.dateCode};
} catch (e) {
return null;
@@ -223,17 +252,16 @@ export default class OTAUpdate extends Extension {
}
};
const from_ = await this.readSoftwareBuildIDAndDateCode(device, false);
const from_ = await this.readSoftwareBuildIDAndDateCode(device, 'immediate');
await device.definition.ota.updateToLatest(device.zh, logger, onProgress);
const to = await this.readSoftwareBuildIDAndDateCode(device, device.zh.type === 'EndDevice');
const [fromS, toS] = [stringify(from_), stringify(to)];
logger.info(`Finished update of '${device.name}'`);
this.eventBus.emitReconfigure({device});
const msg = `Finished update of '${device.name}'` +
(to ? `, from '${fromS}' to '${toS}'` : ``);
logger.info(msg);
this.removeProgressAndRemainingFromState(device);
const payload = this.getEntityPublishPayload('idle');
this.publishEntityState(device, payload);
const to = await this.readSoftwareBuildIDAndDateCode(device, 'active');
const [fromS, toS] = [stringify(from_), stringify(to)];
logger.info(`Device '${device.name}' was updated from '${fromS}' to '${toS}'`);
responseData.from = from_ ? utils.toSnakeCase(from_) : null;
responseData.to = to ? utils.toSnakeCase(to) : null;
this.eventBus.emitDevicesChanged();
+3 -3
View File
@@ -10,7 +10,7 @@ import Device from '../model/device';
import bind from 'bind-decorator';
const topicRegex = new RegExp(`^(.+?)(?:/(${utils.endpointNames.join('|')}|\\d+))?/(get|set)(?:/(.+))?`);
const propertyEndpointRegex = new RegExp(`^(.*)_(${utils.endpointNames.join('|')})$`);
const propertyEndpointRegex = new RegExp(`^(.*?)_(${utils.endpointNames.join('|')})$`);
const stateValues = ['on', 'off', 'toggle', 'open', 'close', 'stop', 'lock', 'unlock'];
const sceneConverterKeys = ['scene_store', 'scene_add', 'scene_remove', 'scene_remove_all'];
@@ -91,7 +91,7 @@ export default class Publish extends Extension {
// Only do this when the retrieve_state option is enabled for this device.
// retrieve_state == decprecated
if (re instanceof Device && result && result.hasOwnProperty('readAfterWriteTime') &&
re.settings.retrieve_state
re.options.retrieve_state
) {
setTimeout(() => converter.convertGet(target, key, meta), result.readAfterWriteTime);
}
@@ -134,7 +134,7 @@ export default class Publish extends Extension {
return;
}
const device = re instanceof Device ? re.zh : null;
const entitySettings = re.settings;
const entitySettings = re.options;
const entityState = this.state.get(re) || {};
const membersState = re instanceof Group ?
Object.fromEntries(re.zh.members.map((e) => [e.getDevice().ieeeAddr,
+4 -21
View File
@@ -91,23 +91,6 @@ export default class Receive extends Extension {
/* istanbul ignore next */
if (!data.device) return;
/**
* 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 (data.device.isXiaomi() && data.device.zh.type === 'Router' && data.groupID) {
logger.debug('Handling re-transmitted Xiaomi message');
data = {...data, device: this.zigbee.deviceByNetworkAddress(data.groupID)};
}
if (!this.shouldProcess(data)) {
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
settings.get(), true, this.publishEntityState);
@@ -145,9 +128,9 @@ export default class Receive extends Extension {
}
// Check if we have to debounce
if (data.device.settings.debounce) {
this.publishDebounce(data.device, payload, data.device.settings.debounce,
data.device.settings.debounce_ignore);
if (data.device.options.debounce) {
this.publishDebounce(data.device, payload, data.device.options.debounce,
data.device.options.debounce_ignore);
} else {
this.publishEntityState(data.device, payload);
}
@@ -158,7 +141,7 @@ export default class Receive extends Extension {
for (const converter of converters) {
try {
const converted = await converter.convert(
data.device.definition, data, publish, data.device.settings, meta);
data.device.definition, data, publish, data.device.options, meta);
if (converted) {
payload = {...payload, ...converted};
}
+8 -11
View File
@@ -5,16 +5,20 @@ import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
export default class Device {
public zh: zh.Device;
private _definition: zhc.Definition;
private _definitionModelID: string;
get ieeeAddr(): string {return this.zh.ieeeAddr;}
get ID(): string {return this.zh.ieeeAddr;}
get settings(): DeviceSettings {return {...settings.get().device_options, ...settings.getDevice(this.ieeeAddr)};}
get options(): DeviceOptions {return {...settings.get().device_options, ...settings.getDevice(this.ieeeAddr)};}
get name(): string {
return this.zh.type === 'Coordinator' ? 'Coordinator' : this.settings?.friendly_name || this.ieeeAddr;
return this.zh.type === 'Coordinator' ? 'Coordinator' : this.options?.friendly_name || this.ieeeAddr;
}
get definition(): zhc.Definition {
if (!this._definition && !this.zh.interviewing) {
// Some devices can change modelID, reconsider the definition in that case.
// https://github.com/Koenkk/zigbee-herdsman-converters/issues/3016
if (!this.zh.interviewing && (!this._definition || this._definitionModelID !== this.zh.modelID)) {
this._definition = zigbeeHerdsmanConverters.findByDevice(this.zh);
this._definitionModelID = this.zh.modelID;
}
return this._definition;
}
@@ -26,7 +30,7 @@ export default class Device {
exposes(): zhc.DefinitionExpose[] {
/* istanbul ignore if */
if (typeof this.definition.exposes == 'function') {
return this.definition.exposes(this.zh, this.settings);
return this.definition.exposes(this.zh, this.options);
} else {
return this.definition.exposes;
}
@@ -67,13 +71,6 @@ export default class Device {
return name === 'default' ? null : name;
}
isXiaomi(): boolean {
const xiaomiManufacturerID = [4151, 4447];
/* istanbul ignore next */
return this.zh.modelID !== 'lumi.router' && xiaomiManufacturerID.includes(this.zh.manufacturerID) &&
(!this.zh.manufacturerName || !this.zh.manufacturerName.startsWith('Trust'));
}
isIkeaTradfri(): boolean {return this.zh.manufacturerID === 4476;}
isDevice(): this is Device {return true;}
+13 -3
View File
@@ -4,13 +4,23 @@ import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
export default class Group {
public zh: zh.Group;
private resolveDevice: (ieeeAddr: string) => Device;
get ID(): number {return this.zh.groupID;}
get settings(): GroupSettings {return settings.getGroup(this.ID);}
get name(): string {return this.settings?.friendly_name || this.ID.toString();}
get options(): GroupOptions {return {...settings.getGroup(this.ID)};}
get name(): string {return this.options?.friendly_name || this.ID.toString();}
constructor(group: zh.Group) {
constructor(group: zh.Group, resolveDevice: (ieeeAddr: string) => Device) {
this.zh = group;
this.resolveDevice = resolveDevice;
}
hasMember(device: Device): boolean {
return !!device.zh.endpoints.find((e) => this.zh.members.includes(e));
}
membersDevices(): Device[] {
return this.zh.members.map((e) => this.resolveDevice(e.getDevice().ieeeAddr)).filter((d) => d);
}
membersDefinitions(): zhc.Definition[] {
+10 -3
View File
@@ -22,7 +22,7 @@ export default class MQTT {
const options: mqtt.IClientOptions = {
will: {
topic: `${settings.get().mqtt.base_topic}/bridge/state`,
payload: 'offline',
payload: utils.availabilityPayload('offline', settings.get()),
retain: settings.get().mqtt.force_disable_retain ? false : true,
qos: 1,
},
@@ -71,6 +71,7 @@ export default class MQTT {
this.client = mqtt.connect(mqttSettings.server, options);
// @ts-ignore https://github.com/Koenkk/zigbee2mqtt/issues/9822
this.client.stream.setMaxListeners(0);
this.eventBus.onPublishAvailability(this, () => this.publishStateOnline());
const onConnect = this.onConnect;
this.client.on('connect', async () => {
@@ -94,12 +95,18 @@ export default class MQTT {
logger.info('Connected to MQTT server');
this.subscribe(`${settings.get().mqtt.base_topic}/#`);
await this.publish('bridge/state', 'online', {retain: true, qos: 0});
await this.publishStateOnline();
}
async publishStateOnline(): Promise<void> {
await this.publish('bridge/state', utils.availabilityPayload('online', settings.get()), {retain: true, qos: 0});
}
async disconnect(): Promise<void> {
clearTimeout(this.connectionTimer);
await this.publish('bridge/state', 'offline', {retain: true, qos: 0});
await this.publish('bridge/state', utils.availabilityPayload('offline', settings.get()),
{retain: true, qos: 0});
this.eventBus.removeListeners(this);
logger.info('Disconnecting from MQTT server');
this.client.end();
}
+43 -41
View File
@@ -115,7 +115,8 @@ declare global {
options: zhc.DefinitionExpose[],
vendor: string
exposes: DefinitionExpose[] | ((device: zh.Device, options: KeyValue) => DefinitionExpose[])
configure?: (device: zh.Device, coordinatorEndpoint: zh.Endpoint, logger: Logger) => Promise<void>;
configure?: (device: zh.Device, coordinatorEndpoint: zh.Endpoint, logger: Logger,
options?: DeviceOptions) => Promise<void>;
onEvent?: (type: string, data: KeyValue, device: zh.Device, settings: KeyValue) => Promise<void>;
ota?: {
isUpdateAvailable: (device: zh.Device, logger: Logger, data?: KeyValue) => Promise<boolean>;
@@ -129,7 +130,7 @@ declare global {
}
namespace eventdata {
type DeviceRenamed = { device: Device, homeAssisantRename: boolean, from: string, to: string };
type EntityRenamed = { entity: Device | Group, homeAssisantRename: boolean, from: string, to: string };
type DeviceRemoved = { ieeeAddr: string, name: string };
type MQTTMessage = { topic: string, message: string };
type MQTTMessagePublished = { topic: string, payload: string, options: {retain: boolean, qos: number} };
@@ -164,24 +165,18 @@ declare global {
// Settings
// eslint-disable camelcase
interface Settings {
homeassistant?: boolean,
devices?: {[s: string]: DeviceSettings},
groups?: {[s: string]: GroupSettings},
passlist: string[],
blocklist: string[],
whitelist: string[],
ban: string[],
availability?: boolean | {
active?: {timeout?: number},
passive?: {timeout?: number}
homeassistant?: {
discovery_topic: string,
status_topic: string,
legacy_entity_attributes: boolean,
legacy_triggers: boolean,
},
permit_join: boolean,
frontend?: {
auth_token?: string,
host?: string,
port?: number,
url?: string,
permit_join?: boolean,
availability?: {
active: {timeout: number},
passive: {timeout: number}
},
external_converters: string[],
mqtt: {
base_topic: string,
include_device_information: boolean,
@@ -198,11 +193,14 @@ declare global {
reject_unauthorized?: boolean,
},
serial: {
disable_led?: boolean,
disable_led: boolean,
port?: string,
adapter?: 'deconz' | 'zstack' | 'ezsp' | 'zigate'
adapter?: 'deconz' | 'zstack' | 'ezsp' | 'zigate',
baudrate?: number,
rtscts?: boolean,
},
device_options: KeyValue,
passlist: string[],
blocklist: string[],
map_options: {
graphviz: {
colors: {
@@ -223,12 +221,24 @@ declare global {
},
},
},
experimental: {
output: 'json' | 'attribute' | 'attribute_and_json',
transmit_power?: number,
ota: {
update_check_interval: number,
disable_automatic_update_check: boolean,
zigbee_ota_override_index_location?: string,
ikea_ota_use_test_url?: boolean,
},
frontend?: {
auth_token?: string,
host?: string,
port?: number,
url?: string,
},
devices?: {[s: string]: DeviceOptions},
groups?: {[s: string]: GroupOptions},
device_options: KeyValue,
advanced: {
legacy_api: boolean,
legacy_availability_payload: boolean,
log_rotation: boolean,
log_symlink_current: boolean,
log_output: ('console' | 'file' | 'syslog')[],
@@ -236,7 +246,6 @@ declare global {
log_file: string,
log_level: 'debug' | 'info' | 'error' | 'warn',
log_syslog: KeyValue,
soft_reset_timeout: number,
pan_id: number | 'GENERATE',
ext_pan_id: number[],
channel: number,
@@ -248,30 +257,21 @@ declare global {
last_seen: 'disable' | 'ISO_8601' | 'ISO_8601_local' | 'epoch',
elapsed: boolean,
network_key: number[] | 'GENERATE',
report: boolean,
homeassistant_discovery_topic: string,
homeassistant_status_topic: string,
homeassistant_legacy_entity_attributes: boolean,
homeassistant_legacy_triggers: boolean,
timestamp_format: string,
baudrate?: number,
rtscts?: boolean,
ikea_ota_use_test_url?: boolean,
// below are deprecated
output: 'json' | 'attribute' | 'attribute_and_json',
transmit_power?: number,
// Everything below is deprecated
availability_timeout?: number,
availability_blocklist?: string[],
availability_passlist?: string[],
availability_blacklist?: string[],
availability_whitelist?: string[],
soft_reset_timeout: number,
report: boolean,
},
ota: {
update_check_interval: number,
disable_automatic_update_check: boolean
},
external_converters: string[],
}
interface DeviceSettings {
interface DeviceOptions {
ID?: string,
retention?: number,
availability?: boolean | {timeout: number},
@@ -285,10 +285,11 @@ declare global {
legacy?: boolean,
filtered_attributes?: string[],
friendly_name: string,
description?: string,
qos?: 0 | 1 | 2,
}
interface GroupSettings {
interface GroupOptions {
devices?: string[],
ID?: number,
optimistic?: boolean,
@@ -297,6 +298,7 @@ declare global {
homeassistant?: KeyValue,
filtered_attributes?: string[],
friendly_name: string,
description?: string,
qos?: 0 | 1 | 2,
}
}
+4
View File
@@ -11,3 +11,7 @@ declare module 'zigbee-herdsman-converters/lib/ota/tradfri' {
export function useTestURL(): void;
}
declare module 'zigbee-herdsman-converters/lib/ota/zigbeeOTA' {
export function useIndexOverride(indexFileName: string): void;
export function setDataDir(dataDir: string): void;
}
+32 -3
View File
@@ -62,7 +62,12 @@ const transportsToUse: winston.transport[] = [
winston.format.printf(/* istanbul ignore next */(info) => {
const {timestamp, level, message} = info;
const l = winstonToZ2mLevel(level as WinstonLogLevel);
const prefix = colorizer.colorize(l, `Zigbee2MQTT:${levelWithCompensatedLength[l]}`);
const plainPrefix = `Zigbee2MQTT:${levelWithCompensatedLength[l]}`;
let prefix = plainPrefix;
if (process.stdout.isTTY) {
prefix = colorizer.colorize(l, plainPrefix);
}
return `${prefix} ${timestamp.split('.')[0]}: ${message}`;
}),
),
@@ -91,8 +96,10 @@ if (settings.get().advanced.log_rotation) {
transportFileOptions.maxsize = 10000000; // 10MB
}
let fileTransport : winston.transport;
if (output.includes('file')) {
transportsToUse.push(new winston.transports.File(transportFileOptions));
fileTransport = new winston.transports.File(transportFileOptions);
transportsToUse.push(fileTransport);
}
/* istanbul ignore next */
@@ -181,6 +188,28 @@ function error(message: string): void {
logger.error(message);
}
// Workaround for https://github.com/winstonjs/winston/issues/1629.
// https://github.com/Koenkk/zigbee2mqtt/pull/10905
/* istanbul ignore next */
async function end(): Promise<void> {
logger.end();
await new Promise<void>((resolve) => {
if (!fileTransport) {
process.nextTick(resolve);
} else {
// @ts-ignore
if (fileTransport._dest) {
// @ts-ignore
fileTransport._dest.on('finish', resolve);
} else {
// @ts-ignore
fileTransport.on('open', () => fileTransport._dest.on('finish', resolve));
}
}
});
}
export default {
logOutput, warn, warning, error, info, debug, setLevel, getLevel, cleanup, addTransport, winston: logger,
logOutput, warn, warning, error, info, debug, setLevel, getLevel, cleanup, addTransport, end, winston: logger,
};
+463 -361
View File
@@ -1,14 +1,49 @@
{
"type": "object",
"properties": {
"device_options": {
"type": "object"
},
"homeassistant": {
"title": "Home Assistant integration",
"type": "boolean",
"requiresRestart": true,
"description": "Home Assistant integration (MQTT discovery)",
"default": false
"default": false,
"oneOf": [
{
"type": "boolean",
"title": "Home Assistant (simple)"
},
{
"type": "object",
"title": "Home Assistant (advanced)",
"properties": {
"legacy_triggers": {
"type": "boolean",
"title": "Home Assistant legacy triggers",
"description": "Home Assistant legacy triggers, when enabled Zigbee2mqt will send an empty 'action' or 'click' after one has been send. A 'sensor_action' and 'sensor_click' will be discoverd",
"default": true
},
"discovery_topic": {
"type": "string",
"title": "Homeassistant discovery topic",
"description": "Home Assistant discovery topic",
"requiresRestart": true,
"examples": ["homeassistant"]
},
"legacy_entity_attributes": {
"type": "boolean",
"title": "Home Assistant legacy entity attributes",
"description": "Home Assistant legacy entity attributes, when enabled Zigbee2MQTT will add state attributes to each entity, additional to the separate entities and devices it already creates",
"default": true
},
"status_topic": {
"type": "string",
"title": "Home Assistant status topic",
"description": "Home Assistant status topic",
"requiresRestart": true,
"examples": ["homeassistant/status"]
}
}
}
]
},
"permit_join": {
"type": "boolean",
@@ -16,25 +51,15 @@
"title": "Permit join",
"description": "Allow new devices to join (re-applied at restart)"
},
"external_converters": {
"type": "array",
"title": "External converters",
"description": "You can define external converters to e.g. add support for a DiY device",
"requiresRestart": true,
"items": {
"type": "string"
},
"examples": ["DIYRuZ_FreePad.js"]
},
"availability": {
"oneOf": [
{
"type": "boolean",
"title": "Availability (boolean)"
"title": "Availability (simple)"
},
{
"type": "object",
"title": "Availability (object)",
"title": "Availability (advanced)",
"properties": {
"active": {
"type": "object",
@@ -73,6 +98,16 @@
"requiresRestart": true,
"description": "Checks whether devices are online/offline"
},
"external_converters": {
"type": "array",
"title": "External converters",
"description": "You can define external converters to e.g. add support for a DiY device",
"requiresRestart": true,
"items": {
"type": "string"
},
"examples": ["DIYRuZ_FreePad.js"]
},
"mqtt": {
"type": "object",
"title": "MQTT",
@@ -80,6 +115,7 @@
"base_topic": {
"type": "string",
"title": "Base topic",
"default": "zigbee2mqtt",
"requiresRestart": true,
"description": "MQTT base topic for Zigbee2MQTT MQTT messages",
"examples": ["zigbee2mqtt"]
@@ -169,7 +205,7 @@
"default": false
}
},
"required": ["base_topic", "server"]
"required": ["server"]
},
"serial": {
"type": "object",
@@ -196,6 +232,19 @@
"default": "auto",
"requiresRestart": true,
"description": "Adapter type, not needed unless you are experiencing problems"
},
"baudrate": {
"type": "number",
"title": "Baudrate",
"requiresRestart": true,
"description": "Baud rate speed for serial port, this can be anything firmware support but default is 115200 for Z-Stack and EZSP, 38400 for Deconz, however note that some EZSP firmware need 57600",
"examples": [38400, 57600, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS",
"requiresRestart": true,
"description": "RTS / CTS Hardware Flow Control for serial port"
}
}
},
@@ -217,331 +266,6 @@
"type": "string"
}
},
"whitelist": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Whitelist (deprecated, use passlist)",
"items": {
"type": "string"
}
},
"ban": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Ban (deprecated, use blocklist)",
"items": {
"type": "string"
}
},
"experimental": {
"type": "object",
"title": "Experimental",
"properties": {
"transmit_power": {
"type": ["number", "null"],
"title": "Transmit power",
"requiresRestart": true,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
},
"output": {
"type": "string",
"enum": ["attribute_and_json", "attribute", "json"],
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)"
}
}
},
"advanced": {
"type": "object",
"title": "Advanced",
"properties": {
"legacy_api": {
"type": "boolean",
"title": "Legacy API",
"requiresRestart": true,
"description": "Disables the legacy api (false = disable)",
"default": true
},
"pan_id": {
"oneOf": [{
"type": "string",
"title": "Pan ID (string)"
},
{
"type": "number",
"title": "Pan ID (number)"
}
],
"title": "Pan ID",
"requiresRestart": true,
"description": "ZigBee pan ID, changing requires repairing all devices!"
},
"ext_pan_id": {
"type": "array",
"items": {
"type": "number"
},
"title": "Ext Pan ID",
"requiresRestart": true,
"description": "Zigbee extended pan ID, changing requires repairing all devices!"
},
"channel": {
"type": "number",
"minimum": 11,
"maximum": 26,
"default": 11,
"title": "ZigBee channel",
"requiresRestart": true,
"description": "Zigbee channel, changing requires repairing all devices! (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)",
"examples": [15, 20, 25]
},
"cache_state": {
"type": "boolean",
"title": "Cache state",
"description": "MQTT message payload will contain all attributes, not only changed ones. Has to be true when integrating via Home Assistant",
"default": true
},
"cache_state_persistent": {
"type": "boolean",
"title": "Persist cache state",
"description": "Persist cached state, only used when cache_state: true",
"default": true
},
"cache_state_send_on_startup": {
"type": "boolean",
"title": "Send cached state on startup",
"description": "Send cached state on startup, only used when cache_state: true",
"default": true
},
"log_rotation": {
"type": "boolean",
"title": "Log rotation",
"requiresRestart": true,
"description": "Log rotation",
"default": true
},
"log_symlink_current": {
"type": "boolean",
"title": "Log symlink current",
"requiresRestart": true,
"description": "Create symlink to current logs in the log directory",
"default": false
},
"log_level": {
"type": "string",
"enum": ["info", "warn", "error", "debug"],
"title": "Log level",
"description": "Logging level",
"default": "info"
},
"log_output": {
"type": "array",
"requiresRestart": true,
"items": {
"type": "string",
"enum": ["console", "file", "syslog"]
},
"title": "Log output",
"description": "Output location of the log, leave empty to supress logging"
},
"log_directory": {
"type": "string",
"title": "Log directory",
"requiresRestart": true,
"description": "Location of log directory",
"examples": ["data/log/%TIMESTAMP%"]
},
"log_file": {
"type": "string",
"title": "Log file",
"requiresRestart": true,
"description": "Log file name, can also contain timestamp",
"examples": ["zigbee2mqtt_%TIMESTAMP%.log"],
"default": "log.txt"
},
"baudrate": {
"type": "number",
"title": "Baudrate",
"requiresRestart": true,
"description": "Baud rate speed for serial port, this can be anything firmware support but default is 115200 for Z-Stack and EZSP, 38400 for Deconz, however note that some EZSP firmware need 57600",
"examples": [38400, 57600, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS",
"requiresRestart": true,
"description": "RTS / CTS Hardware Flow Control for serial port"
},
"soft_reset_timeout": {
"type": "number",
"minimum": 0,
"requiresRestart": true,
"title": "Soft reset timeout (deprecated)",
"description": "Soft reset ZNP after timeout",
"readOnly": true
},
"network_key": {
"oneOf": [{
"type": "string",
"title": "Network key(string)"
},
{
"type": "array",
"items": {
"type": "number"
},
"title": "Network key(array)"
}
],
"title": "Network key",
"requiresRestart": true,
"description": "Network encryption key, changing requires repairing all devices!"
},
"last_seen": {
"type": "string",
"enum": ["disable", "ISO_8601", "ISO_8601_local", "epoch"],
"title": "Last seen",
"description": "Add a last_seen attribute to MQTT messages, contains date/time of last Zigbee message",
"default": "disable"
},
"elapsed": {
"type": "boolean",
"title": "Elapsed",
"description": "Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg",
"default": false
},
"report": {
"type": "boolean",
"title": "Reporting",
"requiresRestart": true,
"readOnly": true,
"description": "Enables report feature (deprecated)"
},
"homeassistant_discovery_topic": {
"type": "string",
"title": "Homeassistant discovery topic",
"description": "Home Assistant discovery topic",
"requiresRestart": true,
"examples": ["homeassistant"]
},
"homeassistant_legacy_entity_attributes": {
"type": "boolean",
"title": "Home Assistant legacy entity attributes",
"description": "Home Assistant legacy entity attributes, when enabled Zigbee2MQTT will add state attributes to each entity, additional to the separate entities and devices it already creates",
"default": true
},
"homeassistant_status_topic": {
"type": "string",
"title": "Home Assistant status topic",
"description": "Home Assistant status topic",
"requiresRestart": true,
"examples": ["homeassistant/status"]
},
"timestamp_format": {
"type": "string",
"title": "Timestamp format",
"requiresRestart": true,
"description": "Log timestamp format",
"examples": ["YYYY-MM-DD HH:mm:ss"]
},
"adapter_concurrent": {
"title": "Adapter concurrency",
"requiresRestart": true,
"type": ["number", "null"],
"description": "Adapter concurrency (e.g. 2 for CC2531 or 16 for CC26X2R1) (default: null, uses recommended value)"
},
"adapter_delay": {
"type": ["number", "null"],
"requiresRestart": true,
"title": "Adapter delay",
"description": "Adapter delay"
},
"ikea_ota_use_test_url": {
"type": "boolean",
"title": "IKEA TRADFRI OTA use test url",
"requiresRestart": true,
"description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation",
"default": false
},
"homeassistant_legacy_triggers": {
"type": "boolean",
"title": "Home Assistant legacy triggers",
"description": "Home Assistant legacy triggers, when enabled Zigbee2mqt will send an empty 'action' or 'click' after one has been send. A 'sensor_action' and 'sensor_click' will be discoverd",
"default": true
},
"log_syslog": {
"type": "object",
"title": "syslog",
"properties": {
"host": {
"type": "string",
"title": "Host",
"description": "The host running syslogd, defaults to localhost.",
"default": "localhost"
},
"port": {
"type": "number",
"title": "Port",
"description": "The port on the host that syslog is running on, defaults to syslogd's default port.",
"default": 123
},
"protocol": {
"type": "string",
"title": "Protocol",
"description": "The network protocol to log over (e.g. tcp4, udp4, tls4, unix, unix-connect, etc).",
"default": "tcp4",
"examples": [
"udp4",
"tls4",
"unix",
"unix-connect"
]
},
"path": {
"type": "string",
"title": "Path",
"description": "The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).",
"default": "/dev/log",
"examples": [
"/var/run/syslog"
]
},
"pid": {
"type": "string",
"title": "PID",
"description": "PID of the process that log messages are coming from (Default process.pid).",
"default": "process.pid"
},
"localhost": {
"type": "string",
"title": "Localhost",
"description": "Host to indicate that log messages are coming from (Default: localhost).",
"default": "localhost"
},
"type": {
"type": "string",
"title": "Type",
"description": "The type of the syslog protocol to use (Default: BSD, also valid: 5424).",
"default": "5424"
},
"app_name": {
"type": "string",
"title": "Localhost",
"description": "The name of the application (Default: Zigbee2MQTT).",
"default": "Zigbee2MQTT"
},
"eol": {
"type": "string",
"title": "eol",
"description": "The end of line character to be added to the end of the message (Default: Message without modifications).",
"default": "/n"
}
}
}
}
},
"map_options": {
"type": "object",
"title": "Networkmap",
@@ -612,9 +336,65 @@
"title": "Disable automatic update check",
"description": "Zigbee devices may request a firmware update, and do so frequently, causing Zigbee2MQTT to reach out to third party servers. If you disable these device initiated checks, you can still initiate a firmware update check manually.",
"default": false
},
"ikea_ota_use_test_url": {
"type": "boolean",
"title": "IKEA TRADFRI OTA use test url",
"requiresRestart": true,
"description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation",
"default": false
},
"zigbee_ota_override_index_location": {
"type": "string",
"title": "OTA index override file name",
"requiresRestart": true,
"description": "Location of override OTA index file",
"examples": ["index.json"]
}
}
},
"frontend": {
"oneOf": [
{
"type": "boolean",
"title": "Frontend (simple)"
},
{
"type": "object",
"title": "Frontend (advanced)",
"properties": {
"port": {
"type": "number",
"title": "Port",
"description": "Frontend binding port",
"default": 8080,
"requiresRestart": true
},
"host": {
"type": "string",
"title": "Bind host",
"description": "Frontend binding host",
"default": "0.0.0.0",
"requiresRestart": true
},
"auth_token": {
"type": ["string", "null"],
"title": "Auth token",
"description": "Enables authentication, disabled by default",
"requiresRestart": true
},
"url": {
"type": ["string", "null"],
"title": "URL",
"description": "URL on which the frontend can be reached, currently only used for the Home Assistant device configuration page",
"requiresRestart": true
}
}
}
],
"title": "Frontend",
"requiresRestart": true
},
"devices": {
"type": "object",
"propertyNames": {
@@ -637,31 +417,353 @@
}
}
},
"frontend": {
"device_options": {
"type": "object",
"title": "Frontend",
"title": "Options that are applied to all devices"
},
"advanced": {
"type": "object",
"title": "Advanced",
"properties": {
"port": {
"type": "number",
"title": "Port",
"description": "Frontend binding port",
"default": 8080,
"requiresRestart": true
"legacy_api": {
"type": "boolean",
"title": "Legacy API",
"requiresRestart": true,
"description": "Disables the legacy api (false = disable)",
"default": true
},
"host": {
"legacy_availability_payload": {
"type": "boolean",
"title": "Legacy availability payload",
"requiresRestart": true,
"description": "Payload to be used for device availabilty and bridge/state topics. true = text, false = JSON",
"default": true
},
"log_rotation": {
"type": "boolean",
"title": "Log rotation",
"requiresRestart": true,
"description": "Log rotation",
"default": true
},
"log_symlink_current": {
"type": "boolean",
"title": "Log symlink current",
"requiresRestart": true,
"description": "Create symlink to current logs in the log directory",
"default": false
},
"log_output": {
"type": "array",
"requiresRestart": true,
"items": {
"type": "string",
"enum": ["console", "file", "syslog"]
},
"title": "Log output",
"description": "Output location of the log, leave empty to suppress logging"
},
"log_directory": {
"type": "string",
"title": "Bind host",
"description": "Frontend binding host",
"default": "0.0.0.0",
"requiresRestart": true
"title": "Log directory",
"requiresRestart": true,
"description": "Location of log directory",
"examples": ["data/log/%TIMESTAMP%"]
},
"auth_token": {
"type": ["string", "null"],
"title": "Auth token",
"description": "Enables authentication, disabled by default",
"requiresRestart": true
"log_file": {
"type": "string",
"title": "Log file",
"requiresRestart": true,
"description": "Log file name, can also contain timestamp",
"examples": ["zigbee2mqtt_%TIMESTAMP%.log"],
"default": "log.txt"
},
"log_level": {
"type": "string",
"enum": ["info", "warn", "error", "debug"],
"title": "Log level",
"description": "Logging level",
"default": "info"
},
"log_syslog": {
"type": "object",
"title": "syslog",
"properties": {
"host": {
"type": "string",
"title": "Host",
"description": "The host running syslogd, defaults to localhost.",
"default": "localhost"
},
"port": {
"type": "number",
"title": "Port",
"description": "The port on the host that syslog is running on, defaults to syslogd's default port.",
"default": 123
},
"protocol": {
"type": "string",
"title": "Protocol",
"description": "The network protocol to log over (e.g. tcp4, udp4, tls4, unix, unix-connect, etc).",
"default": "tcp4",
"examples": [
"udp4",
"tls4",
"unix",
"unix-connect"
]
},
"path": {
"type": "string",
"title": "Path",
"description": "The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).",
"default": "/dev/log",
"examples": [
"/var/run/syslog"
]
},
"pid": {
"type": "string",
"title": "PID",
"description": "PID of the process that log messages are coming from (Default process.pid).",
"default": "process.pid"
},
"localhost": {
"type": "string",
"title": "Localhost",
"description": "Host to indicate that log messages are coming from (Default: localhost).",
"default": "localhost"
},
"type": {
"type": "string",
"title": "Type",
"description": "The type of the syslog protocol to use (Default: BSD, also valid: 5424).",
"default": "5424"
},
"app_name": {
"type": "string",
"title": "Localhost",
"description": "The name of the application (Default: Zigbee2MQTT).",
"default": "Zigbee2MQTT"
},
"eol": {
"type": "string",
"title": "eol",
"description": "The end of line character to be added to the end of the message (Default: Message without modifications).",
"default": "/n"
}
}
},
"pan_id": {
"oneOf": [{
"type": "string",
"title": "Pan ID (string)"
},
{
"type": "number",
"title": "Pan ID (number)"
}
],
"title": "Pan ID",
"requiresRestart": true,
"description": "ZigBee pan ID, changing requires repairing all devices!"
},
"ext_pan_id": {
"type": "array",
"items": {
"type": "number"
},
"title": "Ext Pan ID",
"requiresRestart": true,
"description": "Zigbee extended pan ID, changing requires repairing all devices!"
},
"channel": {
"type": "number",
"minimum": 11,
"maximum": 26,
"default": 11,
"title": "ZigBee channel",
"requiresRestart": true,
"description": "Zigbee channel, changing requires repairing all devices! (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)",
"examples": [15, 20, 25]
},
"adapter_concurrent": {
"title": "Adapter concurrency",
"requiresRestart": true,
"type": ["number", "null"],
"description": "Adapter concurrency (e.g. 2 for CC2531 or 16 for CC26X2R1) (default: null, uses recommended value)"
},
"adapter_delay": {
"type": ["number", "null"],
"requiresRestart": true,
"title": "Adapter delay",
"description": "Adapter delay"
},
"cache_state": {
"type": "boolean",
"title": "Cache state",
"description": "MQTT message payload will contain all attributes, not only changed ones. Has to be true when integrating via Home Assistant",
"default": true
},
"cache_state_persistent": {
"type": "boolean",
"title": "Persist cache state",
"description": "Persist cached state, only used when cache_state: true",
"default": true
},
"cache_state_send_on_startup": {
"type": "boolean",
"title": "Send cached state on startup",
"description": "Send cached state on startup, only used when cache_state: true",
"default": true
},
"last_seen": {
"type": "string",
"enum": ["disable", "ISO_8601", "ISO_8601_local", "epoch"],
"title": "Last seen",
"description": "Add a last_seen attribute to MQTT messages, contains date/time of last Zigbee message",
"default": "disable"
},
"elapsed": {
"type": "boolean",
"title": "Elapsed",
"description": "Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg",
"default": false
},
"network_key": {
"oneOf": [{
"type": "string",
"title": "Network key(string)"
},
{
"type": "array",
"items": {
"type": "number"
},
"title": "Network key(array)"
}
],
"title": "Network key",
"requiresRestart": true,
"description": "Network encryption key, changing requires repairing all devices!"
},
"timestamp_format": {
"type": "string",
"title": "Timestamp format",
"requiresRestart": true,
"description": "Log timestamp format",
"examples": ["YYYY-MM-DD HH:mm:ss"]
},
"transmit_power": {
"type": ["number", "null"],
"title": "Transmit power",
"requiresRestart": true,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
},
"output": {
"type": "string",
"enum": ["attribute_and_json", "attribute", "json"],
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)"
},
"homeassistant_discovery_topic": {
"type": "string",
"title": "Homeassistant discovery topic",
"description": "Home Assistant discovery topic",
"requiresRestart": true,
"examples": ["homeassistant"]
},
"homeassistant_legacy_entity_attributes": {
"type": "boolean",
"title": "Home Assistant legacy entity attributes",
"description": "Home Assistant legacy entity attributes, when enabled Zigbee2MQTT will add state attributes to each entity, additional to the separate entities and devices it already creates",
"default": true
},
"homeassistant_status_topic": {
"type": "string",
"title": "Home Assistant status topic",
"description": "Home Assistant status topic",
"requiresRestart": true,
"examples": ["homeassistant/status"]
},
"homeassistant_legacy_triggers": {
"type": "boolean",
"title": "Home Assistant legacy triggers",
"description": "Home Assistant legacy triggers, when enabled Zigbee2mqt will send an empty 'action' or 'click' after one has been send. A 'sensor_action' and 'sensor_click' will be discovered",
"default": true
},
"soft_reset_timeout": {
"type": "number",
"minimum": 0,
"requiresRestart": true,
"title": "Soft reset timeout (deprecated)",
"description": "Soft reset ZNP after timeout",
"readOnly": true
},
"report": {
"type": "boolean",
"title": "Reporting",
"requiresRestart": true,
"readOnly": true,
"description": "Enables report feature (deprecated)"
},
"baudrate": {
"type": "number",
"title": "Baudrate (deprecated)",
"requiresRestart": true,
"description": "Baud rate speed for serial port, this can be anything firmware support but default is 115200 for Z-Stack and EZSP, 38400 for Deconz, however note that some EZSP firmware need 57600",
"examples": [38400, 57600, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS (deprecated)",
"requiresRestart": true,
"description": "RTS / CTS Hardware Flow Control for serial port"
},
"ikea_ota_use_test_url": {
"type": "boolean",
"title": "IKEA TRADFRI OTA use test url (deprecated)",
"requiresRestart": true,
"description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation",
"default": false
}
}
},
"experimental": {
"type": "object",
"title": "Experimental (deprecated)",
"properties": {
"transmit_power": {
"type": ["number", "null"],
"title": "Transmit power",
"requiresRestart": true,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
},
"output": {
"type": "string",
"enum": ["attribute_and_json", "attribute", "json"],
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)"
}
}
},
"whitelist": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Whitelist (deprecated, use passlist)",
"items": {
"type": "string"
}
},
"ban": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Ban (deprecated, use blocklist)",
"items": {
"type": "string"
}
}
},
"required": ["mqtt"],
+137 -133
View File
@@ -5,33 +5,46 @@ import path from 'path';
import yaml from './yaml';
import Ajv from 'ajv';
import schemaJson from './settings.schema.json';
export const schema = schemaJson;
export let schema = schemaJson;
// @ts-ignore
schema = {};
objectAssignDeep(schema, schemaJson);
// Remove legacy settings from schema
{
delete schema.properties.advanced.properties.homeassistant_discovery_topic;
delete schema.properties.advanced.properties.homeassistant_legacy_entity_attributes;
delete schema.properties.advanced.properties.homeassistant_legacy_triggers;
delete schema.properties.advanced.properties.homeassistant_status_topic;
delete schema.properties.advanced.properties.soft_reset_timeout;
delete schema.properties.advanced.properties.report;
delete schema.properties.advanced.properties.baudrate;
delete schema.properties.advanced.properties.rtscts;
delete schema.properties.advanced.properties.ikea_ota_use_test_url;
delete schema.properties.experimental;
delete schemaJson.properties.whitelist;
delete schemaJson.properties.ban;
}
// DEPRECATED ZIGBEE2MQTT_CONFIG: https://github.com/Koenkk/zigbee2mqtt/issues/4697
const file = process.env.ZIGBEE2MQTT_CONFIG ?? data.joinPath('configuration.yaml');
const ajvSetting = new Ajv({allErrors: true}).addKeyword('requiresRestart').compile(schema);
const ajvSetting = new Ajv({allErrors: true}).addKeyword('requiresRestart').compile(schemaJson);
const ajvRestartRequired = new Ajv({allErrors: true})
.addKeyword({keyword: 'requiresRestart', validate: (schema: unknown) => !schema}).compile(schema);
.addKeyword({keyword: 'requiresRestart', validate: (s: unknown) => !s}).compile(schemaJson);
const defaults: RecursivePartial<Settings> = {
passlist: [],
blocklist: [],
// Deprecated: use block/passlist
whitelist: [],
ban: [],
permit_join: false,
external_converters: [],
mqtt: {
base_topic: 'zigbee2mqtt',
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,
},
device_options: {},
passlist: [],
blocklist: [],
map_options: {
graphviz: {
colors: {
@@ -52,12 +65,14 @@ const defaults: RecursivePartial<Settings> = {
},
},
},
experimental: {
// json or attribute or attribute_and_json
output: 'json',
ota: {
update_check_interval: 24 * 60,
disable_automatic_update_check: false,
},
device_options: {},
advanced: {
legacy_api: true,
legacy_availability_payload: true,
log_rotation: true,
log_symlink_current: false,
log_output: ['console', 'file'],
@@ -65,106 +80,113 @@ const defaults: RecursivePartial<Settings> = {
log_file: 'log.txt',
log_level: /* istanbul ignore next */ process.env.DEBUG ? 'debug' : 'info',
log_syslog: {},
soft_reset_timeout: 0,
pan_id: 0x1a62,
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_blocklist: [],
availability_passlist: [],
// Deprecated, use block/passlist
availability_blacklist: [],
availability_whitelist: [],
/**
* 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.
* advanced.cache_state in configuration.yaml allows to configure this.
* https://www.zigbee2mqtt.io/guide/configuration/
*/
cache_state: true,
cache_state_persistent: true,
cache_state_send_on_startup: true,
/**
* Add a last_seen attribute to mqtt messages, contains date/time of zigbee message arrival
* "ISO_8601": ISO 8601 format
* "ISO_8601_local": Local ISO 8601 format (instead of UTC-based)
* "epoch": milliseconds elapsed since the UNIX epoch
* "disable": no last_seen attribute (default)
*/
last_seen: 'disable',
// Optional: Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg
elapsed: false,
/**
* https://github.com/Koenkk/zigbee2mqtt/issues/685#issuecomment-449112250
*
* Network key will serve as the encryption key of your network.
* Changing this will require you to repair your devices.
*/
network_key: [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13],
/**
* Enables reporting feature
*/
report: false,
/**
* Home Assistant discovery topic
*/
homeassistant_discovery_topic: 'homeassistant',
/**
* Home Assistant status topic
*/
homeassistant_status_topic: 'hass/status',
/**
* Home Assistant legacy entity attributes, when enabled:
* Zigbee2MQTT will send additional states as attributes with each entity.
* For example, A temperature & humidity sensor will have 2 entities for
* the temperature and humidity, with this setting enabled both entities
* will also have an temperature and humidity attribute.
*/
homeassistant_legacy_entity_attributes: true,
/**
* Home Assistant legacy triggers, when enabled:
* - Zigbee2mqt will send an empty 'action' or 'click' after one has been send
* - A 'sensor_action' and 'sensor_click' will be discoverd
*/
homeassistant_legacy_triggers: true,
/**
* Configurable timestampFormat
* https://github.com/Koenkk/zigbee2mqtt/commit/44db557a0c83f419d66755d14e460cd78bd6204e
*/
timestamp_format: 'YYYY-MM-DD HH:mm:ss',
output: 'json',
// Everything below is deprecated
availability_blocklist: [],
availability_passlist: [],
availability_blacklist: [],
availability_whitelist: [],
soft_reset_timeout: 0,
report: false,
},
ota: {
/**
* Minimal time delta in minutes between polling third party server for potential firmware updates
*/
update_check_interval: 24 * 60,
/**
* Completely disallow Zigbee devices to initiate a search for a potential firmware update.
* If set to true, only a user-initiated update search will be possible.
*/
disable_automatic_update_check: false,
},
external_converters: [],
};
let _settings: Partial<Settings>;
let _settingsWithDefaults: Settings;
function loadSettingsWithDefaults(): void {
_settingsWithDefaults = objectAssignDeep({}, defaults, getInternalSettings()) as Settings;
if (!_settingsWithDefaults.devices) {
_settingsWithDefaults.devices = {};
}
if (!_settingsWithDefaults.groups) {
_settingsWithDefaults.groups = {};
}
if (_settingsWithDefaults.homeassistant) {
const defaults = {discovery_topic: 'homeassistant', status_topic: 'hass/status',
legacy_entity_attributes: true, legacy_triggers: true};
const sLegacy = {};
if (_settingsWithDefaults.advanced) {
for (const key of ['homeassistant_legacy_triggers', 'homeassistant_discovery_topic',
'homeassistant_legacy_entity_attributes', 'homeassistant_status_topic']) {
// @ts-ignore
if (_settingsWithDefaults.advanced[key] !== undefined) {
// @ts-ignore
sLegacy[key.replace('homeassistant_', '')] = _settingsWithDefaults.advanced[key];
}
}
}
const s = typeof _settingsWithDefaults.homeassistant === 'object' ? _settingsWithDefaults.homeassistant : {};
// @ts-ignore
_settingsWithDefaults.homeassistant = {};
objectAssignDeep(_settingsWithDefaults.homeassistant, defaults, sLegacy, s);
}
if (_settingsWithDefaults.availability || _settingsWithDefaults.advanced?.availability_timeout) {
const defaults = {};
const s = typeof _settingsWithDefaults.availability === 'object' ? _settingsWithDefaults.availability : {};
// @ts-ignore
_settingsWithDefaults.availability = {};
objectAssignDeep(_settingsWithDefaults.availability, defaults, s);
}
if (_settingsWithDefaults.frontend) {
const defaults = {port: 8080, auth_token: false, host: '0.0.0.0'};
const s = typeof _settingsWithDefaults.frontend === 'object' ? _settingsWithDefaults.frontend : {};
// @ts-ignore
_settingsWithDefaults.frontend = {};
objectAssignDeep(_settingsWithDefaults.frontend, defaults, s);
}
if (_settings.advanced?.hasOwnProperty('baudrate') && _settings.serial?.baudrate == null) {
// @ts-ignore
_settingsWithDefaults.serial.baudrate = _settings.advanced.baudrate;
}
if (_settings.advanced?.hasOwnProperty('rtscts') && _settings.serial?.rtscts == null) {
// @ts-ignore
_settingsWithDefaults.serial.rtscts = _settings.advanced.rtscts;
}
if (_settings.advanced?.hasOwnProperty('ikea_ota_use_test_url') && _settings.ota?.ikea_ota_use_test_url == null) {
// @ts-ignore
_settingsWithDefaults.ota.ikea_ota_use_test_url = _settings.advanced.ikea_ota_use_test_url;
}
// @ts-ignore
if (_settings.experimental?.hasOwnProperty('transmit_power') && _settings.advanced?.transmit_power == null) {
// @ts-ignore
_settingsWithDefaults.advanced.transmit_power = _settings.experimental.transmit_power;
}
// @ts-ignore
if (_settings.experimental?.hasOwnProperty('output') && _settings.advanced?.output == null) {
// @ts-ignore
_settingsWithDefaults.advanced.output = _settings.experimental.output;
}
// @ts-ignore
_settingsWithDefaults.ban && _settingsWithDefaults.blocklist.push(..._settingsWithDefaults.ban);
// @ts-ignore
_settingsWithDefaults.whitelist && _settingsWithDefaults.passlist.push(..._settingsWithDefaults.whitelist);
}
function write(): void {
const settings = getInternalSettings();
const toWrite: KeyValue = objectAssignDeep({}, settings);
@@ -213,7 +235,7 @@ function write(): void {
yaml.writeIfChanged(file, toWrite);
_settings = read();
_settingsWithDefaults = objectAssignDeep({}, defaults, getInternalSettings()) as Settings;
loadSettingsWithDefaults();
}
export function validate(): string[] {
@@ -247,7 +269,7 @@ export function validate(): string[] {
// Verify that all friendly names are unique
const names: string[] = [];
const check = (e: DeviceSettings | GroupSettings): void => {
const check = (e: DeviceOptions | GroupOptions): void => {
if (names.includes(e.friendly_name)) errors.push(`Duplicate friendly_name '${e.friendly_name}' found`);
errors.push(...utils.validateFriendlyName(e.friendly_name));
names.push(e.friendly_name);
@@ -376,7 +398,7 @@ function applyEnvironmentVariables(settings: Partial<Settings>): void {
}
});
};
iterate(schema.properties, []);
iterate(schemaJson.properties, []);
}
function getInternalSettings(): Partial<Settings> {
@@ -390,15 +412,7 @@ function getInternalSettings(): Partial<Settings> {
export function get(): Settings {
if (!_settingsWithDefaults) {
_settingsWithDefaults = objectAssignDeep({}, defaults, getInternalSettings()) as Settings;
}
if (!_settingsWithDefaults.devices) {
_settingsWithDefaults.devices = {};
}
if (!_settingsWithDefaults.groups) {
_settingsWithDefaults.groups = {};
loadSettingsWithDefaults();
}
return _settingsWithDefaults;
@@ -443,7 +457,7 @@ export function apply(newSettings: Record<string, unknown>): boolean {
return restartRequired;
}
export function getGroup(IDorName: string | number): GroupSettings {
export function getGroup(IDorName: string | number): GroupOptions {
const settings = get();
const byID = settings.groups[IDorName];
if (byID) {
@@ -459,14 +473,14 @@ export function getGroup(IDorName: string | number): GroupSettings {
return null;
}
export function getGroups(): GroupSettings[] {
export function getGroups(): GroupOptions[] {
const settings = get();
return Object.entries(settings.groups).map(([ID, group]) => {
return {devices: [], ...group, ID: Number(ID)};
});
}
function getGroupThrowIfNotExists(IDorName: string): GroupSettings {
function getGroupThrowIfNotExists(IDorName: string): GroupOptions {
const group = getGroup(IDorName);
if (!group) {
throw new Error(`Group '${IDorName}' does not exist`);
@@ -475,7 +489,7 @@ function getGroupThrowIfNotExists(IDorName: string): GroupSettings {
return group;
}
export function getDevice(IDorName: string): DeviceSettings {
export function getDevice(IDorName: string): DeviceOptions {
const settings = get();
const byID = settings.devices[IDorName];
if (byID) {
@@ -491,7 +505,7 @@ export function getDevice(IDorName: string): DeviceSettings {
return null;
}
function getDeviceThrowIfNotExists(IDorName: string): DeviceSettings {
function getDeviceThrowIfNotExists(IDorName: string): DeviceOptions {
const device = getDevice(IDorName);
if (!device) {
throw new Error(`Device '${IDorName}' does not exist`);
@@ -500,7 +514,7 @@ function getDeviceThrowIfNotExists(IDorName: string): DeviceSettings {
return device;
}
export function addDevice(ID: string): DeviceSettings {
export function addDevice(ID: string): DeviceOptions {
if (getDevice(ID)) {
throw new Error(`Device '${ID}' already exists`);
}
@@ -516,17 +530,17 @@ export function addDevice(ID: string): DeviceSettings {
return getDevice(ID);
}
export function whitelistDevice(ID: string): void {
export function addDeviceToPasslist(ID: string): void {
const settings = getInternalSettings();
if (!settings.whitelist) {
settings.whitelist = [];
if (!settings.passlist) {
settings.passlist = [];
}
if (settings.whitelist.includes(ID)) {
throw new Error(`Device '${ID}' already whitelisted`);
if (settings.passlist.includes(ID)) {
throw new Error(`Device '${ID}' already in passlist`);
}
settings.whitelist.push(ID);
settings.passlist.push(ID);
write();
}
@@ -540,16 +554,6 @@ export function blockDevice(ID: string): void {
write();
}
export function banDevice(ID: string): void {
const settings = getInternalSettings();
if (!settings.ban) {
settings.ban = [];
}
settings.ban.push(ID);
write();
}
export function removeDevice(IDorName: string): void {
const device = getDeviceThrowIfNotExists(IDorName);
const settings = getInternalSettings();
@@ -567,7 +571,7 @@ export function removeDevice(IDorName: string): void {
write();
}
export function addGroup(name: string, ID?: string): GroupSettings {
export function addGroup(name: string, ID?: string): GroupOptions {
utils.validateFriendlyName(name, true);
if (getGroup(name) || getDevice(name)) {
throw new Error(`friendly_name '${name}' is already in use`);
+15 -7
View File
@@ -38,7 +38,7 @@ const endpointNames = [
'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', 'meter', 'wifi',
'heat', 'cool', 'water', 'meter', 'wifi', 'no_occupancy_since',
];
function capitalize(s: string): string {
@@ -245,9 +245,13 @@ function sanitizeImageParameter(parameter: string): string {
return sanitized;
}
function isAvailabilityEnabledForDevice(device: Device, settings: Settings): boolean {
if (device.settings.hasOwnProperty('availability')) {
return !!device.settings.availability;
function isAvailabilityEnabledForEntity(entity: Device | Group, settings: Settings): boolean {
if (entity.isGroup()) {
return !entity.membersDevices().map((d) => isAvailabilityEnabledForEntity(d, settings)).includes(false);
}
if (entity.options.hasOwnProperty('availability')) {
return !!entity.options.availability;
}
// availability_timeout = deprecated
@@ -256,11 +260,11 @@ function isAvailabilityEnabledForDevice(device: Device, settings: Settings): boo
const passlist = settings.advanced.availability_passlist.concat(settings.advanced.availability_whitelist);
if (passlist.length > 0) {
return passlist.includes(device.name) || passlist.includes(device.ieeeAddr);
return passlist.includes(entity.name) || passlist.includes(entity.ieeeAddr);
}
const blocklist = settings.advanced.availability_blacklist.concat(settings.advanced.availability_blocklist);
return !blocklist.includes(device.name) && !blocklist.includes(device.ieeeAddr);
return !blocklist.includes(entity.name) && !blocklist.includes(entity.ieeeAddr);
}
const entityIDRegex = new RegExp(`^(.+?)(?:/(${endpointNames.join('|')}|\\d+))?$`);
@@ -277,6 +281,10 @@ function isZHGroup(obj: unknown): obj is zh.Group {
return obj.constructor.name.toLowerCase() === 'group';
}
function availabilityPayload(state: 'online' | 'offline', settings: Settings): string {
return settings.advanced.legacy_availability_payload ? state : JSON.stringify({state});
}
const hours = (hours: number): number => 1000 * 60 * 60 * hours;
const minutes = (minutes: number): number => 1000 * 60 * minutes;
const seconds = (seconds: number): number => 1000 * seconds;
@@ -302,5 +310,5 @@ export default {
equalsPartial, getObjectProperty, getResponse, parseJSON, loadModuleFromText, loadModuleFromFile,
getExternalConvertersDefinitions, removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase,
parseEntityID, isEndpoint, isZHGroup, hours, minutes, seconds, validateFriendlyName, sleep,
sanitizeImageParameter, isAvailabilityEnabledForDevice, publishLastSeen,
sanitizeImageParameter, isAvailabilityEnabledForEntity, publishLastSeen, availabilityPayload,
};
+10 -10
View File
@@ -36,8 +36,8 @@ export default class Zigbee {
databaseBackupPath: data.joinPath('database.db.backup'),
backupPath: data.joinPath('coordinator_backup.json'),
serialPort: {
baudRate: settings.get().advanced.baudrate,
rtscts: settings.get().advanced.rtscts,
baudRate: settings.get().serial.baudrate,
rtscts: settings.get().serial.rtscts,
path: settings.get().serial.port,
adapter: settings.get().serial.adapter,
},
@@ -112,8 +112,8 @@ export default class Zigbee {
for (const device of this.devices(false)) {
// If a passlist is used, all other device will be removed from the network.
const passlist = settings.get().passlist.concat(settings.get().whitelist);
const blocklist = settings.get().blocklist.concat(settings.get().ban);
const passlist = settings.get().passlist;
const blocklist = settings.get().blocklist;
const remove = async (device: Device): Promise<void> => {
try {
await device.zh.removeFromNetwork();
@@ -133,8 +133,8 @@ export default class Zigbee {
}
// Check if we have to set a transmit power
if (settings.get().experimental.hasOwnProperty('transmit_power')) {
const transmitPower = settings.get().experimental.transmit_power;
if (settings.get().advanced.hasOwnProperty('transmit_power')) {
const transmitPower = settings.get().advanced.transmit_power;
await this.herdsman.setTransmitPower(transmitPower);
logger.info(`Set transmit power to '${transmitPower}'`);
}
@@ -219,7 +219,7 @@ export default class Zigbee {
}
}
private resolveDevice(ieeeAddr: string): Device {
@bind private resolveDevice(ieeeAddr: string): Device {
if (!this.deviceLookup[ieeeAddr]) {
const device = this.herdsman.getDeviceByIeeeAddr(ieeeAddr);
device && (this.deviceLookup[ieeeAddr] = new Device(device));
@@ -235,7 +235,7 @@ export default class Zigbee {
private resolveGroup(groupID: number): Group {
const group = this.herdsman.getGroupByID(Number(groupID));
if (group && !this.groupLookup[groupID]) {
this.groupLookup[groupID] = new Group(group);
this.groupLookup[groupID] = new Group(group, this.resolveDevice);
}
return this.groupLookup[groupID];
@@ -275,8 +275,8 @@ export default class Zigbee {
@bind private async acceptJoiningDeviceHandler(ieeeAddr: string): Promise<boolean> {
// If passlist is set, all devices not on passlist will be rejected to join the network
const passlist = settings.get().passlist.concat(settings.get().whitelist);
const blocklist = settings.get().blocklist.concat(settings.get().ban);
const passlist = settings.get().passlist;
const blocklist = settings.get().blocklist;
if (passlist.length > 0) {
if (passlist.includes(ieeeAddr)) {
logger.info(`Accepting joining device which is on passlist '${ieeeAddr}'`);
+4147 -4168
View File
File diff suppressed because it is too large Load Diff
+27 -25
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "1.22.2",
"version": "1.25.0",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"repository": {
@@ -35,54 +35,56 @@
},
"homepage": "https://koenkk.github.io/zigbee2mqtt",
"dependencies": {
"ajv": "^8.8.2",
"ajv": "^8.11.0",
"bind-decorator": "^1.0.11",
"connect-gzip-static": "^2.1.1",
"core-js": "^3.20.1",
"core-js": "^3.21.1",
"debounce": "^1.2.1",
"deep-object-diff": "^1.1.0",
"deep-object-diff": "^1.1.7",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.1.2",
"finalhandler": "^1.2.0",
"git-last-commit": "^1.0.1",
"humanize-duration": "^3.27.1",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"mkdir-recursive": "^0.4.0",
"moment": "^2.29.1",
"mqtt": "4.3.1",
"mqtt": "4.3.7",
"object-assign-deep": "^0.4.0",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"source-map-support": "^0.5.21",
"winston": "^3.3.3",
"winston-syslog": "^2.4.4",
"ws": "^8.4.0",
"zigbee-herdsman": "0.13.188",
"zigbee-herdsman-converters": "14.0.366",
"zigbee2mqtt-frontend": "0.6.67"
"uri-js": "^4.4.1",
"winston": "^3.6.0",
"winston-syslog": "^2.5.0",
"winston-transport": "^4.5.0",
"ws": "^8.5.0",
"zigbee-herdsman": "0.14.20",
"zigbee-herdsman-converters": "14.0.467",
"zigbee2mqtt-frontend": "0.6.80"
},
"devDependencies": {
"@babel/core": "^7.16.5",
"@babel/plugin-proposal-decorators": "^7.16.5",
"@babel/preset-env": "^7.16.5",
"@babel/preset-typescript": "^7.16.5",
"@babel/core": "^7.17.8",
"@babel/plugin-proposal-decorators": "^7.17.8",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"@types/debounce": "^1.2.1",
"@types/finalhandler": "^1.1.1",
"@types/humanize-duration": "^3.27.1",
"@types/jest": "^27.0.3",
"@types/jest": "^27.4.1",
"@types/js-yaml": "^4.0.5",
"@types/object-assign-deep": "^0.4.0",
"@types/rimraf": "^3.0.2",
"@types/ws": "^8.2.2",
"@typescript-eslint/eslint-plugin": "^5.8.0",
"@typescript-eslint/parser": "^5.8.0",
"babel-jest": "^27.4.5",
"eslint": "^8.5.0",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0",
"babel-jest": "^27.5.1",
"eslint": "^8.12.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-jest": "^25.3.0",
"jest": "^27.4.5",
"eslint-plugin-jest": "^26.1.3",
"jest": "^27.5.1",
"tmp": "^0.2.1",
"typescript": "^4.5.4"
"typescript": "^4.6.3"
},
"jest": {
"coverageThreshold": {
+1
View File
@@ -6,5 +6,6 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
for (const line of text.split('\n')) {
const model = zigbeeHerdsmanConverters.devices.find((d) => d.model === line);
if (!model) throw new Error(`${line} does not exist`);
console.log(`- \`${line}\` ${model.vendor} ${model.description}`);
}
+45 -5
View File
@@ -227,9 +227,23 @@ describe('Availability', () => {
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(0);
});
it('Should to enable availabilty for just one device', async () => {
settings.set(['availability'], false);
settings.set(['devices', devices.bulb_color.ieeeAddr, 'availability'], true);
await resetExtension();
MQTT.publish.mockClear();
await advancedTime(utils.minutes(11));
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(1);
});
it('Should retrieve device state when it reconnects', async () => {
MQTT.publish.mockClear();
const device = controller.zigbee.resolveEntity(devices.bulb_color.ieeeAddr);
controller.state.set(device, {state: 'OFF'})
const endpoint = devices.bulb_color.getEndpoint(1);
endpoint.read.mockClear();
@@ -242,18 +256,15 @@ describe('Availability', () => {
expect(endpoint.read).toHaveBeenCalledTimes(0);
await advancedTime(utils.seconds(2));
expect(endpoint.read).toHaveBeenCalledTimes(3);
expect(endpoint.read).toHaveBeenCalledTimes(1);
expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']);
expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']);
expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl',
['colorMode', 'currentX', 'currentY', 'enhancedCurrentHue', 'currentSaturation', 'colorTemperature']);
endpoint.read.mockClear();
await zigbeeHerdsman.events.deviceAnnounce({device: devices.bulb_color});
await flushPromises();
endpoint.read.mockImplementationOnce(() => {throw new Error('')});
await advancedTime(utils.seconds(3));
expect(endpoint.read).toHaveBeenCalledTimes(3);
expect(endpoint.read).toHaveBeenCalledTimes(1);
});
it('Should republish availability when device is renamed', async () => {
@@ -262,6 +273,8 @@ describe('Availability', () => {
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb_color', to: 'bulb_new_name'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability',
null, {retain: true, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb_new_name/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
await advancedTime(utils.hours(12));
@@ -269,6 +282,16 @@ describe('Availability', () => {
'offline', {retain: true, qos: 0}, expect.any(Function));
});
it('Should publish availabiltiy payload in JSON format', async () => {
settings.set(['advanced', 'legacy_availability_payload'], false);
await resetExtension();
MQTT.publish.mockClear();
await advancedTime(utils.hours(26));
expect(devices.remote.ping).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/remote/availability',
stringify({state: 'offline'}), {retain: true, qos: 0}, expect.any(Function));
});
it('Deprecated - should allow to block via advanced.availability_blocklist', async () => {
settings.set(['advanced', 'availability_blocklist'], [devices.bulb_color.ieeeAddr]);
await resetExtension();
@@ -295,4 +318,21 @@ describe('Availability', () => {
await advancedTime(utils.minutes(12));
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(1);
});
it('Should publish availabilty for groups', async () => {
settings.set(['devices', devices.bulb_color_2.ieeeAddr, 'availability'], true);
await resetExtension();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
await advancedTime(utils.minutes(12));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'offline', {retain: true, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
devices.bulb_color_2.lastSeen = Date.now();
await zigbeeHerdsman.events.lastSeenChanged({device: devices.bulb_color_2});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
});
});
+18 -18
View File
File diff suppressed because one or more lines are too long
+2 -14
View File
@@ -192,19 +192,7 @@ describe('Configure', () => {
expect(logger.warn).toHaveBeenCalledWith(`Skipping configure of 'ZNLDP12LM', device does not require this.`)
});
it('Should not configure when interviewing', async () => {
const device = zigbeeHerdsman.devices.remote;
delete device.meta.configured;
device.interviewing = true;
const endpoint = device.getEndpoint(1);
mockClear(device);
await zigbeeHerdsman.events.lastSeenChanged({device});
await flushPromises();
expectRemoteNotConfigured();
device.interviewing = false;
});
it('Should configure when not interviewCompleted', async () => {
it('Should not configure when interview not completed', async () => {
const device = zigbeeHerdsman.devices.remote;
delete device.meta.configured;
device.interviewCompleted = false;
@@ -212,7 +200,7 @@ describe('Configure', () => {
mockClear(device);
await zigbeeHerdsman.events.lastSeenChanged({device});
await flushPromises();
expectRemoteConfigured();
expectRemoteNotConfigured();
device.interviewCompleted = true;
});
+6 -19
View File
@@ -28,9 +28,10 @@ describe('Controller', () => {
MQTT.restoreOnMock();
zigbeeHerdsman.returnDevices.splice(0);
mockExit = jest.fn();
data.writeDefaultConfiguration();
settings.reRead();
controller = new Controller(jest.fn(), mockExit);
mocksClear.forEach((m) => m.mockClear());
data.writeDefaultConfiguration();
settings.reRead();
data.writeDefaultState();
});
@@ -180,14 +181,6 @@ describe('Controller', () => {
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(1);
});
it('Should remove non whitelisted devices on startup', async () => {
settings.set(['whitelist'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]);
await controller.start();
await flushPromises();
expect(zigbeeHerdsman.devices.bulb_color.removeFromNetwork).toHaveBeenCalledTimes(0);
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(1);
});
it('Should remove device on blocklist on startup', async () => {
settings.set(['blocklist'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]);
await controller.start();
@@ -196,14 +189,6 @@ describe('Controller', () => {
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(0);
});
it('Should remove banned devices on startup', async () => {
settings.set(['ban'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]);
await controller.start();
await flushPromises();
expect(zigbeeHerdsman.devices.bulb_color.removeFromNetwork).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(0);
});
it('Start controller fails', async () => {
zigbeeHerdsman.start.mockImplementationOnce(() => {throw new Error('failed')});
await controller.start();
@@ -630,11 +615,13 @@ describe('Controller', () => {
});
it('Should disable legacy options on new network start', async () => {
expect(settings.get().advanced.homeassistant_legacy_entity_attributes).toBeTruthy();
settings.set(['homeassistant'], true);
settings.reRead();
expect(settings.get().homeassistant.legacy_entity_attributes).toBeTruthy();
expect(settings.get().advanced.legacy_api).toBeTruthy();
zigbeeHerdsman.start.mockReturnValueOnce('reset');
await controller.start();
expect(settings.get().advanced.homeassistant_legacy_entity_attributes).toBeFalsy();
expect(settings.get().homeassistant.legacy_entity_attributes).toBeFalsy();
expect(settings.get().advanced.legacy_api).toBeFalsy();
});
+195 -20
View File
@@ -24,6 +24,7 @@ describe('HomeAssistant extension', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings.reRead();
settings.set(['homeassistant'], true);
data.writeEmptyState();
controller.state.load();
await resetExtension();
@@ -50,14 +51,19 @@ describe('HomeAssistant extension', () => {
const duplicated = [];
require('zigbee-herdsman-converters').devices.forEach((d) => {
const exposes = typeof d.exposes == 'function' ? d.exposes() : d.exposes;
const device = {definition: d, isDevice: () => true, settings: {}, exposes: () => exposes};
const device = {definition: d, isDevice: () => true, options: {}, exposes: () => exposes};
const configs = extension.getConfigs(device);
const cfg_type_object_ids = [];
configs.forEach((c) => {
const id = c['type'] + '/' + c['object_id'];
if (cfg_type_object_ids.includes(id)) {
duplicated.push(d.model);
if (typeof d.exposes == 'function') {
// A dynamic function must exposes all possible attributes for the docs
console.warn(`${d.model} dynamic exposes contains duplicated ${id}`)
} else {
duplicated.push(d.model);
}
} else {
cfg_type_object_ids.push(id);
}
@@ -475,7 +481,7 @@ describe('HomeAssistant extension', () => {
device: {
manufacturer: 'Not from Xiaomi',
model: 'custom model',
}
},
},
friendly_name: 'weather_sensor',
retain: false,
@@ -544,6 +550,75 @@ describe('HomeAssistant extension', () => {
);
});
it('Should discover devices with overriden name', async () => {
settings.set(['devices', '0x0017880104e45522'], {
homeassistant: {
name: "Weather Sensor",
},
friendly_name: 'weather_sensor',
retain: false,
})
await resetExtension();
let payload;
await flushPromises();
payload = {
'unit_of_measurement': '°C',
'device_class': 'temperature',
'state_class': 'measurement',
'value_template': '{{ value_json.temperature }}',
'state_topic': 'zigbee2mqtt/weather_sensor',
'json_attributes_topic': 'zigbee2mqtt/weather_sensor',
'name': 'Weather Sensor temperature',
'unique_id': '0x0017880104e45522_temperature_zigbee2mqtt',
'device': {
'identifiers': ['zigbee2mqtt_0x0017880104e45522'],
'name': 'Weather Sensor',
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'enabled_by_default': true,
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
payload = {
'unit_of_measurement': '%',
'device_class': 'humidity',
'state_class': 'measurement',
'value_template': '{{ value_json.humidity }}',
'state_topic': 'zigbee2mqtt/weather_sensor',
'json_attributes_topic': 'zigbee2mqtt/weather_sensor',
'name': 'Weather Sensor humidity',
'unique_id': '0x0017880104e45522_humidity_zigbee2mqtt',
'enabled_by_default': true,
'device': {
'identifiers': ['zigbee2mqtt_0x0017880104e45522'],
'name': 'Weather Sensor',
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/humidity/config',
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',
@@ -555,8 +630,7 @@ describe('HomeAssistant extension', () => {
light: {
type: 'this should be ignored',
name: 'my_light_name_override'
}
},
},
})
@@ -671,29 +745,35 @@ describe('HomeAssistant extension', () => {
let payload;
payload = {
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
"action_template":"{% set values = {None:None,'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": null,
"identifiers":[
"zigbee2mqtt_0x0017882104a44559"
],
"manufacturer":"TuYa",
"model":"Radiator valve with thermostat (TS0601_thermostat)",
"name":"TS0601_thermostat",
"sw_version":null
},
"hold_command_topic":"zigbee2mqtt/TS0601_thermostat/set/preset",
"hold_modes":[
"schedule",
"manual",
"boost",
"complex",
"comfort",
"eco"
"schedule",
"manual",
"boost",
"complex",
"comfort",
"eco"
],
"hold_state_template":"{{ value_json.preset }}",
"hold_state_topic":"zigbee2mqtt/TS0601_thermostat",
@@ -704,7 +784,9 @@ describe('HomeAssistant extension', () => {
"mode_state_template":"{{ value_json.system_mode }}",
"mode_state_topic":"zigbee2mqtt/TS0601_thermostat",
"modes":[
"heat", "auto", "off"
"heat",
"auto",
"off"
],
"name":"TS0601_thermostat",
"temp_step":0.5,
@@ -924,6 +1006,19 @@ describe('HomeAssistant extension', () => {
await flushPromises();
});
it('Should discover when options change', async () => {
const device = controller.zigbee.resolveEntity(zigbeeHerdsman.devices.bulb);
MQTT.publish.mockClear();
controller.eventBus.emitEntityOptionsChanged({entity: device, from: {}, to: {'test': 123}});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
`homeassistant/light/${device.ID}/light/config`,
expect.any(String),
expect.any(Object),
expect.any(Function),
);
});
it('Should send all status when home assistant comes online (default topic)', async () => {
data.writeDefaultState();
extension.state.load();
@@ -1141,6 +1236,50 @@ describe('HomeAssistant extension', () => {
);
});
it('Should refresh discovery when group is renamed', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/rename', stringify({"from": "ha_discovery_group", "to": "ha_discovery_group_new","homeassistant_rename":true}));
await flushPromises();
const payload = {
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"brightness":true,
"brightness_scale":254,
"color_mode":true,
"command_topic":"zigbee2mqtt/ha_discovery_group_new/set",
"device":{
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group_new",
"sw_version": version,
},
"json_attributes_topic":"zigbee2mqtt/ha_discovery_group_new",
"max_mireds": 454,
"min_mireds": 250,
"name":"ha_discovery_group_new",
"schema":"json",
"state_topic":"zigbee2mqtt/ha_discovery_group_new",
"supported_color_modes":[
"xy",
"color_temp"
],
"unique_id":"9_light_zigbee2mqtt"
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/1221051039810110150109113116116_9/light/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/1221051039810110150109113116116_9/light/config',
null,
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Shouldnt refresh discovery when device is renamed and homeassistant_rename is false', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "weather_sensor", "to": "weather_sensor_renamed","homeassistant_rename":false}));
@@ -1650,6 +1789,42 @@ describe('HomeAssistant extension', () => {
);
});
it('Should discover with json availability payload value_template', async () => {
settings.set(['advanced', 'legacy_availability_payload'], false);
await resetExtension();
const payload = {
"availability":[{"topic":"zigbee2mqtt/bridge/state","value_template":'{{ value_json.state }}'}],
"brightness":true,
"brightness_scale":254,
"color_mode":true,
"command_topic":"zigbee2mqtt/ha_discovery_group/set",
"device":{
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group",
"sw_version": version,
},
"max_mireds": 454,
"min_mireds": 250,
"json_attributes_topic":"zigbee2mqtt/ha_discovery_group",
"name":"ha_discovery_group",
"schema":"json",
"state_topic":"zigbee2mqtt/ha_discovery_group",
"supported_color_modes":[
"xy",
"color_temp"
],
"unique_id":"9_light_zigbee2mqtt"
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/1221051039810110150109113116116_9/light/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Should discover last_seen when enabled', async () => {
settings.set(['advanced', 'last_seen'], 'ISO_8601');
await resetExtension();
+11 -11
View File
@@ -56,7 +56,7 @@ describe('Bridge legacy', () => {
it('Should allow whitelist', async () => {
const bulb_color = zigbeeHerdsman.devices.bulb_color;
const bulb = zigbeeHerdsman.devices.bulb;
expect(settings.get().whitelist).toStrictEqual([]);
expect(settings.get().passlist).toStrictEqual([]);
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/whitelist', 'bulb_color');
await flushPromises();
@@ -68,7 +68,7 @@ describe('Bridge legacy', () => {
);
MQTT.publish.mockClear()
expect(settings.get().whitelist).toStrictEqual([bulb_color.ieeeAddr]);
expect(settings.get().passlist).toStrictEqual([bulb_color.ieeeAddr]);
MQTT.events.message('zigbee2mqtt/bridge/config/whitelist', 'bulb');
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -79,10 +79,10 @@ describe('Bridge legacy', () => {
);
MQTT.publish.mockClear()
expect(settings.get().whitelist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
expect(settings.get().passlist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
MQTT.events.message('zigbee2mqtt/bridge/config/whitelist', 'bulb');
await flushPromises();
expect(settings.get().whitelist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
expect(settings.get().passlist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
expect(MQTT.publish).toHaveBeenCalledTimes(0);
});
@@ -366,7 +366,7 @@ describe('Bridge legacy', () => {
controller.state.state = {'0x000b57fffec6a5b3': {brightness: 100}};
const device = zigbeeHerdsman.devices.bulb_color;
device.removeFromNetwork.mockClear();
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/remove', 'bulb_color');
@@ -381,14 +381,14 @@ describe('Bridge legacy', () => {
expect.any(Function)
);
expect(controller.state.state).toStrictEqual({});
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
});
it('Should allow to force remove device', async () => {
controller.state.state = {'0x000b57fffec6a5b3': {brightness: 100}};
const device = zigbeeHerdsman.devices.bulb_color;
device.removeFromDatabase.mockClear();
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/force_remove', 'bulb_color');
@@ -403,13 +403,13 @@ describe('Bridge legacy', () => {
expect.any(Function)
);
expect(controller.state.state).toStrictEqual({});
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
});
it('Should allow to ban device', async () => {
it('Should allow to block device', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
device.removeFromNetwork.mockClear();
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/ban', 'bulb_color');
@@ -423,7 +423,7 @@ describe('Bridge legacy', () => {
{qos: 0, retain: false},
expect.any(Function)
);
expect(settings.get().ban).toStrictEqual(['0x000b57fffec6a5b3']);
expect(settings.get().blocklist).toStrictEqual(['0x000b57fffec6a5b3']);
});
it('Shouldnt crash when removing non-existing device', async () => {
+29 -6
View File
@@ -7,10 +7,18 @@ const Controller = require('../lib/controller');
const flushPromises = require('./lib/flushPromises');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const stringify = require('json-stable-stringify-without-jsonify');
const zigbeeOTA = require('zigbee-herdsman-converters/lib/ota/zigbeeOTA');
const spyUseIndexOverride = jest.spyOn(zigbeeOTA, 'useIndexOverride');
describe('OTA update', () => {
let controller;
let resetExtension = async () => {
await controller.enableDisableExtension(false, 'OTAUpdate');
await controller.enableDisableExtension(true, 'OTAUpdate');
}
const mockClear = (mapped) => {
mapped.ota.updateToLatest = jest.fn();
mapped.ota.isUpdateAvailable = jest.fn();
@@ -20,7 +28,7 @@ describe('OTA update', () => {
data.writeDefaultConfiguration();
settings.reRead();
data.writeDefaultConfiguration();
settings.set(['advanced', 'ikea_ota_use_test_url'], true);
settings.set(['ota', 'ikea_ota_use_test_url'], true);
settings.reRead();
jest.useFakeTimers();
controller = new Controller(jest.fn(), jest.fn());
@@ -70,9 +78,11 @@ 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 '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}'`);
expect(device.save).toHaveBeenCalledTimes(2);
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhenActive': false});
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhen': 'immediate'});
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhen': 'active'});
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"update_available":false,"update":{"state":"updating","progress":0}}),
@@ -90,7 +100,7 @@ describe('OTA update', () => {
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
stringify({"data":{"id": "bulb","from":{"software_build_id":1,"date_code":"20190101"},"to":{"software_build_id":2,"date_code":"20190102"}},"status":"ok"}),
stringify({"data":{"from":{"date_code":"20190101","software_build_id":1},"id":"bulb","to":{"date_code":"20190103","software_build_id":3}},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -369,10 +379,11 @@ 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 '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}'`);
expect(logger.error).toHaveBeenCalledTimes(0);
expect(device.save).toHaveBeenCalledTimes(2);
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhenActive': false});
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhen': 'immediate'});
});
it('Legacy api: Should handle when OTA update fails', async () => {
@@ -453,4 +464,16 @@ describe('OTA update', () => {
await flushPromises();
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
});
it('Set zigbee_ota_override_index_location', async () => {
settings.set(['ota', 'zigbee_ota_override_index_location'], 'local.index.json');
await resetExtension();
expect(spyUseIndexOverride).toHaveBeenCalledWith(data.mockDir + '/local.index.json');
spyUseIndexOverride.mockClear();
settings.set(['ota', 'zigbee_ota_override_index_location'], 'http://my.site/index.json');
await resetExtension();
expect(spyUseIndexOverride).toHaveBeenCalledWith('http://my.site/index.json');
spyUseIndexOverride.mockClear();
});
});
+2 -29
View File
@@ -367,7 +367,7 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/thermostat_group/set', stringify({child_lock: 'LOCK'}));
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith("manuSpecificTuya", "dataRequest", {data: [1], datatype: 1, dp: 7, length_hi: 0, length_lo: 1, seq: expect.any(Number)}, {disableDefaultResponse: true});
expect(group.command).toHaveBeenCalledWith("manuSpecificTuya", "dataRequest", {dpValues: [{data: [1], datatype: 1, dp: 7}], seq: expect.any(Number)}, {disableDefaultResponse: true});
});
it('Should publish messages to groups with on and brightness', async () => {
@@ -907,33 +907,6 @@ describe('Publish', () => {
expect(endpoint.command.mock.calls[1]).toEqual(["lightingColorCtrl", "moveToColor", {"colorx": 45940, "colory": 19595, "transtime": 30}, {}]);
});
it('Should correctly toggle when transition is used', async () => {
settings.set(['devices', zigbeeHerdsman.devices.bulb_color.ieeeAddr, 'transition'], 20);
const endpoint = zigbeeHerdsman.devices.bulb_color.getEndpoint(1);
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', brightness: 163}));
await flushPromises();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'TOGGLE',}));
await flushPromises();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'TOGGLE',}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(4);
expect(endpoint.command).toHaveBeenNthCalledWith(1, "genLevelCtrl", "moveToLevelWithOnOff", {"level": 163, "transtime": 200}, {})
expect(endpoint.command).toHaveBeenNthCalledWith(2, "genLevelCtrl", "moveToLevelWithOnOff", {"level": 0, "transtime": 200}, {})
expect(endpoint.command).toHaveBeenNthCalledWith(3, "genLevelCtrl", "moveToLevelWithOnOff", {"level": 163, "transtime": 200}, {})
expect(endpoint.command).toHaveBeenNthCalledWith(4, "genLevelCtrl", "moveToLevelWithOnOff", {"level": 0, "transtime": 200}, {})
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({"brightness":163,"state":"ON"}),
{ retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({"brightness":163,"state":"OFF"}),
{ retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(3, 'zigbee2mqtt/bulb_color', stringify({"brightness":163,"state":"ON"}),
{ retain: false, qos: 0 }, expect.any(Function));
expect(MQTT.publish).toHaveBeenNthCalledWith(4, 'zigbee2mqtt/bulb_color', stringify({"brightness":163,"state":"OFF"}),
{ retain: false, qos: 0 }, expect.any(Function));
});
it('Should turn device off when brightness 0 is send', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
@@ -1321,7 +1294,7 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/MKS-CM-W5/l3/set', stringify({state: 'ON'}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuya", "dataRequest", {data: [1], datatype: 1, dp: 3, length_hi: 0, length_lo: 1, seq: expect.any(Number)}, {disableDefaultResponse: true});
expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuya", "dataRequest", {dpValues: [{data: [1], datatype: 1, dp: 3}], seq: expect.any(Number)}, {disableDefaultResponse: true});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/MKS-CM-W5');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state_l3: 'ON'});
-50
View File
@@ -254,42 +254,6 @@ describe('Receive', () => {
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_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});
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_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});
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_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});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should publish 1 message when converted twice', async () => {
const device = zigbeeHerdsman.devices.RTCGQ11LM;
const data = {'65281': {'1': 3045, '3': 19, '4': 17320, '5': 35, '6': [0, 3], '10': 51107, '11': 381, '100': 0}}
@@ -350,20 +314,6 @@ describe('Receive', () => {
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should handle forwarded Xiaomi messages', async () => {
const device = zigbeeHerdsman.devices.ZNCZ02LM;
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(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 () => {
const device = zigbeeHerdsman.devices.ZNCZ02LM;
const data = {onOff: 1};
+77 -10
View File
@@ -66,7 +66,7 @@ describe('Settings', () => {
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_OUTPUT'] = 'csvtest';
process.env['ZIGBEE2MQTT_CONFIG_MAP_OPTIONS_GRAPHVIZ_COLORS_FILL'] = '{"enddevice": "#ff0000", "coordinator": "#00ff00", "router": "#0000ff"}';
process.env['ZIGBEE2MQTT_CONFIG_MQTT_BASE_TOPIC'] = 'testtopic';
@@ -77,7 +77,7 @@ describe('Settings', () => {
expected.groups = {};
expected.serial.disable_led = true;
expected.advanced.soft_reset_timeout = 1;
expected.experimental.output = 'csvtest';
expected.advanced.output = 'csvtest';
expected.map_options.graphviz.colors.fill = {enddevice: '#ff0000', coordinator: '#00ff00', router: '#0000ff'};
expected.mqtt.base_topic = 'testtopic';
@@ -150,6 +150,7 @@ describe('Settings', () => {
write(configurationFile, contentConfiguration);
const expected = {
base_topic: 'zigbee2mqtt',
include_device_information: false,
force_disable_retain: false,
password: "mysecretpassword",
@@ -672,14 +673,6 @@ describe('Settings', () => {
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Should ban devices', () => {
write(configurationFile, {});
settings.banDevice('0x123');
expect(settings.get().ban).toStrictEqual(['0x123']);
settings.banDevice('0x1234');
expect(settings.get().ban).toStrictEqual(['0x123', '0x1234']);
});
it('Should add devices to blocklist', () => {
write(configurationFile, {});
settings.blockDevice('0x123');
@@ -842,4 +835,78 @@ describe('Settings', () => {
const after = fs.statSync(configurationFile).mtimeMs;
expect(before).toBe(after);
});
it('Frontend config', () => {
write(configurationFile, {...minimalConfig,
frontend: true,
});
settings.reRead();
expect(settings.get().frontend).toStrictEqual({port: 8080, auth_token: false, host: '0.0.0.0'})
});
it('Baudrate config', () => {
write(configurationFile, {...minimalConfig,
advanced: {baudrate: 20},
});
settings.reRead();
expect(settings.get().serial.baudrate).toStrictEqual(20)
});
it('ikea_ota_use_test_url config', () => {
write(configurationFile, {...minimalConfig,
advanced: {ikea_ota_use_test_url: true},
});
settings.reRead();
expect(settings.get().ota.ikea_ota_use_test_url).toStrictEqual(true)
});
it('transmit_power config', () => {
write(configurationFile, {...minimalConfig,
experimental: {transmit_power: 1337},
});
settings.reRead();
expect(settings.get().advanced.transmit_power).toStrictEqual(1337)
});
it('output config', () => {
write(configurationFile, {...minimalConfig,
experimental: {output: 'json'},
});
settings.reRead();
expect(settings.get().advanced.output).toStrictEqual('json')
});
it('Baudrartsctste config', () => {
write(configurationFile, {...minimalConfig,
advanced: {rtscts: true},
});
settings.reRead();
expect(settings.get().serial.rtscts).toStrictEqual(true)
});
it('Deprecated: Home Assistant config', () => {
write(configurationFile, {...minimalConfig,
homeassistant: {discovery_topic: 'new'},
advanced: {homeassistant_discovery_topic: 'old', homeassistant_status_topic: 'olds'},
});
settings.reRead();
expect(settings.get().homeassistant).toStrictEqual({discovery_topic: 'new', legacy_entity_attributes: true, legacy_triggers: true, status_topic: 'olds'})
});
it('Deprecated: ban/whitelist config', () => {
write(configurationFile, {...minimalConfig,
ban: ['ban'], whitelist: ['whitelist'], passlist: ['passlist'], blocklist: ['blocklist']
});
settings.reRead();
expect(settings.get().blocklist).toStrictEqual(['blocklist', 'ban'])
expect(settings.get().passlist).toStrictEqual(['passlist', 'whitelist'])
});
});
+2 -1
View File
@@ -21,7 +21,8 @@ function writeDefaultConfiguration() {
devices: {
"0x000b57fffec6a5b2": {
retain: true,
friendly_name: "bulb"
friendly_name: "bulb",
description: "this is my bulb",
},
"0x0017880104e45517": {
retain: true,
+1
View File
@@ -23,6 +23,7 @@ const mock = {
setLevel: (newLevel) => {level = newLevel},
getLevel: () => level,
setTransportsEnabled: (value) => {transportsEnabled = value},
end: jest.fn(),
};
jest.mock('../../lib/util/logger', () => (mock));