Compare commits

...
166 Commits
Author SHA1 Message Date
Koen Kanters b5a90e921b chore(dev): release 1.35.0 (#20467) 2024-01-01 12:29:01 +01:00
koenkk 32b0ded17c fix(ignore): fix dee8781e51 2024-01-01 12:18:25 +01:00
koenkk dee8781e51 fix(ignore): fix homeassistant.test.js 2024-01-01 11:42:48 +01:00
koenkk 64fd21c9c2 chore: fix release please 2024-01-01 11:14:28 +01:00
Koen KantersandKoenkk f9e0c2058f fix(ignore): update zigbee-herdsman-converters to 18.3.0 (#20465)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-01 09:57:41 +00:00
Koen KantersandKoenkk 16730b14a1 fix(ignore): update dependencies (#20452)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-01 10:53:14 +01:00
Koen KantersandKoenkk 05c65d31ed fix(ignore): update zigbee-herdsman to 0.30.0 (#20464)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-01 09:47:11 +00:00
Koen KantersandKoenkk a8c791b030 fix(ignore): update zigbee-herdsman-converters to 18.2.0 (#20451)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-30 21:59:55 +00:00
Koen KantersandKoenkk a3a87f4580 fix(ignore): update zigbee-herdsman-converters to 18.1.0 (#20426)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-29 07:47:48 +00:00
Koen KantersandKoenkk ffa43915f0 Update zigbee-herdsman-converters to 18.0.2 (#20404)
* fix(ignore): update zigbee-herdsman-converters to 18.0.2

* Update bridge.test.js

* Update networkMap.ts

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-27 20:53:57 +00:00
Koen KantersandKoenkk 7bca19dae7 fix(ignore): update zigbee-herdsman to 0.29.1 (#20406)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-27 21:27:35 +01:00
koenkk 6b6cc8aa34 fix(ignore): Revert module to commonjs 2023-12-27 21:24:08 +01:00
Koen KantersandKoenkk 03a1ce3895 fix(ignore): update zigbee-herdsman to 0.29.0 (#20403)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-27 19:39:32 +01:00
koenkk 82fea1fe41 Revert "fix(ignore): Update alpine to 3.19.0 (#20392)"
This reverts commit eaaf1b0b4f.
2023-12-27 13:21:16 +01:00
koenkk 084b048ff3 fix(ignore): Update tsconfig for Node 18 2023-12-27 13:19:54 +01:00
Guy Driesen 085b46c995 fix: Add Home Assistant entity_category for various Ubisys D1 entities (#20393) 2023-12-27 12:38:42 +01:00
Koen Kanters eaaf1b0b4f fix(ignore): Update alpine to 3.19.0 (#20392) 2023-12-27 12:35:54 +01:00
koenkk 265d280efc chore: remove unused @babel/plugin-proposal-class-properties 2023-12-26 20:52:06 +01:00
Koen Kanters e6e3d92971 fix(ignore): Changes for zhc 18.0.0 (#20390)
* fix(ignore): Changes for zhc 18.0.0

* fix
2023-12-26 19:49:49 +00:00
Koen KantersandKoenkk bf744f48dc fix(ignore): update zigbee-herdsman-converters to 18.0.0 (#20391)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-26 20:44:48 +01:00
Koen KantersandKoenkk ec06efe86a Update zigbee-herdsman-converters to 17.1.1 (#20385)
* fix(ignore): update zigbee-herdsman-converters to 17.1.1

* Update otaUpdate.ts

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-26 17:56:54 +00:00
Koen Kanters 56f57625b0 feat: Support automatically generating device definitions (#20358)
* feat: Support automatically generating device definitions

* updates

* update template
2023-12-25 12:46:57 +01:00
Koen KantersandKoenkk 2110954bb8 fix(ignore): update zigbee-herdsman-converters to 17.0.0 (#20378)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-25 12:44:08 +01:00
Koen Kantersandnurikk d616e57daf fix(ignore): update zigbee2mqtt-frontend to 0.6.150 (#20377)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-12-25 11:36:13 +00:00
Ilya KirovandKoen Kanters d702919735 fix(ignore): Ability to use a specified endpoint in the Tz.Converter (#20356)
* Ability to use a specified endpoint in the Tz.Converter

* Update publish.ts

* Update publish.ts

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2023-12-25 10:48:15 +01:00
Koen KantersandKoenkk fe7e82f7d3 fix(ignore): update zigbee-herdsman-converters to 16.18.0 (#20374)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-25 09:41:40 +00:00
Koen KantersandKoenkk 03857916c4 fix(ignore): update dependencies (#20368)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-25 09:31:09 +01:00
Koen KantersandKoenkk 1a7265017a fix(ignore): update zigbee-herdsman-converters to 16.17.0 (#20359)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-23 08:49:50 +00:00
Koen KantersandKoenkk 89f8e43ad1 fix(ignore): update zigbee-herdsman to 0.28.0 (#20357)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-23 07:48:39 +00:00
Koen KantersandKoenkk 6875146211 fix(ignore): update zigbee-herdsman-converters to 16.16.0 (#20326)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-22 08:14:16 +00:00
Koen KantersandKoenkk 27ccd1a570 fix(ignore): update zigbee-herdsman-converters to 16.15.0 (#20318)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-20 19:38:52 +00:00
slugzero a5b959a6d5 fix: Clear old Home Assistant discovery topics when exposes are changed (#20313) 2023-12-20 19:46:59 +01:00
Roland Huß b67888f938 fix: Fix load of empty configuration .yaml file failing (#20289)
* Always return an object when loading a YAML file, even when it is empty or containing the `null` value.

Fixes #20283

* Add unit test for having multiple device files, with one is empty. This test breaks without the proposed.
2023-12-20 19:31:49 +01:00
Koen KantersandKoenkk af8c2d86fe fix(ignore): update zigbee-herdsman-converters to 16.14.1 (#20301)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-19 19:26:14 +00:00
Koen KantersandKoenkk 29635a9b6a fix(ignore): update zigbee-herdsman-converters to 16.13.0 (#20280)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-18 19:18:25 +00:00
Koen KantersandKoenkk 65d7f5f3e2 fix(ignore): update zigbee-herdsman to 0.27.1 (#20262)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-17 20:38:55 +00:00
Koen KantersandKoenkk c26a6a28e4 fix(ignore): update zigbee-herdsman-converters to 16.12.0 (#20258)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-17 14:39:55 +00:00
Koen KantersandKoenkk 19754c69e5 fix(ignore): update dependencies (#20257)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-17 15:11:01 +01:00
Koen KantersandKoenkk 1e4ac34c44 fix(ignore): update dependencies (#20256)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-17 15:03:35 +01:00
koenkk 89e6d307f2 chore: update_deps 2023-12-17 15:03:17 +01:00
koenkk e7e9616f7e chore: override zigbee-herdsman-converters zigbee-herdsman version 2023-12-17 14:59:16 +01:00
Koen KantersandKoenkk 6e52b2a7af fix(ignore): update dependencies (#20249)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-17 14:56:50 +01:00
koenkk 2e704f3ff7 fix(ignore): improve 8f62a0a 2023-12-17 14:42:48 +01:00
koenkk 8f62a0a618 fix: Fix Home Assistant Entity category config is invalid for sensors warning https://github.com/Koenkk/zigbee2mqtt/issues/20252 2023-12-17 14:24:12 +01:00
Der Mundschenk & Compagnie e0f50218d7 fix: Only republish changed scenes to Home Assistant when changed (#20248) 2023-12-17 09:21:59 +01:00
Koen KantersandKoenkk 2c5d3e14f7 fix(ignore): update zigbee-herdsman-converters to 16.11.0 (#20228)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-16 08:46:43 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 851e3f559a chore: Bump actions/upload-artifact from 3 to 4 (#20226)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 20:46:17 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> cefd6e5685 chore: Bump actions/download-artifact from 3 to 4 (#20225)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 20:46:09 +01:00
Koen KantersandKoenkk 25de4fded9 fix(ignore): update zigbee-herdsman-converters to 16.9.0 (#20200)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-13 19:52:28 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ca6b24304c chore: Bump github/codeql-action from 2 to 3 (#20197)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-13 20:11:55 +01:00
Koen KantersandKoenkk b415443126 fix(ignore): update dependencies (#20133)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-13 19:46:59 +01:00
Koen KantersandKoenkk bcf50bd0c5 fix(ignore): update zigbee-herdsman-converters to 16.8.0 (#20185)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-12 19:07:25 +00:00
Koen KantersandKoenkk 7c81eda46a fix(ignore): update zigbee-herdsman-converters to 16.7.0 (#20167)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-11 19:22:30 +00:00
koenkk abde909b71 fix(ignore): Fix OTA sensor discoverd while device doesn't support it https://github.com/Koenkk/zigbee2mqtt/issues/20131 2023-12-11 20:10:08 +01:00
Viacheslav Bocharov 48347a51dc fix: Allow null for zigbee_ota_override_index_location option (#20151) 2023-12-11 19:25:49 +01:00
koenkk f64c00c8d3 fix(ignore): Downgrade connect-gzip-static to 3.0.1 https://github.com/Koenkk/zigbee2mqtt/issues/20149 2023-12-11 19:24:36 +01:00
Koen KantersandKoenkk 87f6b9e81a fix(ignore): update zigbee-herdsman-converters to 16.6.0 (#20145)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-10 20:36:46 +00:00
Koen Kanters 0a2d9ba952 chore: merge master to dev 2023-12-10 21:21:18 +01:00
Mario Guggenberger 5aeec130b1 fix: Fix availability checks not stopped on extension stop (#20093)
* fix: availability checks not stopped on extension stop

* Add test

* Clarify comments

* Simplify test

* Simplify code
2023-12-10 21:20:59 +01:00
Koen KantersandKoenkk a26008f74c fix(ignore): update zigbee-herdsman to 0.26.1 (#20143)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-10 19:37:42 +00:00
Koen Kantersandnurikk c7e294482b fix(ignore): update zigbee2mqtt-frontend to 0.6.149 (#20141)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-12-10 15:41:52 +00:00
Koen KantersandKoenkk 5ff10c3e5c fix(ignore): update zigbee-herdsman-converters to 16.5.0 (#20125)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-09 16:57:25 +00:00
koenkk be17544598 Revert "chore: Bump alpine from 3.18.5 to 3.19.0 in /docker (#20103)"
This reverts commit 81033700e2.
2023-12-09 14:16:22 +01:00
Koen KantersandKoenkk 8929ab3b97 fix(ignore): update zigbee-herdsman to 0.26.0 (#20092)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-09 14:12:06 +01:00
koenkk d1501052ad fix(ignore): fix bind test 2023-12-09 14:11:45 +01:00
Koen KantersandKoenkk 4cc88f1e16 fix(ignore): update zigbee-herdsman-converters to 16.4.0 (#20120)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-09 13:20:40 +01:00
Koen KantersandKoenkk 4e6bc33a22 fix(ignore): update zigbee-herdsman-converters to 16.3.1 (#20109)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-08 20:19:31 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 81033700e2 chore: Bump alpine from 3.18.5 to 3.19.0 in /docker (#20103)
Bumps alpine from 3.18.5 to 3.19.0.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-08 19:53:32 +01:00
Der Mundschenk & Compagnie a17f650ccb fix: Prevent duplicate Home Assistant scene entities (#20097) 2023-12-08 19:34:37 +01:00
Der Mundschenk & Compagnie 59efc15f3a fix(ignore): Fix "Permit join timeout" template (#20096) 2023-12-08 19:33:30 +01:00
Koen KantersandKoenkk cb509426ec fix(ignore): update zigbee-herdsman-converters to 16.2.0 (#20094)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-07 20:59:01 +00:00
dependabot[bot] aa8144b739 chore: Bump actions/stale from 8 to 9 (#20088) 2023-12-07 19:31:47 +01:00
Der Mundschenk & Compagnie f11eb2459b feat: Support bridge discovery for Home Assistant (#19994)
* Simplify getDiscoverKey

* Add support for discovering bridge entities

* Add automatic discovery for bridge

* Make availability configurable and disable it
for `connection_state` entity

* Link devices & groups to bridge

* Additional entities (and some clean-up)
2023-12-06 19:36:45 +01:00
Koen KantersandKoenkk cb74db47f7 fix(ignore): update zigbee-herdsman-converters to 16.1.0 (#20059)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-05 19:44:28 +00:00
koenkk 299b37a721 chore: include frontend in changelog 2023-12-05 10:22:12 +01:00
Koen Kantersandnurikk ffcb8202f0 fix(ignore): update zigbee2mqtt-frontend to 0.6.148 (#20034)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-12-05 10:21:50 +01:00
Koen KantersandKoenkk e60b94b553 fix(ignore): update zigbee-herdsman-converters to 16.0.2 (#20027)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-04 20:29:11 +00:00
koenkk fbabe9674c chore: increase stale timeout 2023-12-04 20:46:18 +01:00
koenkk 8ab96e124f fix: Don't crash on startup when external converters fails to load https://github.com/Koenkk/zigbee2mqtt/issues/20010 2023-12-04 20:40:45 +01:00
Koen KantersandKoenkk 483acc3ca1 fix(ignore): update zigbee-herdsman to 0.25.3 (#20025)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-04 19:19:19 +00:00
Koen KantersandKoenkk e0deef8889 fix(ignore): update dependencies (#20023)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-04 20:00:39 +01:00
koenkk bbb0ea329c chore: update release-please config for v4 bf69db3e82 2023-12-04 19:55:44 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> bf69db3e82 chore: Bump google-github-actions/release-please-action from 3 to 4 (#20017)
Bumps [google-github-actions/release-please-action](https://github.com/google-github-actions/release-please-action) from 3 to 4.
- [Release notes](https://github.com/google-github-actions/release-please-action/releases)
- [Changelog](https://github.com/google-github-actions/release-please-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/release-please-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: google-github-actions/release-please-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 19:54:47 +01:00
koenkk adc08125fe fix: Support Node 21 and drop 16 2023-12-04 19:29:35 +01:00
koenkk aa9a916d61 chore: fix changelog generator 2023-12-04 19:13:57 +01:00
Koen KantersandKoenkk 5e3bd75f09 fix(ignore): update zigbee-herdsman-converters to 16.0.1 (#20012)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-04 09:52:13 +00:00
koenkk 04d8522b3b fix(ignore): fix dd1e0db407 2023-12-04 10:29:09 +01:00
Koen Kanters dd1e0db407 fix(ignore): Use zhc types (#20006)
* fix(ignore): Use zhc TS types

* updates
2023-12-04 10:05:05 +01:00
Koen KantersandKoenkk 2fa8afe37f fix(ignore): update zigbee-herdsman-converters to 16.0.0 (#20008)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-04 10:04:31 +01:00
Koen KantersandKoenkk 5afd16ac65 fix(ignore): update zigbee-herdsman-converters to 15.132.0 (#20005)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-03 21:01:30 +00:00
Koen KantersandKoenkk 9a2425a202 fix(ignore): update zigbee-herdsman to 0.25.2 (#19997)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-03 10:34:47 +00:00
Koen KantersandKoenkk af02370777 fix(ignore): update zigbee-herdsman-converters to 15.131.0 (#19987)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-02 21:10:40 +00:00
Jakub Jirutka 9c8aee17dc fix: Fix build on Node 20+ (#19983)
See https://github.com/bnoordhuis/node-unix-dgram/issues/61
2023-12-02 20:37:15 +01:00
Koen KantersandKoenkk a822873748 fix(ignore): update zigbee-herdsman to 0.25.1 (#19985)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-02 19:18:59 +00:00
Koen KantersandKoenkk 6e843d0f1c fix(ignore): update dependencies (#19896)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-02 20:13:41 +01:00
Impact 346cb90e9e fix: Make update script work from anywhere (#19965)
Makes update script work from anywhere changing the current directory to where the file is located.
2023-12-02 19:48:17 +01:00
Koen Kanters 6619146028 chore: merge master to dev 2023-12-02 13:10:26 +01:00
koenkk 56589dccf1 chore: automate HA addon updates 2023-12-02 13:10:03 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 982af4b724 chore: Bump alpine from 3.18.4 to 3.18.5 in /docker (#19962)
Bumps alpine from 3.18.4 to 3.18.5.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 19:52:54 +01:00
Koen Kanters 7a516bba16 chore: merge master to dev 2023-12-01 11:47:50 +01:00
koenkk 2080e36f19 chore: fix release_please 2023-12-01 11:47:28 +01:00
Koen Kanters a71f43d75b chore: merge master to dev 2023-12-01 10:57:36 +01:00
Koenkk c20293078a chore: promote to dev 2023-12-01 09:57:12 +00:00
Koen Kanters 37636b243f 2023-12-01 10:57:11 +01:00
Koen Kanters aae7312d8a chore(dev): release 1.34.0 (#19544) 2023-12-01 10:54:40 +01:00
Koen KantersandKoenkk 3bbf89cba0 fix(ignore): update zigbee-herdsman-converters to 15.130.1 (#19945)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-01 10:51:26 +01:00
Koen KantersandKoenkk f976d4efb4 fix(ignore): update zigbee-herdsman-converters to 15.130.0 (#19942)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-01 09:25:52 +00:00
slugzero a904e7a396 fix: Fix unable to configure again after configure fails (#19908)
* Remove device from "configuring" after error

* use finally for cleanup
2023-11-30 23:02:16 +01:00
Koen KantersandKoenkk 12521add39 fix(ignore): update zigbee-herdsman-converters to 15.129.0 (#19934)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-30 19:37:15 +00:00
Koen KantersandKoenkk 2dabc1e61b fix(ignore): update zigbee-herdsman to 0.25.0 (#19933)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-30 18:51:16 +00:00
Koen KantersandKoenkk e5dc271a78 fix(ignore): update zigbee-herdsman to 0.24.0 (#19926)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-30 10:57:31 +00:00
Koen KantersandKoenkk 4e128e26fe fix(ignore): update zigbee-herdsman-converters to 15.128.0 (#19906)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-28 19:05:56 +00:00
Koen KantersandKoenkk b4c0a59501 Update dependencies (#19895)
* fix(ignore): update dependencies

* fix

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-27 21:55:35 +01:00
Koen KantersandKoenkk 9032bb3d13 Update dependencies (#19866)
* fix(ignore): update dependencies

* fix

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-27 21:36:06 +01:00
Koen KantersandKoenkk 8a6d90d70e fix(ignore): update zigbee-herdsman-converters to 15.127.0 (#19894)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-27 19:10:10 +00:00
Koen KantersandKoenkk b17487e6bb fix(ignore): update zigbee-herdsman-converters to 15.126.0 (#19878)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-26 20:36:41 +00:00
Der Mundschenk & Compagnie 838629ca42 feat: Add Home Assistant scene discovery (#19838)
* Add device info for Zigbee Groups

* Move Scene and getScenes to utils.ts

* Add auto-discovery for group scenes

* Update scene auto-discovery on change

* Add support for single endpoint scenes

* Add test for onScenesChanged

* Move Scene to types.d.ts

* Allow optional object_id_postfix key in payload

* Add scene name to object_id for nicer HA entity_id
2023-11-26 19:38:53 +01:00
Koen KantersandKoenkk 51a93418fa fix(ignore): update zigbee-herdsman-converters to 15.125.1 (#19867)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-26 08:42:21 +00:00
Koen KantersandKoenkk ef00a64bf8 fix(ignore): update zigbee-herdsman-converters to 15.125.0 (#19854)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-24 20:50:08 +00:00
Koen KantersandKoenkk ef4d9e1e55 fix(ignore): update zigbee-herdsman-converters to 15.124.0 (#19836)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-23 19:29:23 +00:00
Koen KantersandKoenkk 2d08baf620 fix(ignore): update zigbee-herdsman-converters to 15.123.1 (#19809)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-22 20:48:46 +00:00
koenkk 8cf41a137a fix(ignore): remove console.log in test 2023-11-22 21:42:44 +01:00
Koen Kantersandnurikk 61b08c94bf fix(ignore): update zigbee2mqtt-frontend to 0.6.147 (#19801)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-11-21 10:58:37 +00:00
Koen Kantersandnurikk c87ac9cc2b fix(ignore): update zigbee2mqtt-frontend to 0.6.146 (#19784)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-11-20 11:30:45 +00:00
Koen Kantersandnurikk ed20e2a1bc fix(ignore): update zigbee2mqtt-frontend to 0.6.145 (#19778)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-11-20 00:57:52 +00:00
Koen KantersandKoenkk d4e3aa9d97 fix(ignore): update zigbee-herdsman-converters to 15.121.0 (#19777)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-19 20:05:34 +00:00
Koen Kantersandnurikk 91aed7e98e fix(ignore): update zigbee2mqtt-frontend to 0.6.144 (#19776)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-11-19 19:51:41 +00:00
Koen KantersandKoenkk fd19c86cb1 fix(ignore): update zigbee-herdsman-converters to 15.120.0 (#19761)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-18 20:20:27 +00:00
Koen KantersandKoenkk ad4bed8921 fix(ignore): update zigbee-herdsman-converters to 15.119.0 (#19740)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-16 21:02:58 +00:00
koenkk 01d32cb230 fix(ignore): Fix color_temp_percent test 2023-11-16 21:55:07 +01:00
Koen KantersandKoenkk 746066c920 fix(ignore): update zigbee-herdsman-converters to 15.117.0 (#19727)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-15 19:46:18 +00:00
Koen KantersandKoenkk 4815ac7110 fix(ignore): update zigbee-herdsman-converters to 15.116.0 (#19714)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-14 20:11:34 +00:00
Koen Kanters 035e2a3023 chore: merge master to dev 2023-11-14 20:17:06 +01:00
koenkk 311ea070b1 chore: Add build step to update script https://github.com/Koenkk/zigbee2mqtt/pull/19232 2023-11-14 20:16:40 +01:00
dependabot[bot] 736caac46d chore: Bump actions/github-script from 6 to 7 (#19710) 2023-11-14 19:16:09 +01:00
Koen KantersandKoenkk 157dff14d3 fix(ignore): update zigbee-herdsman-converters to 15.115.0 (#19701)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-13 20:00:33 +00:00
Koen KantersandKoenkk a152bded93 fix(ignore): update zigbee-herdsman to 0.23.0 (#19700)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-13 19:34:48 +00:00
Koen KantersandKoenkk 6587812b72 fix(ignore): update zigbee-herdsman-converters to 15.114.0 (#19692)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-12 20:46:35 +00:00
Koen KantersandKoenkk c8848be981 fix(ignore): update dependencies (#19679)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-12 21:37:34 +01:00
Der Mundschenk & Compagnie baae082258 fix(ignore): Fixes quote type for update JSON attribute discovery (#19668) 2023-11-11 15:24:32 +01:00
Koen KantersandKoenkk c8dcd716ab fix(ignore): update zigbee-herdsman-converters to 15.113.0 (#19663)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-10 21:11:17 +00:00
Nicolas Thumann 45ac44dd06 fix: Make frontend listen on both IPv4 and IPv6 by default (#19660)
* Change: Set frontend host to null by default

* Change: Allow frontend to listen on port without host

* Change: Unit test

* Add: Unit test
2023-11-10 21:57:53 +01:00
Der Mundschenk & Compagnie da04d223b4 fix: Fix invalid JSON error in Home Assistant update entity (#19659) 2023-11-10 21:41:36 +01:00
Koen KantersandKoenkk d26ea1a411 fix(ignore): update zigbee-herdsman-converters to 15.112.0 (#19643)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-09 20:50:15 +00:00
Koen Kantersandnurikk 9d5d61754d fix(ignore): update zigbee2mqtt-frontend to 0.6.143 (#19642)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-11-09 20:42:43 +00:00
Koen Kanters 2bb77a31cf chore: merge master to dev 2023-11-09 20:36:22 +01:00
Ivan Belokobylskiy 86ae0ed90f fix: Set entity category config for Home Assistant temperature_display_mode sensor (#19631) 2023-11-09 20:36:00 +01:00
koenkk 2540fbd771 fix: Fix Home Assistant in_progress Erroneous JSON error https://github.com/Koenkk/zigbee2mqtt/issues/19597 2023-11-08 21:52:02 +01:00
Koen KantersandKoenkk 354ada5c4c fix(ignore): update zigbee-herdsman-converters to 15.111.0 (#19630)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-08 20:40:37 +00:00
Koen Kanters 0fec5958fa chore: merge master to dev 2023-11-08 20:13:23 +01:00
koenkk ccc08a3fc7 chore: Set days-before-stale to 100 2023-11-08 20:13:02 +01:00
Koen KantersandKoenkk 5889cdd9ab fix(ignore): update zigbee-herdsman-converters to 15.110.0 (#19619)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-07 21:04:25 +00:00
koenkk 93af087264 chore: fix changelog generator 2023-11-06 20:58:45 +01:00
Koen KantersandKoenkk 2d5bb2462e fix(ignore): update zigbee-herdsman to 0.22.1 (#19602)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-06 19:30:05 +00:00
Koen KantersandKoenkk b71aa0a3fd fix(ignore): update zigbee-herdsman-converters to 15.109.0 (#19601)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-06 19:12:45 +00:00
Koen KantersandKoenkk 1fd1686ee6 fix(ignore): update zigbee-herdsman-converters to 15.108.0 (#19589)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-05 20:25:57 +00:00
Koen KantersandKoenkk fc073e8743 fix(ignore): update dependencies (#19570)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-05 21:10:02 +01:00
Rotzbua be48d545cc fix(ignore): remove unused variable (#19584) 2023-11-05 21:02:15 +01:00
Rotzbua 13443a42ce fix(typo): correct misspell (#19581)
Credit to `codespell`.
2023-11-05 21:01:55 +01:00
koenkk 071842ef49 fix: Fix entity category for Home Assistant binary_sensor https://github.com/Koenkk/zigbee2mqtt/pull/19474 2023-11-05 20:56:06 +01:00
Koen KantersandKoenkk 366e29d4d9 fix(ignore): update zigbee-herdsman-converters to 15.107.0 (#19548)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-03 19:31:32 +00:00
Koen KantersandKoenkk 02a5a1b14f fix(ignore): update zigbee-herdsman to 0.22.0 (#19547)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-03 20:20:30 +01:00
Arne Rekdal 64b26e47da fix: Map climate idle to idle instead of off in Home Assistant (#19545)
* Missing HA Climate Idle state

Idle is mapped to idle state in HA

* Update homeassistant.test.js
2023-11-03 20:14:25 +01:00
Laxilef 1f2bd841c4 fix: Fix in_progress attribute for Home Assistant update sensor (#19542) 2023-11-03 17:16:57 +01:00
koenkk 1e905598f2 chore: fix release-please 2023-11-01 20:33:51 +01:00
koenkk e76b0ff282 chore: promote to dev 2023-11-01 20:23:03 +01:00
58 changed files with 2692 additions and 9102 deletions
+5 -17
View File
@@ -7,7 +7,7 @@ body:
attributes:
value: |
**IMPORTANT:** Before submitting:
- Make sure this device is not already supported in the [dev branch](https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html)
- Make sure this device is not already supported in the dev branch by checking the [dev branch changelog](https://gist.github.com/Koenkk/bfd4c3d1725a2cccacc11d6ba51008ba#new-supported-devices)
- Make sure there is no existing issue or PR for this device already, search for your device here: https://github.com/Koenkk/zigbee2mqtt/issues
- Follow this [guide](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html)
- If you are using the Home Assistant addon and are still on 1.18.1, check the first point of [this](https://github.com/Koenkk/zigbee2mqtt/releases/tag/1.19.0)
@@ -36,20 +36,8 @@ body:
required: true
- type: textarea
attributes:
label: External converter
description: In case you tried to create an external converter, put it below
label: External defintion
description: See [Creating the external definition](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html#_2-creating-the-external-definition)
render: shell
- type: markdown
attributes:
value: |
# Lights
In case this device is a light, fill in the fields below:
- type: input
attributes:
label: Supported color modes
placeholder: color temperature, color (rgb)
- 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).
placeholder: '{"colorTempPhysicalMin":153,"colorTempPhysicalMax":454}'
validations:
required: true
+1 -1
View File
@@ -83,7 +83,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [16, 18, 20]
node: [18, 20]
runs-on: ${{ matrix.os }}
continue-on-error: true
steps:
+3 -3
View File
@@ -16,8 +16,8 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Delete untagged images
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
+27 -25
View File
@@ -20,15 +20,12 @@ jobs:
with:
node-version: 20
- uses: google-github-actions/release-please-action@v3
- uses: google-github-actions/release-please-action@v4
id: release
with:
release-type: node
package-name: release-please-action
default-branch: dev
target-branch: dev
token: ${{secrets.GH_TOKEN}}
include-v-in-tag: false
skip-github-release: true
# Checkout repos
- uses: actions/checkout@v4
@@ -53,10 +50,11 @@ jobs:
MASTER_Z2M_VERSION=$(cat z2m-master/package.json | jq -r '.version')
MASTER_ZHC_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee-herdsman-converters"')
MASTER_ZH_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee-herdsman"')
MASTER_FRONTEND_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee2mqtt-frontend"')
wget -q -O - https://raw.githubusercontent.com/Koenkk/zigbee2mqtt/release-please--branches--dev--components--release-please-action/CHANGELOG.md > z2m/CHANGELOG.md || true
cd z2m
npm ci
node scripts/generateChangelog.js $MASTER_Z2M_VERSION $MASTER_ZHC_VERSION $MASTER_ZH_VERSION >> ../changelog.md
node scripts/generateChangelog.js $MASTER_Z2M_VERSION $MASTER_ZHC_VERSION $MASTER_ZH_VERSION $MASTER_FRONTEND_VERSION >> ../changelog.md
env:
GH_TOKEN: ${{secrets.GH_TOKEN}}
- name: Update changelog in release PR
@@ -65,7 +63,7 @@ jobs:
env:
GH_TOKEN: ${{secrets.GH_TOKEN}}
- name: Upload changelog
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: changelog
path: changelog.md
@@ -85,25 +83,13 @@ jobs:
with:
ref: dev
token: ${{secrets.GH_TOKEN}}
- name: Download changelog
uses: actions/download-artifact@v3
with:
name: changelog
path: ../changelog
- uses: ncipollo/release-action@v1
name: Create release
with:
bodyFile: ../changelog/changelog.md
draft: true
name: ${{ needs.release-please.outputs.version }}
tag: ${{ needs.release-please.outputs.version }}
- name: Merge dev -> master
uses: devmasx/merge-branch@master
with:
type: now
from_branch: dev
target_branch: master
github_token: ${{secrets.GH_TOKEN}}
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git checkout master
git merge -m 'chore: merge dev to master'
git push origin master
- name: Promote to dev
run: |
jq '.version = "${{ needs.release-please.outputs.version }}-dev"' package.json > package.json.tmp
@@ -113,3 +99,19 @@ jobs:
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: promote to dev"
- name: Download changelog
uses: actions/download-artifact@v4
with:
name: changelog
path: ../changelog
- name: update release
id: update_release
uses: tubone24/update_release@v1.0
env:
GITHUB_TOKEN: ${{ github.token }}
TAG_NAME: ${{ needs.release-please.outputs.version }}
with:
body_path: ../changelog/changelog.md
- name: Trigger HA addon update
run: |
curl -XPOST -H "Authorization: token ${{ secrets.GH_TOKEN }}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/zigbee2mqtt/hassio-zigbee2mqtt/dispatches --data "{\"event_type\": \"release\", \"client_payload\": { \"version\": \"${{ needs.release-please.outputs.version }}-1\"}}"
+3 -3
View File
@@ -9,12 +9,12 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days'
stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days'
days-before-stale: 30
days-before-close: 7
days-before-stale: 180
days-before-close: 30
exempt-issue-labels: dont-stale
operations-per-run: 500
+3 -3
View File
@@ -22,9 +22,9 @@ jobs:
with:
node-version: 20
cache: npm
# connect-gzip-static only supports node 16
# @types/ws 8.5.5 is broken
- run: npx npm-check-updates -u -x connect-gzip-static -x @types/ws
# connect-gzip-static@4.0.0 requires Node 20 >=
- run: npx npm-check-updates -u -x connect-gzip-static
- run: rm -f package-lock.json
- run: npm install
- uses: peter-evans/create-pull-request@v5
with:
+3 -2
View File
@@ -1,4 +1,5 @@
{
".": "1.33.2",
"bootstrap-sha": "7ee207fc0e05f32c9636c0298b48eea1b91ed9e1"
".": "1.35.0",
"bootstrap-sha": "7ee207fc0e05f32c9636c0298b48eea1b91ed9e1",
"include-v-in-tag": false
}
+146
View File
@@ -1,5 +1,151 @@
# Changelog
## [1.35.0](https://github.com/Koenkk/zigbee2mqtt/compare/1.34.0...v1.35.0) (2024-01-01)
### Features
* Support automatically generating device definitions ([#20358](https://github.com/Koenkk/zigbee2mqtt/issues/20358)) ([56f5762](https://github.com/Koenkk/zigbee2mqtt/commit/56f57625b0d5ce5b3bf6fa6a1a048f6ae8902311))
* Support bridge discovery for Home Assistant ([#19994](https://github.com/Koenkk/zigbee2mqtt/issues/19994)) ([f11eb24](https://github.com/Koenkk/zigbee2mqtt/commit/f11eb2459bd1ad89a1588b8d2d023d949faf3313))
### Bug Fixes
* Add Home Assistant `entity_category` for various Ubisys D1 entities ([#20393](https://github.com/Koenkk/zigbee2mqtt/issues/20393)) ([085b46c](https://github.com/Koenkk/zigbee2mqtt/commit/085b46c995c888751ca5a1ff73391b8f56591826))
* Allow `null` for `zigbee_ota_override_index_location` option ([#20151](https://github.com/Koenkk/zigbee2mqtt/issues/20151)) ([48347a5](https://github.com/Koenkk/zigbee2mqtt/commit/48347a51dcff3ce8ed533c71cc27651bc884228b))
* Clear old Home Assistant discovery topics when exposes are changed ([#20313](https://github.com/Koenkk/zigbee2mqtt/issues/20313)) ([a5b959a](https://github.com/Koenkk/zigbee2mqtt/commit/a5b959a6d520679bce1586c50f01a57f13d6af01))
* Don't crash on startup when external converters fails to load https://github.com/Koenkk/zigbee2mqtt/issues/20010 ([8ab96e1](https://github.com/Koenkk/zigbee2mqtt/commit/8ab96e124fa5e24e504b2a11ae6cf9836d93f5c8))
* Fix availability checks not stopped on extension stop ([#20093](https://github.com/Koenkk/zigbee2mqtt/issues/20093)) ([5aeec13](https://github.com/Koenkk/zigbee2mqtt/commit/5aeec130b19e112ad52cbc506a810dcdfc2e2605))
* Fix build on Node 20+ ([#19983](https://github.com/Koenkk/zigbee2mqtt/issues/19983)) ([9c8aee1](https://github.com/Koenkk/zigbee2mqtt/commit/9c8aee17dc9ee0cea311d99c282f1558d2fb841b))
* Fix Home Assistant `Entity category `config` is invalid for sensors` warning https://github.com/Koenkk/zigbee2mqtt/issues/20252 ([8f62a0a](https://github.com/Koenkk/zigbee2mqtt/commit/8f62a0a61899837c63d7053efd68b7a315d623ac))
* Fix load of empty configuration `.yaml` file failing ([#20289](https://github.com/Koenkk/zigbee2mqtt/issues/20289)) ([b67888f](https://github.com/Koenkk/zigbee2mqtt/commit/b67888f938e9e04b8547d7569b78058ba3fb017b))
* **ignore:** Ability to use a specified endpoint in the Tz.Converter ([#20356](https://github.com/Koenkk/zigbee2mqtt/issues/20356)) ([d702919](https://github.com/Koenkk/zigbee2mqtt/commit/d702919735c2f399d73c3fb902721e4b5367b320))
* **ignore:** Changes for zhc 18.0.0 ([#20390](https://github.com/Koenkk/zigbee2mqtt/issues/20390)) ([e6e3d92](https://github.com/Koenkk/zigbee2mqtt/commit/e6e3d92971162494ff999f74d13b00429b164c51))
* **ignore:** Downgrade connect-gzip-static to 3.0.1 https://github.com/Koenkk/zigbee2mqtt/issues/20149 ([f64c00c](https://github.com/Koenkk/zigbee2mqtt/commit/f64c00c8d34dae64279a607368c82c06dc9dc53a))
* **ignore:** Fix "Permit join timeout" template ([#20096](https://github.com/Koenkk/zigbee2mqtt/issues/20096)) ([59efc15](https://github.com/Koenkk/zigbee2mqtt/commit/59efc15f3adb58954f3a9eb524d728679fa28e77))
* **ignore:** fix bind test ([d150105](https://github.com/Koenkk/zigbee2mqtt/commit/d1501052ad1dc7d1cdf9e0f925c3e9694c748226))
* **ignore:** fix dd1e0db407f80add7355879823cf519ae869a7f6 ([04d8522](https://github.com/Koenkk/zigbee2mqtt/commit/04d8522b3ba95f83e0b569373726e4da7a1ece13))
* **ignore:** fix dee8781e51f2aac466e273fb81cbdd31da56ac18 ([32b0ded](https://github.com/Koenkk/zigbee2mqtt/commit/32b0ded17c781fea0474c9928d795a3eb3c81acf))
* **ignore:** fix homeassistant.test.js ([dee8781](https://github.com/Koenkk/zigbee2mqtt/commit/dee8781e51f2aac466e273fb81cbdd31da56ac18))
* **ignore:** Fix OTA sensor discoverd while device doesn't support it https://github.com/Koenkk/zigbee2mqtt/issues/20131 ([abde909](https://github.com/Koenkk/zigbee2mqtt/commit/abde909b7106ffdc0d841c44b9791aaafd874cc2))
* **ignore:** improve 8f62a0a ([2e704f3](https://github.com/Koenkk/zigbee2mqtt/commit/2e704f3ff7ac1dbe1b6f12c7cdace29850a288cf))
* **ignore:** Revert module to commonjs ([6b6cc8a](https://github.com/Koenkk/zigbee2mqtt/commit/6b6cc8aa34ec8b63db332cd8ad33d3592e6c515b))
* **ignore:** Update alpine to 3.19.0 ([#20392](https://github.com/Koenkk/zigbee2mqtt/issues/20392)) ([eaaf1b0](https://github.com/Koenkk/zigbee2mqtt/commit/eaaf1b0b4f6ebd631117ae13666e5e0ebcb89b23))
* **ignore:** update dependencies ([#19896](https://github.com/Koenkk/zigbee2mqtt/issues/19896)) ([6e843d0](https://github.com/Koenkk/zigbee2mqtt/commit/6e843d0f1cdf0e76a2a07cf3d33f3287f0a5e70c))
* **ignore:** update dependencies ([#20023](https://github.com/Koenkk/zigbee2mqtt/issues/20023)) ([e0deef8](https://github.com/Koenkk/zigbee2mqtt/commit/e0deef8889835949af99e4dfd4c47a182d329514))
* **ignore:** update dependencies ([#20133](https://github.com/Koenkk/zigbee2mqtt/issues/20133)) ([b415443](https://github.com/Koenkk/zigbee2mqtt/commit/b415443126e3ee0bda25a9fee8e37615c2290b2e))
* **ignore:** update dependencies ([#20249](https://github.com/Koenkk/zigbee2mqtt/issues/20249)) ([6e52b2a](https://github.com/Koenkk/zigbee2mqtt/commit/6e52b2a7af0ec9bd636a9be12ff7aa16e2c37f04))
* **ignore:** update dependencies ([#20256](https://github.com/Koenkk/zigbee2mqtt/issues/20256)) ([1e4ac34](https://github.com/Koenkk/zigbee2mqtt/commit/1e4ac34c443e29df9631b97ed83c407c2c174af1))
* **ignore:** update dependencies ([#20257](https://github.com/Koenkk/zigbee2mqtt/issues/20257)) ([19754c6](https://github.com/Koenkk/zigbee2mqtt/commit/19754c69e51d642dd9360aa27c3d0a121f8d5d61))
* **ignore:** update dependencies ([#20368](https://github.com/Koenkk/zigbee2mqtt/issues/20368)) ([0385791](https://github.com/Koenkk/zigbee2mqtt/commit/03857916c455a08c42bc241bc643af7a91061b5d))
* **ignore:** update dependencies ([#20452](https://github.com/Koenkk/zigbee2mqtt/issues/20452)) ([16730b1](https://github.com/Koenkk/zigbee2mqtt/commit/16730b14a1d1de396fd8f6260567a1da4afae623))
* **ignore:** Update tsconfig for Node 18 ([084b048](https://github.com/Koenkk/zigbee2mqtt/commit/084b048ff389261edbd72f05e0c461e725574b6e))
* **ignore:** update zigbee-herdsman to 0.25.1 ([#19985](https://github.com/Koenkk/zigbee2mqtt/issues/19985)) ([a822873](https://github.com/Koenkk/zigbee2mqtt/commit/a8228737480dac0d61768131c7b4850a614db4fc))
* **ignore:** update zigbee-herdsman to 0.25.2 ([#19997](https://github.com/Koenkk/zigbee2mqtt/issues/19997)) ([9a2425a](https://github.com/Koenkk/zigbee2mqtt/commit/9a2425a202bc70a6d7f4ba534e145bcb3736f041))
* **ignore:** update zigbee-herdsman to 0.25.3 ([#20025](https://github.com/Koenkk/zigbee2mqtt/issues/20025)) ([483acc3](https://github.com/Koenkk/zigbee2mqtt/commit/483acc3ca156e441bb82d9e8040ccf9dc7a5e1e5))
* **ignore:** update zigbee-herdsman to 0.26.0 ([#20092](https://github.com/Koenkk/zigbee2mqtt/issues/20092)) ([8929ab3](https://github.com/Koenkk/zigbee2mqtt/commit/8929ab3b9704e924fc2f0f5e84e6d15e5c14a8b5))
* **ignore:** update zigbee-herdsman to 0.26.1 ([#20143](https://github.com/Koenkk/zigbee2mqtt/issues/20143)) ([a26008f](https://github.com/Koenkk/zigbee2mqtt/commit/a26008f74c7a8ef138e51debc79965fde53ca61b))
* **ignore:** update zigbee-herdsman to 0.27.1 ([#20262](https://github.com/Koenkk/zigbee2mqtt/issues/20262)) ([65d7f5f](https://github.com/Koenkk/zigbee2mqtt/commit/65d7f5f3e27ad4a8dbe2abeca1f5ffa349c9631b))
* **ignore:** update zigbee-herdsman to 0.28.0 ([#20357](https://github.com/Koenkk/zigbee2mqtt/issues/20357)) ([89f8e43](https://github.com/Koenkk/zigbee2mqtt/commit/89f8e43ad11ffd84cf6a3c9d3bd57d5577063e15))
* **ignore:** update zigbee-herdsman to 0.29.0 ([#20403](https://github.com/Koenkk/zigbee2mqtt/issues/20403)) ([03a1ce3](https://github.com/Koenkk/zigbee2mqtt/commit/03a1ce3895cbc9dafb1c83a7013e1f96409db05a))
* **ignore:** update zigbee-herdsman to 0.29.1 ([#20406](https://github.com/Koenkk/zigbee2mqtt/issues/20406)) ([7bca19d](https://github.com/Koenkk/zigbee2mqtt/commit/7bca19dae777eb919a0017b4c52304ec83972e73))
* **ignore:** update zigbee-herdsman to 0.30.0 ([#20464](https://github.com/Koenkk/zigbee2mqtt/issues/20464)) ([05c65d3](https://github.com/Koenkk/zigbee2mqtt/commit/05c65d31ed76f0f5f080518a4196ca7999a41adc))
* **ignore:** update zigbee-herdsman-converters to 15.131.0 ([#19987](https://github.com/Koenkk/zigbee2mqtt/issues/19987)) ([af02370](https://github.com/Koenkk/zigbee2mqtt/commit/af02370777fc5e81363810c759359e5ba17e3bf7))
* **ignore:** update zigbee-herdsman-converters to 15.132.0 ([#20005](https://github.com/Koenkk/zigbee2mqtt/issues/20005)) ([5afd16a](https://github.com/Koenkk/zigbee2mqtt/commit/5afd16ac652560250ec9a21f4d05fba7e25af853))
* **ignore:** update zigbee-herdsman-converters to 16.0.0 ([#20008](https://github.com/Koenkk/zigbee2mqtt/issues/20008)) ([2fa8afe](https://github.com/Koenkk/zigbee2mqtt/commit/2fa8afe37f99425234b2841a3ffd8c954a7b78c6))
* **ignore:** update zigbee-herdsman-converters to 16.0.1 ([#20012](https://github.com/Koenkk/zigbee2mqtt/issues/20012)) ([5e3bd75](https://github.com/Koenkk/zigbee2mqtt/commit/5e3bd75f098b28a5df544ef5b06eab9f35020419))
* **ignore:** update zigbee-herdsman-converters to 16.0.2 ([#20027](https://github.com/Koenkk/zigbee2mqtt/issues/20027)) ([e60b94b](https://github.com/Koenkk/zigbee2mqtt/commit/e60b94b55329e30f00c1dec5094f139233d08521))
* **ignore:** update zigbee-herdsman-converters to 16.1.0 ([#20059](https://github.com/Koenkk/zigbee2mqtt/issues/20059)) ([cb74db4](https://github.com/Koenkk/zigbee2mqtt/commit/cb74db47f7140357796618350c1ae73b7b1d4d1a))
* **ignore:** update zigbee-herdsman-converters to 16.11.0 ([#20228](https://github.com/Koenkk/zigbee2mqtt/issues/20228)) ([2c5d3e1](https://github.com/Koenkk/zigbee2mqtt/commit/2c5d3e14f779ff138949f11f6a443fd8821e0120))
* **ignore:** update zigbee-herdsman-converters to 16.12.0 ([#20258](https://github.com/Koenkk/zigbee2mqtt/issues/20258)) ([c26a6a2](https://github.com/Koenkk/zigbee2mqtt/commit/c26a6a28e4562d7cc97763f6c02782525575f84f))
* **ignore:** update zigbee-herdsman-converters to 16.13.0 ([#20280](https://github.com/Koenkk/zigbee2mqtt/issues/20280)) ([29635a9](https://github.com/Koenkk/zigbee2mqtt/commit/29635a9b6aedd8f140c126a36813bb922f6a450e))
* **ignore:** update zigbee-herdsman-converters to 16.14.1 ([#20301](https://github.com/Koenkk/zigbee2mqtt/issues/20301)) ([af8c2d8](https://github.com/Koenkk/zigbee2mqtt/commit/af8c2d86fe1b50934ec8ccd675b298dc2f86a29a))
* **ignore:** update zigbee-herdsman-converters to 16.15.0 ([#20318](https://github.com/Koenkk/zigbee2mqtt/issues/20318)) ([27ccd1a](https://github.com/Koenkk/zigbee2mqtt/commit/27ccd1a5704b366f86b4c708865831abe40cd2ad))
* **ignore:** update zigbee-herdsman-converters to 16.16.0 ([#20326](https://github.com/Koenkk/zigbee2mqtt/issues/20326)) ([6875146](https://github.com/Koenkk/zigbee2mqtt/commit/687514621152c48809d922af9e712a45c762173d))
* **ignore:** update zigbee-herdsman-converters to 16.17.0 ([#20359](https://github.com/Koenkk/zigbee2mqtt/issues/20359)) ([1a72650](https://github.com/Koenkk/zigbee2mqtt/commit/1a7265017a0b7facbe88058daa1ef47fcc79747e))
* **ignore:** update zigbee-herdsman-converters to 16.18.0 ([#20374](https://github.com/Koenkk/zigbee2mqtt/issues/20374)) ([fe7e82f](https://github.com/Koenkk/zigbee2mqtt/commit/fe7e82f7d34db4d79448f198f51902592633fbc1))
* **ignore:** update zigbee-herdsman-converters to 16.2.0 ([#20094](https://github.com/Koenkk/zigbee2mqtt/issues/20094)) ([cb50942](https://github.com/Koenkk/zigbee2mqtt/commit/cb509426eca455d693723b5631313163b931dbc9))
* **ignore:** update zigbee-herdsman-converters to 16.3.1 ([#20109](https://github.com/Koenkk/zigbee2mqtt/issues/20109)) ([4e6bc33](https://github.com/Koenkk/zigbee2mqtt/commit/4e6bc33a2203591a9bcee84eb90cefdb9c883c69))
* **ignore:** update zigbee-herdsman-converters to 16.4.0 ([#20120](https://github.com/Koenkk/zigbee2mqtt/issues/20120)) ([4cc88f1](https://github.com/Koenkk/zigbee2mqtt/commit/4cc88f1e16e5bd8e901c555c6950f14d46013787))
* **ignore:** update zigbee-herdsman-converters to 16.5.0 ([#20125](https://github.com/Koenkk/zigbee2mqtt/issues/20125)) ([5ff10c3](https://github.com/Koenkk/zigbee2mqtt/commit/5ff10c3e5ced22a6070360f76ea9ffd7e9805194))
* **ignore:** update zigbee-herdsman-converters to 16.6.0 ([#20145](https://github.com/Koenkk/zigbee2mqtt/issues/20145)) ([87f6b9e](https://github.com/Koenkk/zigbee2mqtt/commit/87f6b9e81a4efaa857c06ba179665535e1beb742))
* **ignore:** update zigbee-herdsman-converters to 16.7.0 ([#20167](https://github.com/Koenkk/zigbee2mqtt/issues/20167)) ([7c81eda](https://github.com/Koenkk/zigbee2mqtt/commit/7c81eda46a1b7d3ce190a11f89618e60afaba7b5))
* **ignore:** update zigbee-herdsman-converters to 16.8.0 ([#20185](https://github.com/Koenkk/zigbee2mqtt/issues/20185)) ([bcf50bd](https://github.com/Koenkk/zigbee2mqtt/commit/bcf50bd0c532d822768d380cb99f793e00db07f0))
* **ignore:** update zigbee-herdsman-converters to 16.9.0 ([#20200](https://github.com/Koenkk/zigbee2mqtt/issues/20200)) ([25de4fd](https://github.com/Koenkk/zigbee2mqtt/commit/25de4fded9bd67bafb2e8a596490988c624dce88))
* **ignore:** update zigbee-herdsman-converters to 17.0.0 ([#20378](https://github.com/Koenkk/zigbee2mqtt/issues/20378)) ([2110954](https://github.com/Koenkk/zigbee2mqtt/commit/2110954bb8c47b4fb9b3b536e857183cb4fc83ac))
* **ignore:** update zigbee-herdsman-converters to 18.0.0 ([#20391](https://github.com/Koenkk/zigbee2mqtt/issues/20391)) ([bf744f4](https://github.com/Koenkk/zigbee2mqtt/commit/bf744f48dc820f46f9944cf52513437908586d79))
* **ignore:** update zigbee-herdsman-converters to 18.1.0 ([#20426](https://github.com/Koenkk/zigbee2mqtt/issues/20426)) ([a3a87f4](https://github.com/Koenkk/zigbee2mqtt/commit/a3a87f4580ad4b2a77d1c039232035f7cf5797ac))
* **ignore:** update zigbee-herdsman-converters to 18.2.0 ([#20451](https://github.com/Koenkk/zigbee2mqtt/issues/20451)) ([a8c791b](https://github.com/Koenkk/zigbee2mqtt/commit/a8c791b0302cb885671bdfd0dd785a83185dffe0))
* **ignore:** update zigbee-herdsman-converters to 18.3.0 ([#20465](https://github.com/Koenkk/zigbee2mqtt/issues/20465)) ([f9e0c20](https://github.com/Koenkk/zigbee2mqtt/commit/f9e0c2058fe15517d6b17445eb48ad6c9777c3db))
* **ignore:** update zigbee2mqtt-frontend to 0.6.148 ([#20034](https://github.com/Koenkk/zigbee2mqtt/issues/20034)) ([ffcb820](https://github.com/Koenkk/zigbee2mqtt/commit/ffcb8202f01d9ba627a362abdbc350793e269647))
* **ignore:** update zigbee2mqtt-frontend to 0.6.149 ([#20141](https://github.com/Koenkk/zigbee2mqtt/issues/20141)) ([c7e2944](https://github.com/Koenkk/zigbee2mqtt/commit/c7e294482bfd7dacb5fc70858041141070a6971d))
* **ignore:** update zigbee2mqtt-frontend to 0.6.150 ([#20377](https://github.com/Koenkk/zigbee2mqtt/issues/20377)) ([d616e57](https://github.com/Koenkk/zigbee2mqtt/commit/d616e57daf89f36b145a46bec01b80deaaaae5a7))
* **ignore:** Use zhc types ([#20006](https://github.com/Koenkk/zigbee2mqtt/issues/20006)) ([dd1e0db](https://github.com/Koenkk/zigbee2mqtt/commit/dd1e0db407f80add7355879823cf519ae869a7f6))
* Make update script work from anywhere ([#19965](https://github.com/Koenkk/zigbee2mqtt/issues/19965)) ([346cb90](https://github.com/Koenkk/zigbee2mqtt/commit/346cb90e9e78fd08ffb49cb954bcbee2f3bc9191))
* Only republish changed scenes to Home Assistant when changed ([#20248](https://github.com/Koenkk/zigbee2mqtt/issues/20248)) ([e0f5021](https://github.com/Koenkk/zigbee2mqtt/commit/e0f50218d70c1932fbc32f28f60737a4bff5f048))
* Prevent duplicate Home Assistant scene entities ([#20097](https://github.com/Koenkk/zigbee2mqtt/issues/20097)) ([a17f650](https://github.com/Koenkk/zigbee2mqtt/commit/a17f650ccbb8113874dc5dd5f5ad3e3419dc6c15))
* Support Node 21 and drop 16 ([adc0812](https://github.com/Koenkk/zigbee2mqtt/commit/adc08125fe892dfe456edf3a4d85da23b6f5798d))
## [1.34.0](https://github.com/Koenkk/zigbee2mqtt/compare/1.33.2...1.34.0) (2023-12-01)
### Features
* Add Home Assistant scene discovery ([#19838](https://github.com/Koenkk/zigbee2mqtt/issues/19838)) ([838629c](https://github.com/Koenkk/zigbee2mqtt/commit/838629ca42162d3d910b06bbc100b6270f75de33))
### Bug Fixes
* Fix `in_progress` attribute for Home Assistant update sensor ([#19542](https://github.com/Koenkk/zigbee2mqtt/issues/19542)) ([1f2bd84](https://github.com/Koenkk/zigbee2mqtt/commit/1f2bd841c4548777fc0c0f6ad6c54dfe913b3fd0))
* Fix entity category for Home Assistant `binary_sensor` https://github.com/Koenkk/zigbee2mqtt/pull/19474 ([071842e](https://github.com/Koenkk/zigbee2mqtt/commit/071842ef49eccc002efefbad3abf2780e6fdd0a2))
* Fix Home Assistant `in_progress` Erroneous JSON error https://github.com/Koenkk/zigbee2mqtt/issues/19597 ([2540fbd](https://github.com/Koenkk/zigbee2mqtt/commit/2540fbd771d01ee18cbb72c67cb0a24b44131912))
* Fix invalid JSON error in Home Assistant update entity ([#19659](https://github.com/Koenkk/zigbee2mqtt/issues/19659)) ([da04d22](https://github.com/Koenkk/zigbee2mqtt/commit/da04d223b446000df5cc59ccb38115eb3e5eac96))
* Fix unable to configure again after configure fails ([#19908](https://github.com/Koenkk/zigbee2mqtt/issues/19908)) ([a904e7a](https://github.com/Koenkk/zigbee2mqtt/commit/a904e7a396e34bbeef2e624702d55e9d0522b242))
* **ignore:** Fix color_temp_percent test ([01d32cb](https://github.com/Koenkk/zigbee2mqtt/commit/01d32cb2301f6db11f61bca113a2db8d86c7acc1))
* **ignore:** Fixes quote type for update JSON attribute discovery ([#19668](https://github.com/Koenkk/zigbee2mqtt/issues/19668)) ([baae082](https://github.com/Koenkk/zigbee2mqtt/commit/baae0822583fb29e079c57746b047cb602f017a7))
* **ignore:** remove console.log in test ([8cf41a1](https://github.com/Koenkk/zigbee2mqtt/commit/8cf41a137aa8275e61a244b0096796466b10648d))
* **ignore:** remove unused variable ([#19584](https://github.com/Koenkk/zigbee2mqtt/issues/19584)) ([be48d54](https://github.com/Koenkk/zigbee2mqtt/commit/be48d545cc03c95b0a6302b67cc04b4e7d187065))
* **ignore:** update dependencies ([#19570](https://github.com/Koenkk/zigbee2mqtt/issues/19570)) ([fc073e8](https://github.com/Koenkk/zigbee2mqtt/commit/fc073e8743c0ba9c05d0a952f6bf3ae6fa804f68))
* **ignore:** update dependencies ([#19679](https://github.com/Koenkk/zigbee2mqtt/issues/19679)) ([c8848be](https://github.com/Koenkk/zigbee2mqtt/commit/c8848be981c709ebb52bfb9c7347ab3a88cce82e))
* **ignore:** update zigbee-herdsman to 0.22.0 ([#19547](https://github.com/Koenkk/zigbee2mqtt/issues/19547)) ([02a5a1b](https://github.com/Koenkk/zigbee2mqtt/commit/02a5a1b14f4907e7d170ef3e3e4f93b176c4bbc5))
* **ignore:** update zigbee-herdsman to 0.22.1 ([#19602](https://github.com/Koenkk/zigbee2mqtt/issues/19602)) ([2d5bb24](https://github.com/Koenkk/zigbee2mqtt/commit/2d5bb2462eb547bc75df1bace2f01b6b15e6d171))
* **ignore:** update zigbee-herdsman to 0.23.0 ([#19700](https://github.com/Koenkk/zigbee2mqtt/issues/19700)) ([a152bde](https://github.com/Koenkk/zigbee2mqtt/commit/a152bded93877cfab3839b9b22e7d22ce43fb263))
* **ignore:** update zigbee-herdsman to 0.24.0 ([#19926](https://github.com/Koenkk/zigbee2mqtt/issues/19926)) ([e5dc271](https://github.com/Koenkk/zigbee2mqtt/commit/e5dc271a783aeec0c28e106f0f259a083d737d7c))
* **ignore:** update zigbee-herdsman to 0.25.0 ([#19933](https://github.com/Koenkk/zigbee2mqtt/issues/19933)) ([2dabc1e](https://github.com/Koenkk/zigbee2mqtt/commit/2dabc1e61b897fdb041a42bda7adc6acac6bb2b3))
* **ignore:** update zigbee-herdsman-converters to 15.107.0 ([#19548](https://github.com/Koenkk/zigbee2mqtt/issues/19548)) ([366e29d](https://github.com/Koenkk/zigbee2mqtt/commit/366e29d4d9126eaeca2476cc0fa56cf7faf70764))
* **ignore:** update zigbee-herdsman-converters to 15.108.0 ([#19589](https://github.com/Koenkk/zigbee2mqtt/issues/19589)) ([1fd1686](https://github.com/Koenkk/zigbee2mqtt/commit/1fd1686ee63cbd2b41d9d0a13669f9c665eb36da))
* **ignore:** update zigbee-herdsman-converters to 15.109.0 ([#19601](https://github.com/Koenkk/zigbee2mqtt/issues/19601)) ([b71aa0a](https://github.com/Koenkk/zigbee2mqtt/commit/b71aa0a3fd9a8e6a7fcc5e7bdb4a2f2b37cd690a))
* **ignore:** update zigbee-herdsman-converters to 15.110.0 ([#19619](https://github.com/Koenkk/zigbee2mqtt/issues/19619)) ([5889cdd](https://github.com/Koenkk/zigbee2mqtt/commit/5889cdd9abe6b41899a4a7422b696bde8bbb0390))
* **ignore:** update zigbee-herdsman-converters to 15.111.0 ([#19630](https://github.com/Koenkk/zigbee2mqtt/issues/19630)) ([354ada5](https://github.com/Koenkk/zigbee2mqtt/commit/354ada5c4c33d88677ce7f8b072b682dde6f19aa))
* **ignore:** update zigbee-herdsman-converters to 15.112.0 ([#19643](https://github.com/Koenkk/zigbee2mqtt/issues/19643)) ([d26ea1a](https://github.com/Koenkk/zigbee2mqtt/commit/d26ea1a4112f9e142ac72e2add82784012c28c51))
* **ignore:** update zigbee-herdsman-converters to 15.113.0 ([#19663](https://github.com/Koenkk/zigbee2mqtt/issues/19663)) ([c8dcd71](https://github.com/Koenkk/zigbee2mqtt/commit/c8dcd716ab271bdbe9d259943fb77d6d6accbc06))
* **ignore:** update zigbee-herdsman-converters to 15.114.0 ([#19692](https://github.com/Koenkk/zigbee2mqtt/issues/19692)) ([6587812](https://github.com/Koenkk/zigbee2mqtt/commit/6587812b725d28dc200eb42f6b0714ed1ed76e9f))
* **ignore:** update zigbee-herdsman-converters to 15.115.0 ([#19701](https://github.com/Koenkk/zigbee2mqtt/issues/19701)) ([157dff1](https://github.com/Koenkk/zigbee2mqtt/commit/157dff14d3ac32c70f13f5f7be0a85e2763bf2a6))
* **ignore:** update zigbee-herdsman-converters to 15.116.0 ([#19714](https://github.com/Koenkk/zigbee2mqtt/issues/19714)) ([4815ac7](https://github.com/Koenkk/zigbee2mqtt/commit/4815ac711003632ac21977b120dea7444ebeda3e))
* **ignore:** update zigbee-herdsman-converters to 15.117.0 ([#19727](https://github.com/Koenkk/zigbee2mqtt/issues/19727)) ([746066c](https://github.com/Koenkk/zigbee2mqtt/commit/746066c9205a7ab77393d1934aacb7f17209a1e0))
* **ignore:** update zigbee-herdsman-converters to 15.119.0 ([#19740](https://github.com/Koenkk/zigbee2mqtt/issues/19740)) ([ad4bed8](https://github.com/Koenkk/zigbee2mqtt/commit/ad4bed89215e1727f0966708f510b8c933ac1a32))
* **ignore:** update zigbee-herdsman-converters to 15.120.0 ([#19761](https://github.com/Koenkk/zigbee2mqtt/issues/19761)) ([fd19c86](https://github.com/Koenkk/zigbee2mqtt/commit/fd19c86cb19513dadae9a44ee8b50d287cc1860e))
* **ignore:** update zigbee-herdsman-converters to 15.121.0 ([#19777](https://github.com/Koenkk/zigbee2mqtt/issues/19777)) ([d4e3aa9](https://github.com/Koenkk/zigbee2mqtt/commit/d4e3aa9d976b67cb1519ca78c9b8de5e70247ad9))
* **ignore:** update zigbee-herdsman-converters to 15.123.1 ([#19809](https://github.com/Koenkk/zigbee2mqtt/issues/19809)) ([2d08baf](https://github.com/Koenkk/zigbee2mqtt/commit/2d08baf6204bde6f2ec90dba4a6a77963b14310c))
* **ignore:** update zigbee-herdsman-converters to 15.124.0 ([#19836](https://github.com/Koenkk/zigbee2mqtt/issues/19836)) ([ef4d9e1](https://github.com/Koenkk/zigbee2mqtt/commit/ef4d9e1e558561c1625d1a0d41ddc718d40b5c61))
* **ignore:** update zigbee-herdsman-converters to 15.125.0 ([#19854](https://github.com/Koenkk/zigbee2mqtt/issues/19854)) ([ef00a64](https://github.com/Koenkk/zigbee2mqtt/commit/ef00a64bf87adf38346461f5fa15757052400b2a))
* **ignore:** update zigbee-herdsman-converters to 15.125.1 ([#19867](https://github.com/Koenkk/zigbee2mqtt/issues/19867)) ([51a9341](https://github.com/Koenkk/zigbee2mqtt/commit/51a93418fa2de62e6691ef23a65725d28d9c8b42))
* **ignore:** update zigbee-herdsman-converters to 15.126.0 ([#19878](https://github.com/Koenkk/zigbee2mqtt/issues/19878)) ([b17487e](https://github.com/Koenkk/zigbee2mqtt/commit/b17487e6bb8d04bca0aaa5b0ad2338dc9eef46aa))
* **ignore:** update zigbee-herdsman-converters to 15.127.0 ([#19894](https://github.com/Koenkk/zigbee2mqtt/issues/19894)) ([8a6d90d](https://github.com/Koenkk/zigbee2mqtt/commit/8a6d90d70e9ebcb2eeeacf385f4c3d47adeebc88))
* **ignore:** update zigbee-herdsman-converters to 15.128.0 ([#19906](https://github.com/Koenkk/zigbee2mqtt/issues/19906)) ([4e128e2](https://github.com/Koenkk/zigbee2mqtt/commit/4e128e26fee314f2bdcce014132f85d5167ce0bf))
* **ignore:** update zigbee-herdsman-converters to 15.129.0 ([#19934](https://github.com/Koenkk/zigbee2mqtt/issues/19934)) ([12521ad](https://github.com/Koenkk/zigbee2mqtt/commit/12521add3968cc9ed5394cf128bf2917f75597a5))
* **ignore:** update zigbee-herdsman-converters to 15.130.0 ([#19942](https://github.com/Koenkk/zigbee2mqtt/issues/19942)) ([f976d4e](https://github.com/Koenkk/zigbee2mqtt/commit/f976d4efb48440ee7899490d31f14d820d71087d))
* **ignore:** update zigbee-herdsman-converters to 15.130.1 ([#19945](https://github.com/Koenkk/zigbee2mqtt/issues/19945)) ([3bbf89c](https://github.com/Koenkk/zigbee2mqtt/commit/3bbf89cba038c6bdfab08c3e2860e561b2845a43))
* **ignore:** update zigbee2mqtt-frontend to 0.6.143 ([#19642](https://github.com/Koenkk/zigbee2mqtt/issues/19642)) ([9d5d617](https://github.com/Koenkk/zigbee2mqtt/commit/9d5d61754d2808c83eee48dfc6a0e0c4c740002a))
* **ignore:** update zigbee2mqtt-frontend to 0.6.144 ([#19776](https://github.com/Koenkk/zigbee2mqtt/issues/19776)) ([91aed7e](https://github.com/Koenkk/zigbee2mqtt/commit/91aed7e98e65d1c1c1469e3bfaab74233f0002bf))
* **ignore:** update zigbee2mqtt-frontend to 0.6.145 ([#19778](https://github.com/Koenkk/zigbee2mqtt/issues/19778)) ([ed20e2a](https://github.com/Koenkk/zigbee2mqtt/commit/ed20e2a1bc0a474157880c2c54f7c2d656658901))
* **ignore:** update zigbee2mqtt-frontend to 0.6.146 ([#19784](https://github.com/Koenkk/zigbee2mqtt/issues/19784)) ([c87ac9c](https://github.com/Koenkk/zigbee2mqtt/commit/c87ac9cc2b1d360ff0962deed973545a9846fcd6))
* **ignore:** update zigbee2mqtt-frontend to 0.6.147 ([#19801](https://github.com/Koenkk/zigbee2mqtt/issues/19801)) ([61b08c9](https://github.com/Koenkk/zigbee2mqtt/commit/61b08c94bfd3dd58efdb22632476f410fac56d13))
* Make frontend listen on both IPv4 and IPv6 by default ([#19660](https://github.com/Koenkk/zigbee2mqtt/issues/19660)) ([45ac44d](https://github.com/Koenkk/zigbee2mqtt/commit/45ac44dd064a3f8aca87a25e7a0b2daafe53c5d3))
* Map climate `idle` to `idle` instead of `off` in Home Assistant ([#19545](https://github.com/Koenkk/zigbee2mqtt/issues/19545)) ([64b26e4](https://github.com/Koenkk/zigbee2mqtt/commit/64b26e47da3d765bfde8ccac8f9a1e304b28c697))
* Set entity category `config` for Home Assistant `temperature_display_mode` sensor ([#19631](https://github.com/Koenkk/zigbee2mqtt/issues/19631)) ([86ae0ed](https://github.com/Koenkk/zigbee2mqtt/commit/86ae0ed90fb1d94f5cb09545355968affa5654ca))
* **typo:** correct misspell ([#19581](https://github.com/Koenkk/zigbee2mqtt/issues/19581)) ([13443a4](https://github.com/Koenkk/zigbee2mqtt/commit/13443a42ce7239a7308efb829c8ff007e59ca45d))
## [1.33.2](https://github.com/Koenkk/zigbee2mqtt/compare/1.33.1...1.33.2) (2023-11-01)
-1
View File
@@ -6,6 +6,5 @@ module.exports = {
assumptions: {setPublicClassFields: true},
plugins: [
['@babel/plugin-proposal-decorators', {'legacy': true}],
['@babel/plugin-proposal-class-properties'],
],
};
+1 -1
View File
@@ -1,4 +1,4 @@
FROM alpine:3.18.4 as base
FROM alpine:3.18.5 as base
WORKDIR /app
RUN apk add --no-cache tzdata eudev tini nodejs
+2 -2
View File
@@ -121,7 +121,7 @@ export class Controller {
const devices = this.zigbee.devices(false);
logger.info(`Currently ${devices.length} devices are joined:`);
for (const device of devices) {
const model = device.definition ?
const model = device.isSupported ?
`${device.definition.model} - ${device.definition.vendor} ${device.definition.description}` :
'Not supported';
logger.info(`${device.name} (${device.ieeeAddr}): ${model} (${device.zh.type})`);
@@ -241,7 +241,7 @@ export class Controller {
if (entity.isDevice() && settings.get().mqtt.include_device_information) {
message.device = {
friendlyName: entity.name, model: entity.definition ? entity.definition.model : 'unknown',
friendlyName: entity.name, model: entity.definition?.model,
ieeeAddr: entity.ieeeAddr, networkAddress: entity.zh.networkAddress, type: entity.zh.type,
manufacturerID: entity.zh.manufacturerID,
powerSource: entity.zh.powerSource, applicationVersion: entity.zh.applicationVersion,
+21 -3
View File
@@ -4,6 +4,7 @@ import utils from '../util/utils';
import * as settings from '../util/settings';
import debounce from 'debounce';
import bind from 'bind-decorator';
import * as zhc from 'zigbee-herdsman-converters';
const retrieveOnReconnect = [
{keys: ['state']},
@@ -17,6 +18,7 @@ export default class Availability extends Extension {
private retrieveStateDebouncers: {[s: string]: () => void} = {};
private pingQueue: Device[] = [];
private pingQueueExecuting = false;
private stopped = false;
private getTimeout(device: Device): number {
if (typeof device.options.availability === 'object' && device.options.availability?.timeout != null) {
@@ -92,6 +94,11 @@ export default class Availability extends Extension {
}
}
if (this.stopped) {
// Exit here to avoid triggering any follow-up activity (e.g., re-queuing another ping attempt).
return;
}
this.publishAvailability(device, !pingedSuccessfully);
this.resetTimer(device);
this.removeFromPingQueue(device);
@@ -103,6 +110,10 @@ export default class Availability extends Extension {
}
override async start(): Promise<void> {
if (this.stopped) {
throw new Error('This extension cannot be restarted.');
}
this.eventBus.onEntityRenamed(this, (data) => {
if (utils.isAvailabilityEnabledForEntity(data.entity, settings.get())) {
this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 1});
@@ -183,8 +194,10 @@ export default class Availability extends Extension {
}
override async stop(): Promise<void> {
this.stopped = true;
this.pingQueue = [];
Object.values(this.timers).forEach((t) => clearTimeout(t));
super.stop();
await super.stop();
}
private retrieveState(device: Device): void {
@@ -199,8 +212,13 @@ export default class Availability extends Extension {
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})
const options: KeyValue = device.options;
const state = this.state.get(device);
const meta: zhc.Tz.Meta = {
message: this.state.get(device), mapped: device.definition, logger, endpoint_name: null,
options, state, device: device.zh,
};
await converter?.convertGet?.(device.endpoint(), item.keys[0], meta)
.catch((e) => {
logger.error(`Failed to read state of '${device.name}' after reconnect (${e.message})`);
});
+23 -27
View File
@@ -12,13 +12,13 @@ import Group from '../model/group';
import data from '../util/data';
import JSZip from 'jszip';
import fs from 'fs';
import * as zhc from 'zigbee-herdsman-converters';
const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);
type Scene = {id: number, name: string};
type DefinitionPayload = {
model: string, vendor: string, description: string, exposes: zhc.DefinitionExpose[], supports_ota:
boolean, icon: string, options: zhc.DefinitionExpose[],
model: string, vendor: string, description: string, exposes: zhc.Expose[], supports_ota:
boolean, icon: string, options: zhc.Expose[],
};
export default class Bridge extends Extension {
@@ -35,6 +35,7 @@ export default class Bridge extends Extension {
'device/options': this.deviceOptions,
'device/configure_reporting': this.deviceConfigureReporting,
'device/remove': this.deviceRemove,
'device/generate_external_definition': this.deviceGenerateExternalDefinition,
'device/rename': this.deviceRename,
'group/add': this.groupAdd,
'group/options': this.groupOptions,
@@ -100,7 +101,7 @@ export default class Bridge extends Extension {
const payload: KeyValue =
{friendly_name: data.device.name, status: data.status, ieee_address: data.device.ieeeAddr};
if (data.status === 'successful') {
payload.supported = !!data.device.definition;
payload.supported = data.device.isSupported;
payload.definition = this.getDefinitionPayload(data.device);
}
publishEvent('device_interview', payload);
@@ -451,6 +452,18 @@ export default class Bridge extends Extension {
}, null);
}
@bind async deviceGenerateExternalDefinition(message: string | KeyValue): Promise<MQTTResponse> {
if (typeof message !== 'object' || !message.hasOwnProperty('id')) {
throw new Error(`Invalid payload`);
}
const parsedID = utils.parseEntityID(message.id);
const device = this.getEntity('device', parsedID.ID) as Device;
const source = await zhc.generateExternalDefinitionSource(device.zh);
return utils.getResponse(message, {id: message.id, source}, null);
}
async renameEntity(entityType: 'group' | 'device', message: string | KeyValue): Promise<MQTTResponse> {
const deviceAndHasLast = entityType === 'device' && typeof message === 'object' && message.last === true;
if (typeof message !== 'object' || (!message.hasOwnProperty('from') && !deviceAndHasLast) ||
@@ -605,25 +618,6 @@ export default class Bridge extends Extension {
'bridge/info', stringify(payload), {retain: true, qos: 0}, settings.get().mqtt.base_topic, true);
}
private getScenes(entity: zh.Endpoint | zh.Group): Scene[] {
const scenes: {[id: number]: Scene} = {};
const endpoints = utils.isEndpoint(entity) ? [entity] : entity.members;
const groupID = utils.isEndpoint(entity) ? 0 : entity.groupID;
for (const endpoint of endpoints) {
for (const [key, data] of Object.entries(endpoint.meta?.scenes || {})) {
const split = key.split('_');
const sceneID = parseInt(split[0], 10);
const sceneGroupID = parseInt(split[1], 10);
if (sceneGroupID === groupID) {
scenes[sceneID] = {id: sceneID, name: (data as KeyValue).name || `Scene ${sceneID}`};
}
}
}
return Object.values(scenes);
}
async publishDevices(): Promise<void> {
interface Data {
bindings: {cluster: string, target: {type: string, endpoint?: number, ieee_address?: string, id?: number}}[]
@@ -636,7 +630,7 @@ export default class Bridge extends Extension {
const endpoints: {[s: number]: Data} = {};
for (const endpoint of device.zh.endpoints) {
const data: Data = {
scenes: this.getScenes(endpoint),
scenes: utils.getScenes(endpoint),
bindings: [],
configured_reportings: [],
clusters: {
@@ -669,7 +663,7 @@ export default class Bridge extends Extension {
ieee_address: device.ieeeAddr,
type: device.zh.type,
network_address: device.zh.networkAddress,
supported: !!device.definition,
supported: device.isSupported,
friendly_name: device.name,
disabled: !!device.options.disabled,
description: device.options.description,
@@ -695,7 +689,7 @@ export default class Bridge extends Extension {
id: g.ID,
friendly_name: g.ID === 901 ? 'default_bind_group' : g.name,
description: g.options.description,
scenes: this.getScenes(g.zh),
scenes: utils.getScenes(g.zh),
members: g.zh.members.map((e) => {
return {ieee_address: e.getDevice().ieeeAddr, endpoint: e.ID};
}),
@@ -707,7 +701,9 @@ export default class Bridge extends Extension {
getDefinitionPayload(device: Device): DefinitionPayload {
if (!device.definition) return null;
let icon = device.options.icon ? device.options.icon : device.definition.icon;
// @ts-expect-error icon is valid for external definitions
const definitionIcon = device.definition.icon;
let icon = device.options.icon ?? definitionIcon;
if (icon) {
icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(device.zh.modelID));
icon = icon.replace('${model}', utils.sanitizeImageParameter(device.definition.model));
+4 -5
View File
@@ -2,7 +2,7 @@ import * as settings from '../util/settings';
import utils from '../util/utils';
import logger from '../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import zhc from 'zigbee-herdsman-converters';
import * as zhc from 'zigbee-herdsman-converters';
import Extension from './extension';
import bind from 'bind-decorator';
import Device from '../model/device';
@@ -114,8 +114,7 @@ export default class Configure extends Extension {
logger.info(`Configuring '${device.name}'`);
try {
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger,
device.options);
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger);
logger.info(`Successfully configured '${device.name}'`);
device.zh.meta.configured = zhc.getConfigureKey(device.definition);
device.zh.save();
@@ -129,8 +128,8 @@ export default class Configure extends Extension {
if (throwError) {
throw error;
}
} finally {
this.configuring.delete(device.ieeeAddr);
}
this.configuring.delete(device.ieeeAddr);
}
}
+20 -6
View File
@@ -1,7 +1,8 @@
import zhc from 'zigbee-herdsman-converters';
import * as zhc from 'zigbee-herdsman-converters';
import * as settings from '../util/settings';
import utils from '../util/utils';
import {loadExternalConverter} from '../util/utils';
import Extension from './extension';
import logger from '../util/logger';
export default class ExternalConverters extends Extension {
constructor(zigbee: Zigbee, mqtt: MQTT, state: State, publishEntityState: PublishEntityState,
@@ -9,10 +10,23 @@ export default class ExternalConverters extends Extension {
restartCallback: () => void, addExtension: (extension: Extension) => Promise<void>) {
super(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension, restartCallback, addExtension);
for (const definition of utils.getExternalConvertersDefinitions(settings.get())) {
const toAdd = {...definition};
delete toAdd['homeassistant'];
zhc.addDeviceDefinition(toAdd);
for (const file of settings.get().external_converters) {
try {
for (const definition of loadExternalConverter(file)) {
const toAdd = {...definition};
delete toAdd['homeassistant'];
zhc.addDefinition(toAdd);
}
} catch (error) {
logger.error(`Failed to load external converter file '${file}' (${error.message})`);
logger.error(
`Probably there is a syntax error in the file or the external converter is not ` +
`compatible with the current Zigbee2MQTT version`);
logger.error(
`Note that external converters are not meant for long term usage, it's meant for local ` +
`testing after which a pull request should be created to add out-of-the-box support for the device`,
);
}
}
}
}
+4 -1
View File
@@ -74,7 +74,10 @@ export default class Frontend extends Extension {
this.wss = new WebSocket.Server({noServer: true});
this.wss.on('connection', this.onWebSocketConnection);
if (this.host.startsWith('/')) {
if (!this.host) {
this.server.listen(this.port);
logger.info(`Started frontend on port ${this.port}`);
} else if (this.host.startsWith('/')) {
this.server.listen(this.host);
logger.info(`Started frontend on socket ${this.host}`);
} else {
+2 -1
View File
@@ -7,13 +7,14 @@ import bind from 'bind-decorator';
import Extension from './extension';
import Device from '../model/device';
import Group from '../model/group';
import * as zhc from 'zigbee-herdsman-converters';
const topicRegex =
new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/(.+)/(remove|add|remove_all)$`);
const legacyTopicRegexRemoveAll = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/remove_all$`);
const stateProperties: {[s: string]: (value: string, exposes: zhc.DefinitionExpose[]) => boolean} = {
const stateProperties: {[s: string]: (value: string, exposes: zhc.Expose[]) => boolean} = {
'state': () => true,
'brightness': (value, exposes) =>
!!exposes.find((e) => e.type === 'light' && e.features.find((f) => f.name === 'brightness')),
+382 -63
View File
@@ -1,10 +1,11 @@
import * as settings from '../util/settings';
import logger from '../util/logger';
import utils from '../util/utils';
import utils, {isNumericExposeFeature, isBinaryExposeFeature, isEnumExposeFeature} from '../util/utils';
import stringify from 'json-stable-stringify-without-jsonify';
import assert from 'assert';
import Extension from './extension';
import bind from 'bind-decorator';
import * as zhc from 'zigbee-herdsman-converters';
interface MockProperty {property: string, value: KeyValue | string}
@@ -52,7 +53,7 @@ const legacyMapping = [
},
];
const featurePropertyWithoutEndpoint = (feature: zhc.DefinitionExposeFeature): string => {
const featurePropertyWithoutEndpoint = (feature: zhc.Feature): string => {
if (feature.endpoint) {
return feature.property.slice(0, -1 + -1 * feature.endpoint.length);
} else {
@@ -60,6 +61,47 @@ const featurePropertyWithoutEndpoint = (feature: zhc.DefinitionExposeFeature): s
}
};
/**
* This class handles the bridge entity configuration for Home Assistant Discovery.
*/
class Bridge {
private coordinatorIeeeAddress: string;
private coordinatorType: string;
private coordinatorFirmwareVersion: string;
private discoveryEntries: DiscoveryEntry[];
readonly options: {
ID?: string,
homeassistant?: KeyValue,
};
/* eslint-disable brace-style */
get ID(): string {return this.coordinatorIeeeAddress;}
get name(): string {return 'bridge';}
get hardwareVersion(): string {return this.coordinatorType;}
get firmwareVersion(): string {return this.coordinatorFirmwareVersion;}
get configs(): DiscoveryEntry[] {return this.discoveryEntries;}
constructor(ieeeAdress: string, version: zh.CoordinatorVersion, discovery: DiscoveryEntry[]) {
this.coordinatorIeeeAddress = ieeeAdress;
this.coordinatorType = version.type;
/* istanbul ignore next */
this.coordinatorFirmwareVersion = version.meta.revision ? `${version.meta.revision}` : '';
this.discoveryEntries = discovery;
this.options = {
ID: `bridge_${ieeeAdress}`,
homeassistant: {
name: `Zigbee2MQTT Bridge`,
},
};
}
isDevice(): this is Device {return false;}
isGroup(): this is Group {return false;}
/* eslint-enable brace-style */
}
/**
* This extensions handles integration with HomeAssistant
*/
@@ -72,6 +114,8 @@ export default class HomeAssistant extends Extension {
private entityAttributes = settings.get().homeassistant.legacy_entity_attributes;
private zigbee2MQTTVersion: string;
private discoveryOrigin: {name: string, sw: string, url: string};
private bridge: Bridge;
private bridgeIdentifier: string;
constructor(zigbee: Zigbee, mqtt: MQTT, state: State, publishEntityState: PublishEntityState,
eventBus: EventBus, enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
@@ -89,7 +133,8 @@ export default class HomeAssistant extends Extension {
this.zigbee2MQTTVersion = (await utils.getZigbee2MQTTVersion(false)).version;
this.discoveryOrigin = {name: 'Zigbee2MQTT', sw: this.zigbee2MQTTVersion, url: 'https://www.zigbee2mqtt.io'};
this.bridge = this.getBridgeEntity(await this.zigbee.getCoordinatorVersion());
this.bridgeIdentifier = this.getDevicePayload(this.bridge).identifiers[0];
this.eventBus.onDeviceRemoved(this, this.onDeviceRemoved);
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onEntityRenamed(this, this.onEntityRenamed);
@@ -99,6 +144,7 @@ export default class HomeAssistant extends Extension {
this.eventBus.onDeviceJoined(this, this.onZigbeeEvent);
this.eventBus.onDeviceInterview(this, this.onZigbeeEvent);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
this.eventBus.onScenesChanged(this, this.onScenesChanged);
this.eventBus.onEntityOptionsChanged(this, (data) => this.discover(data.entity, true));
this.mqtt.subscribe(this.statusTopic);
@@ -106,7 +152,7 @@ export default class HomeAssistant extends Extension {
this.mqtt.subscribe(`${this.discoveryTopic}/#`);
// MQTT discovery of all paired devices on startup.
for (const entity of [...this.zigbee.devices(false), ...this.zigbee.groups()]) {
for (const entity of [this.bridge, ...this.zigbee.devices(false), ...this.zigbee.groups()]) {
this.discover(entity, true);
}
@@ -114,8 +160,8 @@ export default class HomeAssistant extends Extension {
this.eventBus.emitPublishAvailability();
}
private exposeToConfig(exposes: zhc.DefinitionExpose[], entityType: 'device' | 'group',
allExposes: zhc.DefinitionExpose[], definition?: zhc.Definition): DiscoveryEntry[] {
private exposeToConfig(exposes: zhc.Expose[], entityType: 'device' | 'group',
allExposes: zhc.Expose[], definition?: zhc.Definition): DiscoveryEntry[] {
// For groups an array of exposes (of the same type) is passed, this is to determine e.g. what features
// to use for a bulb (e.g. color_xy/color_temp)
assert(entityType === 'group' || exposes.length === 1, 'Multiple exposes for device not allowed');
@@ -125,7 +171,7 @@ export default class HomeAssistant extends Extension {
const discoveryEntries: DiscoveryEntry[] = [];
const endpoint = entityType === 'device' ? exposes[0].endpoint : undefined;
const getProperty = (feature: zhc.DefinitionExposeFeature): string => entityType === 'group' ?
const getProperty = (feature: zhc.Feature): string => entityType === 'group' ?
featurePropertyWithoutEndpoint(feature) : feature.property;
/* istanbul ignore else */
@@ -169,7 +215,7 @@ export default class HomeAssistant extends Extension {
if (hasColorTemp) {
const colorTemps = exposes.map((expose) => expose.features.find((e) => e.name === 'color_temp'))
.filter((e) => e);
.filter((e) => e).filter(isNumericExposeFeature);
const max = Math.min(...colorTemps.map((e) => e.value_max));
const min = Math.max(...colorTemps.map((e) => e.value_min));
discoveryEntry.discovery_payload.max_mireds = max;
@@ -177,7 +223,7 @@ export default class HomeAssistant extends Extension {
}
const effects = utils.arrayUnique(utils.flatten(
allExposes.filter((e) => e.type === 'enum' && e.name === 'effect').map((e) => e.values)));
allExposes.filter(isEnumExposeFeature).filter((e) => e.name === 'effect').map((e) => e.values)));
if (effects.length) {
discoveryEntry.discovery_payload.effect = true;
discoveryEntry.discovery_payload.effect_list = effects;
@@ -185,7 +231,7 @@ export default class HomeAssistant extends Extension {
discoveryEntries.push(discoveryEntry);
} else if (firstExpose.type === 'switch') {
const state = firstExpose.features.find((f) => f.name === 'state');
const state = firstExpose.features.filter(isBinaryExposeFeature).find((f) => f.name === 'state');
const property = getProperty(state);
const discoveryEntry: DiscoveryEntry = {
type: 'switch',
@@ -217,7 +263,8 @@ export default class HomeAssistant extends Extension {
discoveryEntries.push(discoveryEntry);
} else if (firstExpose.type === 'climate') {
const setpointProperties = ['occupied_heating_setpoint', 'current_heating_setpoint'];
const setpoint = firstExpose.features.find((f) => setpointProperties.includes(f.name));
const setpoint = firstExpose.features.filter(isNumericExposeFeature)
.find((f) => setpointProperties.includes(f.name));
assert(setpoint, 'No setpoint found');
const temperature = firstExpose.features.find((f) => f.name === 'local_temperature');
assert(temperature, 'No temperature found');
@@ -242,7 +289,7 @@ export default class HomeAssistant extends Extension {
},
};
const mode = firstExpose.features.find((f) => f.name === 'system_mode');
const mode = firstExpose.features.filter(isEnumExposeFeature).find((f) => f.name === 'system_mode');
if (mode) {
if (mode.values.includes('sleep')) {
// 'sleep' is not supported by Home Assistant, but is valid according to ZCL
@@ -261,7 +308,7 @@ export default class HomeAssistant extends Extension {
discoveryEntry.mockProperties.push({property: state.property, value: null});
discoveryEntry.discovery_payload.action_topic = true;
discoveryEntry.discovery_payload.action_template = `{% set values = ` +
`{None:None,'idle':'off','heat':'heating','cool':'cooling','fan_only':'fan'}` +
`{None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'}` +
` %}{{ values[value_json.${state.property}] }}`;
}
@@ -282,7 +329,8 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.temperature_state_topic = true;
}
const fanMode = firstExpose.features.find((f) => f.name === 'fan_mode');
const fanMode = firstExpose.features.filter(isEnumExposeFeature)
.find((f) => f.name === 'fan_mode');
if (fanMode) {
discoveryEntry.discovery_payload.fan_modes = fanMode.values;
discoveryEntry.discovery_payload.fan_mode_command_topic = true;
@@ -291,7 +339,8 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.fan_mode_state_topic = true;
}
const swingMode = firstExpose.features.find((f) => f.name === 'swing_mode');
const swingMode = firstExpose.features.filter(isEnumExposeFeature)
.find((f) => f.name === 'swing_mode');
if (swingMode) {
discoveryEntry.discovery_payload.swing_modes = swingMode.values;
discoveryEntry.discovery_payload.swing_mode_command_topic = true;
@@ -300,7 +349,7 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.swing_mode_state_topic = true;
}
const preset = firstExpose.features.find((f) => f.name === 'preset');
const preset = firstExpose.features.filter(isEnumExposeFeature).find((f) => f.name === 'preset');
if (preset) {
discoveryEntry.discovery_payload.preset_modes = preset.values;
discoveryEntry.discovery_payload.preset_mode_command_topic = 'preset';
@@ -309,7 +358,8 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.preset_mode_state_topic = true;
}
const tempCalibration = firstExpose.features.find((f) => f.name === 'local_temperature_calibration');
const tempCalibration = firstExpose.features.filter(isNumericExposeFeature)
.find((f) => f.name === 'local_temperature_calibration');
if (tempCalibration) {
const discoveryEntry: DiscoveryEntry = {
type: 'number',
@@ -336,7 +386,8 @@ export default class HomeAssistant extends Extension {
discoveryEntries.push(discoveryEntry);
}
const piHeatingDemand = firstExpose.features.find((f) => f.name === 'pi_heating_demand');
const piHeatingDemand = firstExpose.features.filter(isNumericExposeFeature)
.find((f) => f.name === 'pi_heating_demand');
if (piHeatingDemand) {
const discoveryEntry: DiscoveryEntry = {
type: 'sensor',
@@ -357,7 +408,7 @@ export default class HomeAssistant extends Extension {
discoveryEntries.push(discoveryEntry);
} else if (firstExpose.type === 'lock') {
assert(!endpoint, `Endpoint not supported for lock type`);
const state = firstExpose.features.find((f) => f.name === 'state');
const state = firstExpose.features.filter(isBinaryExposeFeature).find((f) => f.name === 'state');
assert(state, 'No state found');
const discoveryEntry: DiscoveryEntry = {
type: 'lock',
@@ -403,8 +454,8 @@ export default class HomeAssistant extends Extension {
?.features.find((f) => f.name === 'position');
const tilt = exposes.find((expose) => expose.features.find((e) => e.name === 'tilt'))
?.features.find((f) => f.name === 'tilt');
const motorState = allExposes?.find((e) => e.type === 'enum' &&
['motor_state', 'moving'].includes(e.name) && e.access === ACCESS_STATE);
const motorState = allExposes?.filter(isEnumExposeFeature)
.find((e) => ['motor_state', 'moving'].includes(e.name) && e.access === ACCESS_STATE);
const running = allExposes?.find((e) => e.type === 'binary' && e.name === 'running');
const discoveryEntry: DiscoveryEntry = {
@@ -435,9 +486,9 @@ export default class HomeAssistant extends Extension {
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()));
const openingState = motorState.values.find((s) => openingLookup.includes(s.toString().toLowerCase()));
const closingState = motorState.values.find((s) => closingLookup.includes(s.toString().toLowerCase()));
const stoppedState = motorState.values.find((s) => stoppedLookup.includes(s.toString().toLowerCase()));
if (openingState && closingState && stoppedState) {
discoveryEntry.discovery_payload.state_opening = openingState;
@@ -495,7 +546,7 @@ export default class HomeAssistant extends Extension {
},
};
const speed = firstExpose.features.find((e) => e.name === 'mode');
const speed = firstExpose.features.filter(isEnumExposeFeature).find((e) => e.name === 'mode');
if (speed) {
// A fan entity in Home Assistant 2021.3 and above may have a speed,
// controlled by a percentage from 1 to 100, and/or non-speed presets.
@@ -523,7 +574,7 @@ export default class HomeAssistant extends Extension {
}
const allowed = [...speeds, ...presets];
speed.values.forEach((s) => assert(allowed.includes(s)));
speed.values.forEach((s) => assert(allowed.includes(s.toString())));
const percentValues = speeds.map((s, i) => `'${s}':${i}`).join(', ');
const percentCommands = speeds.map((s, i) => `${i}:'${s}'`).join(', ');
const presetList = presets.map((s) => `'${s}'`).join(', ');
@@ -546,11 +597,16 @@ export default class HomeAssistant extends Extension {
}
discoveryEntries.push(discoveryEntry);
} else if (firstExpose.type === 'binary') {
} else if (isBinaryExposeFeature(firstExpose)) {
const lookup: {[s: string]: KeyValue}= {
battery_low: {entity_category: 'diagnostic', device_class: 'battery'},
button_lock: {entity_category: 'config', icon: 'mdi:lock'},
calibration: {entity_category: 'config', icon: 'mdi:progress-wrench'},
capabilities_configurable_curve: {entity_category: 'diagnostic', icon: 'mdi:tune'},
capabilities_forward_phase_control: {entity_category: 'diagnostic', icon: 'mdi:tune'},
capabilities_overload_detection: {entity_category: 'diagnostic', icon: 'mdi:tune'},
capabilities_reactance_discriminator: {entity_category: 'diagnostic', icon: 'mdi:tune'},
capabilities_reverse_phase_control: {entity_category: 'diagnostic', icon: 'mdi:tune'},
carbon_monoxide: {device_class: 'carbon_monoxide'},
card: {entity_category: 'config', icon: 'mdi:clipboard-check'},
child_lock: {entity_category: 'config', icon: 'mdi:account-lock'},
@@ -576,6 +632,11 @@ export default class HomeAssistant extends Extension {
presence: {device_class: 'presence'},
smoke: {device_class: 'smoke'},
sos: {device_class: 'safety'},
status_capacitive_load: {entity_category: 'diagnostic', icon: 'mdi:tune'},
status_forward_phase_control: {entity_category: 'diagnostic', icon: 'mdi:tune'},
status_inductive_load: {entity_category: 'diagnostic', icon: 'mdi:tune'},
status_overload: {entity_category: 'diagnostic', icon: 'mdi:tune'},
status_reverse_phase_control: {entity_category: 'diagnostic', icon: 'mdi:tune'},
tamper: {device_class: 'tamper'},
temperature_scale: {entity_category: 'config', icon: 'mdi:temperature-celsius'},
test: {entity_category: 'diagnostic', icon: 'mdi:test-tube'},
@@ -635,7 +696,7 @@ export default class HomeAssistant extends Extension {
discoveryEntries.push(discoveryEntry);
}
} else if (firstExpose.type === 'numeric') {
} else if (isNumericExposeFeature(firstExpose)) {
const lookup: {[s: string]: KeyValue} = {
ac_frequency: {device_class: 'frequency', enabled_by_default: false, entity_category: 'diagnostic',
state_class: 'measurement'},
@@ -651,6 +712,10 @@ export default class HomeAssistant extends Extension {
auto_relock_time: {entity_category: 'config', icon: 'mdi:timer'},
away_preset_days: {entity_category: 'config', icon: 'mdi:timer'},
away_preset_temperature: {entity_category: 'config', icon: 'mdi:thermometer'},
ballast_maximum_level: {entity_category: 'config'},
ballast_minimum_level: {entity_category: 'config'},
ballast_physical_maximum_level: {entity_category: 'diagnostic'},
ballast_physical_minimum_level: {entity_category: 'diagnostic'},
battery: {device_class: 'battery', entity_category: 'diagnostic', state_class: 'measurement'},
battery2: {device_class: 'battery', entity_category: 'diagnostic', state_class: 'measurement'},
battery_voltage: {device_class: 'voltage', entity_category: 'diagnostic', state_class: 'measurement',
@@ -713,6 +778,7 @@ export default class HomeAssistant extends Extension {
max_temperature_limit: {entity_category: 'config', icon: 'mdi:thermometer-high'},
min_temperature_limit: {entity_category: 'config', icon: 'mdi:thermometer-low'},
min_temperature: {entity_category: 'config', icon: 'mdi:thermometer-low'},
minimum_on_level: {entity_category: 'config'},
measurement_poll_interval: {entity_category: 'config', icon: 'mdi:clock-out'},
occupancy_timeout: {entity_category: 'config', icon: 'mdi:timer'},
pm10: {device_class: 'pm10', state_class: 'measurement'},
@@ -805,6 +871,12 @@ export default class HomeAssistant extends Extension {
delete discoveryEntry.discovery_payload.device_class;
}
// entity_category config is not allowed for sensors
// https://github.com/Koenkk/zigbee2mqtt/issues/20252
if (discoveryEntry.discovery_payload.entity_category === 'config') {
discoveryEntry.discovery_payload.entity_category = 'diagnostic';
}
// Let Home Assistant generate entity name when device_class is present
if (discoveryEntry.discovery_payload.device_class) delete discoveryEntry.discovery_payload.name;
@@ -846,7 +918,7 @@ export default class HomeAssistant extends Extension {
discoveryEntries.push(discoveryEntry);
}
} else if (firstExpose.type === 'enum') {
} else if (isEnumExposeFeature(firstExpose)) {
const lookup: {[s: string]: KeyValue} = {
action: {icon: 'mdi:gesture-double-tap'},
alarm_humidity: {entity_category: 'config', icon: 'mdi:water-percent-alert'},
@@ -877,6 +949,7 @@ export default class HomeAssistant extends Extension {
sound_volume: {entity_category: 'config', icon: 'mdi:volume-high'},
status: {icon: 'mdi:state-machine'},
switch_type: {entity_category: 'config', icon: 'mdi:tune'},
temperature_display_mode: {entity_category: 'config', icon: 'mdi:thermometer'},
temperature_sensor_select: {entity_category: 'config', icon: 'mdi:home-thermometer'},
thermostat_unit: {entity_category: 'config', icon: 'mdi:thermometer'},
volume: {entity_category: 'config', icon: 'mdi: volume-high'},
@@ -927,6 +1000,7 @@ export default class HomeAssistant extends Extension {
const settableText = firstExpose.type === 'text' && firstExpose.access & ACCESS_SET;
const lookup: {[s: string]: KeyValue} = {
action: {icon: 'mdi:gesture-double-tap'},
level_config: {entity_category: 'diagnostic'},
programming_mode: {icon: 'mdi:calendar-clock'},
program: {value_template: `{{ value_json.${firstExpose.property}|default('',true) ` +
`| truncate(254, True, '', 0) }}`},
@@ -969,7 +1043,7 @@ export default class HomeAssistant extends Extension {
// If a sensor has entity category `config`, then change
// it to `diagnostic`. Sensors have no input, so can't be configured.
// https://github.com/Koenkk/zigbee2mqtt/pull/19474
if (d.type === 'sensor' && d.discovery_payload.entity_category === 'config') {
if (['binary_sensor', 'sensor'].includes(d.type) && d.discovery_payload.entity_category === 'config') {
d.discovery_payload.entity_category = 'diagnostic';
}
});
@@ -1080,8 +1154,9 @@ export default class HomeAssistant extends Extension {
}
}
private getConfigs(entity: Device | Group): DiscoveryEntry[] {
private getConfigs(entity: Device | Group | Bridge): DiscoveryEntry[] {
const isDevice = entity.isDevice();
const isGroup = entity.isGroup();
/* istanbul ignore next */
if (!entity || (isDevice && !entity.definition)) return [];
@@ -1104,9 +1179,9 @@ export default class HomeAssistant extends Extension {
// @ts-ignore
configs.push(entity.definition.homeassistant);
}
} else { // group
const exposesByType: {[s: string]: zhc.DefinitionExpose[]} = {};
const allExposes: zhc.DefinitionExpose[] = [];
} else if (isGroup) { // group
const exposesByType: {[s: string]: zhc.Expose[]} = {};
const allExposes: zhc.Expose[] = [];
entity.zh.members.map((e) => this.zigbee.resolveEntity(e.getDevice()) as Device)
.filter((d) => d.definition).forEach((device) => {
@@ -1128,6 +1203,9 @@ export default class HomeAssistant extends Extension {
configs = [].concat(...Object.values(exposesByType)
.map((exposes) => this.exposeToConfig(exposes, 'group', allExposes)));
} else {
// Discover bridge config.
configs.push(...entity.configs);
}
if (isDevice && settings.get().advanced.last_seen !== 'disable') {
@@ -1152,7 +1230,7 @@ export default class HomeAssistant extends Extension {
configs.push(config);
}
if (isDevice && entity.definition.hasOwnProperty('ota')) {
if (isDevice && entity.definition.ota) {
const updateStateSensor: DiscoveryEntry = {
type: 'sensor',
object_id: 'update_state',
@@ -1198,12 +1276,34 @@ export default class HomeAssistant extends Extension {
value_template: `{{ value_json['update']['installed_version'] }}`,
latest_version_template: `{{ value_json['update']['latest_version'] }}`,
json_attributes_topic: `${settings.get().mqtt.base_topic}/${entity.name}`, // state topic
json_attributes_template: `{"in_progress": "{{ value_json['update']['state'] == 'updating' }}"}`,
json_attributes_template:
`{"in_progress": {{ iif(value_json['update']['state'] == 'updating', 'true', 'false') }} }`,
},
};
configs.push(updateSensor);
}
// Discover scenes.
const endpointsOrGroups = isDevice ? entity.zh.endpoints : isGroup ? [entity.zh] : [];
endpointsOrGroups.forEach((endpointOrGroup) => {
utils.getScenes(endpointOrGroup).forEach((scene) => {
const sceneEntry: DiscoveryEntry = {
type: 'scene',
object_id: `scene_${scene.id}`,
mockProperties: [],
discovery_payload: {
name: `${scene.name}`,
state_topic: false,
command_topic: true,
payload_on: `{ "scene_recall": ${scene.id} }`,
object_id_postfix: `_${scene.name.replace(/\s+/g, '_').toLowerCase()}`,
},
};
configs.push(sceneEntry);
});
});
if (isDevice && entity.options.hasOwnProperty('legacy') && !entity.options.legacy) {
configs = configs.filter((c) => c !== sensorClick);
}
@@ -1230,21 +1330,26 @@ export default class HomeAssistant extends Extension {
return configs;
}
private getDiscoverKey(entity: Device | Group): string | number {
return entity.isDevice() ? entity.ieeeAddr : entity.ID;
private getDiscoverKey(entity: Device | Group | Bridge): string | number {
return entity.ID;
}
private discover(entity: Device | Group, force=false): void {
private discover(entity: Device | Group | Bridge, force=false): void {
// 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.options.hasOwnProperty('homeassistant') && !entity.options.homeassistant)) {
// Handle type differences.
const isDevice = entity.isDevice();
const isGroup = entity.isGroup();
if (isGroup && (!discover || entity.zh.members.length === 0)) {
return;
} else if (isDevice && (!discover || !entity.definition || entity.zh.interviewing ||
(entity.options.hasOwnProperty('homeassistant') && !entity.options.homeassistant))) {
return;
}
const lastDiscovered = this.discovered[discoverKey];
this.discovered[discoverKey] = {topics: new Set(), mockProperties: new Set(), objectIDs: new Set()};
this.getConfigs(entity).forEach((config) => {
@@ -1287,6 +1392,11 @@ export default class HomeAssistant extends Extension {
payload.object_id += `_${config.object_id}`;
}
// Allow customization of the `payload.object_id` without touching the other uses of `config.object_id`
// (e.g. for setting the `payload.unique_id` and as an internal key).
payload.object_id = `${payload.object_id}${payload.object_id_postfix ?? ''}`;
delete payload.object_id_postfix;
// Set unique_id
payload.unique_id = `${entity.options.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
@@ -1294,20 +1404,27 @@ export default class HomeAssistant extends Extension {
payload.device = devicePayload;
payload.origin = this.discoveryOrigin;
// Availability payload
payload.availability = [{topic: `${settings.get().mqtt.base_topic}/bridge/state`}];
// Availability payload (can be disabled by setting `payload.availability = false`).
if (! payload.hasOwnProperty('availability') || payload.availability) {
payload.availability = [{topic: `${settings.get().mqtt.base_topic}/bridge/state`}];
/* istanbul ignore next */
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
payload.availability_mode = 'all';
payload.availability.push({topic: `${baseTopic}/availability`});
}
if (isDevice||isGroup) {
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
payload.availability_mode = 'all';
payload.availability.push({topic: `${baseTopic}/availability`});
}
} else { // Bridge availability is different.
payload.availability_mode = 'all';
}
if (entity.isDevice() && entity.options.disabled) {
// Mark disabled device always as unavailable
payload.availability.forEach((a: KeyValue) => a.value_template = '{{ "offline" }}');
} else if (!settings.get().advanced.legacy_availability_payload) {
payload.availability.forEach((a: KeyValue) => a.value_template = '{{ value_json.state }}');
if (isDevice && entity.options.disabled) {
// Mark disabled device always as unavailable
payload.availability.forEach((a: KeyValue) => a.value_template = '{{ "offline" }}');
} else if (!settings.get().advanced.legacy_availability_payload) {
payload.availability.forEach((a: KeyValue) => a.value_template = '{{ value_json.state }}');
}
} else {
delete payload.availability;
}
const commandTopicPrefix = payload.command_topic_prefix ? `${payload.command_topic_prefix}/` : '';
@@ -1443,6 +1560,11 @@ export default class HomeAssistant extends Extension {
config.mockProperties?.forEach((mockProperty) =>
this.discovered[discoverKey].mockProperties.add(mockProperty));
});
lastDiscovered?.topics?.forEach((topic) => {
if (!this.discovered[discoverKey].topics.has(topic)) {
this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
}
});
}
@bind private onMQTTMessage(data: eventdata.MQTTMessage): void {
@@ -1469,7 +1591,7 @@ export default class HomeAssistant extends Extension {
// Group discovery topic uses "ENCODEDBASETOPIC_GROUPID", device use ieeeAddr
const ID = discoveryMatch[2].includes('_') ? discoveryMatch[2].split('_')[1] : discoveryMatch[2];
const entity = this.zigbee.resolveEntity(ID);
const entity = ID === this.bridge.ID ? this.bridge : this.zigbee.resolveEntity(ID);
let clear = !entity || entity.isDevice() && !entity.definition;
// Only save when topic matches otherwise config is not updated when renamed by editing configuration.yaml
@@ -1518,7 +1640,35 @@ export default class HomeAssistant extends Extension {
this.discover(data.device);
}
private getDevicePayload(entity: Device | Group): KeyValue {
@bind async onScenesChanged(): Promise<void> {
// Re-trigger MQTT discovery of all devices and groups, similar to bridge.ts
const entities = [...this.zigbee.devices(), ...this.zigbee.groups()];
const clearedEntities = new Set<Device|Group>();
// First, clear existing scene discovery topics
entities.forEach((entity) => {
logger.debug(`Clearing Home Assistant scene discovery topics for '${entity.name}'`);
this.discovered[this.getDiscoverKey(entity)]?.topics.forEach((topic) => {
if (topic.startsWith('scene')) {
this.mqtt.publish(topic, null, {retain: true, qos: 1}, this.discoveryTopic, false, false);
clearedEntities.add(entity);
}
});
});
// Make sure Home Assistant deletes the old entity first otherwise another one (_2) is created
// https://github.com/Koenkk/zigbee2mqtt/issues/12610
logger.debug(`Finished clearing scene discovery topics, waiting for Home Assistant.`);
await utils.sleep(2);
// Re-discover all entities (including their new scenes).
logger.debug(`Re-discovering entities with their scenes.`);
clearedEntities.forEach((entity) => {
this.discover(entity, true);
});
}
private getDevicePayload(entity: Device | Group | Bridge): KeyValue {
const identifierPostfix = entity.isGroup() ?
`zigbee2mqtt_${this.getEncodedBaseTopic()}` : 'zigbee2mqtt';
@@ -1534,22 +1684,37 @@ export default class HomeAssistant extends Extension {
sw_version: `Zigbee2MQTT ${this.zigbee2MQTTVersion}`,
};
const url = settings.get().frontend?.url ?? '';
if (entity.isDevice()) {
payload.model = `${entity.definition.description} (${entity.definition.model})`;
payload.manufacturer = entity.definition.vendor;
payload.sw_version = entity.zh.softwareBuildID;
payload.configuration_url = `${url}/#/device/${entity.ieeeAddr}/info`;
} else if (entity.isGroup()) {
payload.model = 'Group';
payload.manufacturer = 'Zigbee2MQTT';
payload.configuration_url = `${url}/#/group/${entity.ID}`;
} else {
payload.model = 'Bridge';
payload.manufacturer = 'Zigbee2MQTT';
payload.hw_version = `${entity.hardwareVersion} ${entity.firmwareVersion}`;
payload.sw_version = this.zigbee2MQTTVersion;
payload.configuration_url = `${url}/#/settings`;
}
if (settings.get().frontend?.url) {
const url = settings.get().frontend?.url;
payload.configuration_url = entity.isDevice() ? `${url}/#/device/${entity.ieeeAddr}/info` :
`${url}/#/group/${entity.ID}`;
if (!url) {
delete payload.configuration_url;
}
// Link devices & groups to bridge.
if (entity !== this.bridge) {
payload.via_device = this.bridgeIdentifier;
}
return payload;
}
override adjustMessageBeforePublish(entity: Device | Group, message: KeyValue): void {
override adjustMessageBeforePublish(entity: Device | Group | Bridge, message: KeyValue): void {
const discoverKey = this.getDiscoverKey(entity);
this.discovered[discoverKey]?.mockProperties?.forEach((mockProperty) => {
if (!message.hasOwnProperty(mockProperty.property)) {
@@ -1576,7 +1741,7 @@ export default class HomeAssistant extends Extension {
return settings.get().mqtt.base_topic.split('').map((s) => s.charCodeAt(0).toString()).join('');
}
private getDiscoveryTopic(config: DiscoveryEntry, entity: Device | Group): string {
private getDiscoveryTopic(config: DiscoveryEntry, entity: Device | Group | Bridge): string {
const key = entity.isDevice() ? entity.ieeeAddr : `${this.getEncodedBaseTopic()}_${entity.ID}`;
return `${config.type}/${key}/${config.object_id}/config`;
}
@@ -1625,4 +1790,158 @@ export default class HomeAssistant extends Extension {
_clearDiscoveredTrigger(): void {
this.discoveredTriggers = {};
}
private getBridgeEntity(coordinatorVersion: zh.CoordinatorVersion): Bridge {
const coordinatorIeeeAddress = this.zigbee.firstCoordinatorEndpoint().deviceIeeeAddress;
const discovery: DiscoveryEntry[] = [];
const bridge = new Bridge(coordinatorIeeeAddress, coordinatorVersion, discovery);
const baseTopic = `${settings.get().mqtt.base_topic}/${bridge.name}`;
discovery.push(
// Binary sensors.
{
type: 'binary_sensor',
object_id: 'connection_state',
mockProperties: [],
discovery_payload: {
name: 'Connection state',
device_class: 'connectivity',
entity_category: 'diagnostic',
state_topic: true,
state_topic_postfix: 'state',
value_template: '{{ value_json.state }}',
payload_on: 'online',
payload_off: 'offline',
availability: false,
},
},
{
type: 'binary_sensor',
object_id: 'restart_required',
mockProperties: [],
discovery_payload: {
name: 'Restart required',
device_class: 'problem',
entity_category: 'diagnostic',
enabled_by_default: false,
state_topic: true,
state_topic_postfix: 'info',
value_template: '{{ value_json.restart_required }}',
payload_on: true,
payload_off: false,
},
},
// Buttons.
{
type: 'button',
object_id: 'restart',
mockProperties: [],
discovery_payload: {
name: 'Restart',
device_class: 'restart',
state_topic: false,
command_topic: `${baseTopic}/request/restart`,
payload_press: '',
},
},
// Selects.
{
type: 'select',
object_id: 'log_level',
mockProperties: [],
discovery_payload: {
name: 'Log level',
entity_category: 'config',
state_topic: true,
state_topic_postfix: 'info',
value_template: '{{ value_json.log_level | lower }}',
command_topic: `${baseTopic}/request/options`,
command_template:
'{"options": {"advanced": {"log_level": "{{ value }}" } } }',
options: ['info', 'warn', 'error', 'debug'],
},
},
// Sensors:
{
type: 'sensor',
object_id: 'version',
mockProperties: [],
discovery_payload: {
name: 'Version',
icon: 'mdi:zigbee',
entity_category: 'diagnostic',
state_topic: true,
state_topic_postfix: 'info',
value_template: '{{ value_json.version }}',
},
},
{
type: 'sensor',
object_id: 'coordinator_version',
mockProperties: [],
discovery_payload: {
name: 'Coordinator version',
icon: 'mdi:chip',
entity_category: 'diagnostic',
enabled_by_default: false,
state_topic: true,
state_topic_postfix: 'info',
value_template: '{{ value_json.coordinator.meta.revision }}',
},
},
{
type: 'sensor',
object_id: 'network_map',
mockProperties: [],
discovery_payload: {
name: 'Network map',
entity_category: 'diagnostic',
enabled_by_default: false,
state_topic: true,
state_topic_postfix: 'response/networkmap',
value_template: '{{ now().strftime(\'%Y-%m-%d %H:%M:%S\') }}',
json_attributes_topic: `${baseTopic}/response/networkmap`,
json_attributes_template: '{{ value_json.data.value | tojson }}',
},
},
{
type: 'sensor',
object_id: 'permit_join_timeout',
mockProperties: [],
discovery_payload: {
name: 'Permit join timeout',
device_class: 'duration',
unit_of_measurement: 's',
entity_category: 'diagnostic',
state_topic: true,
state_topic_postfix: 'info',
value_template:
'{{ iif(value_json.permit_join_timeout is defined, value_json.permit_join_timeout, None) }}',
},
},
// Switches.
{
type: 'switch',
object_id: 'permit_join',
mockProperties: [],
discovery_payload: {
name: 'Permit join',
icon: 'mdi:human-greeting-proximity',
state_topic: true,
state_topic_postfix: 'info',
value_template: '{{ value_json.permit_join | lower }}',
command_topic: `${baseTopic}/request/permit_join`,
payload_on: 'true',
payload_off: 'false',
},
},
);
return bridge;
}
}
+3 -4
View File
@@ -1,6 +1,5 @@
import * as settings from '../../util/settings';
import logger from '../../util/logger';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import utils from '../../util/utils';
import assert from 'assert';
import Extension from '../extension';
@@ -90,7 +89,7 @@ export default class BridgeLegacy extends Extension {
@bind async reset(): Promise<void> {
try {
await this.zigbee.reset('soft');
logger.info('Soft resetted ZNP');
logger.info('Soft reset ZNP');
} catch (error) {
logger.error('Soft reset failed');
}
@@ -140,7 +139,7 @@ export default class BridgeLegacy extends Extension {
};
if (device.zh.type !== 'Coordinator') {
const definition = zigbeeHerdsmanConverters.findByDevice(device.zh);
const definition = device.definition;
payload.model = definition ? definition.model : device.zh.modelID;
payload.vendor = definition ? definition.vendor : '-';
payload.description = definition ? definition.description : '-';
@@ -385,7 +384,7 @@ export default class BridgeLegacy extends Extension {
);
} else if (type === 'deviceInterview') {
if (data.status === 'successful') {
if (resolvedEntity.definition) {
if (resolvedEntity.isSupported) {
const {vendor, description, model} = resolvedEntity.definition;
const log = {friendly_name: resolvedEntity.name, model, vendor, description, supported: true};
this.mqtt.publish(
+7 -7
View File
@@ -1,4 +1,4 @@
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import * as zhc from 'zigbee-herdsman-converters';
import logger from '../../util/logger';
import * as settings from '../../util/settings';
import Extension from '../extension';
@@ -7,14 +7,14 @@ const defaultConfiguration = {
minimumReportInterval: 3, maximumReportInterval: 300, reportableChange: 1,
};
const ZNLDP12LM = zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'ZNLDP12LM');
const ZNLDP12LM = zhc.definitions.find((d) => d.model === 'ZNLDP12LM');
const devicesNotSupportingReporting = [
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'CC2530.ROUTER'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'BASICZBR3'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'ZM-CSW032-D'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'TS0001'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'TS0115'),
zhc.definitions.find((d) => d.model === 'CC2530.ROUTER'),
zhc.definitions.find((d) => d.model === 'BASICZBR3'),
zhc.definitions.find((d) => d.model === 'ZM-CSW032-D'),
zhc.definitions.find((d) => d.model === 'TS0001'),
zhc.definitions.find((d) => d.model === 'TS0115'),
];
const reportKey = 1;
+1 -1
View File
@@ -43,7 +43,7 @@ export default class SoftReset extends Extension {
try {
await this.zigbee.reset('soft');
logger.warn('Soft resetted ZNP due to timeout');
logger.warn('Soft reset ZNP due to timeout');
} catch (error) {
logger.warn('Soft reset failed, trying stop/start');
+3 -14
View File
@@ -99,12 +99,7 @@ export default class NetworkMap extends Extension {
// Add the device model
if (node.type !== 'Coordinator') {
if (node.definition) {
labels.push(`${node.definition.vendor} ${node.definition.description} (${node.definition.model})`);
} else {
// This model is not supported by zigbee-herdsman-converters, add zigbee model information
labels.push(`${node.manufacturerName} ${node.modelID}`);
}
labels.push(`${node.definition?.vendor} ${node.definition?.description} (${node.definition?.model})`);
}
// Add the device last_seen timestamp
@@ -177,12 +172,7 @@ export default class NetworkMap extends Extension {
if (node.type !== 'Coordinator') {
text.push(`---`);
const definition = (this.zigbee.resolveEntity(node.ieeeAddr) as Device).definition;
if (definition) {
text.push(`${definition.vendor} ${definition.description} (${definition.model})`);
} else {
// This model is not supported by zigbee-herdsman-converters, add zigbee model information
text.push(`${node.manufacturerName} ${node.modelID}`);
}
text.push(`${definition?.vendor} ${definition?.description} (${definition?.model})`);
}
// Add the device last_seen timestamp
@@ -269,8 +259,7 @@ export default class NetworkMap extends Extension {
vendor: device.definition.vendor,
description: device.definition.description,
supports: Array.from(new Set((device.exposes()).map((e) => {
return e.hasOwnProperty('name') ? e.name :
`${e.type} (${e.features.map((f) => f.name).join(', ')})`;
return e.name ?? `${e.type} (${e.features.map((f) => f.name).join(', ')})`;
}))).join(', '),
} : null;
+5 -4
View File
@@ -1,4 +1,4 @@
import zhc from 'zigbee-herdsman-converters';
import * as zhc from 'zigbee-herdsman-converters';
import Extension from './extension';
/**
@@ -39,12 +39,13 @@ export default class OnEvent extends Extension {
}
}
private async callOnEvent(device: Device, type: string, data: KeyValue): Promise<void> {
private async callOnEvent(device: Device, type: zhc.OnEventType, data: KeyValue): Promise<void> {
const state = this.state.get(device);
zhc.onEvent(type, data, device.zh, device.options, state);
zhc.onEvent(type, data, device.zh);
if (device.definition?.onEvent) {
await device.definition.onEvent(type, data, device.zh, device.options, state);
const options: KeyValue = device.options;
await device.definition.onEvent(type, data, device.zh, options, state);
}
}
}
+9 -8
View File
@@ -2,14 +2,13 @@ import * as settings from '../util/settings';
import logger from '../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import utils from '../util/utils';
import * as tradfriOTA from 'zigbee-herdsman-converters/lib/ota/tradfri';
import * as 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';
import * as zhc from 'zigbee-herdsman-converters';
function isValidUrl(url: string): boolean {
let parsed;
@@ -45,7 +44,7 @@ export default class OTAUpdate extends Extension {
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
if (settings.get().ota.ikea_ota_use_test_url) {
tradfriOTA.useTestURL();
zhc.ota.tradfri.useTestURL();
}
// Let zigbeeOTA module know if the override index file is provided
@@ -56,11 +55,11 @@ export default class OTAUpdate extends Extension {
overrideOTAIndex = dataDir.joinPath(overrideOTAIndex);
}
zigbeeOTA.useIndexOverride(overrideOTAIndex);
zhc.ota.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());
zhc.ota.setDataDir(dataDir.getPath());
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state.
// remove them.
@@ -80,7 +79,7 @@ export default class OTAUpdate extends Extension {
logger.debug(`Device '${data.device.name}' requested OTA`);
const automaticOTACheckDisabled = settings.get().ota.disable_automatic_update_check;
let supportsOTA = data.device.definition.hasOwnProperty('ota');
let supportsOTA = !!data.device.definition.ota;
if (supportsOTA && !automaticOTACheckDisabled) {
// When a device does a next image request, it will usually do it a few times after each other
// with only 10 - 60 seconds inbetween. It doesn't make sense to check for a new update
@@ -93,7 +92,9 @@ export default class OTAUpdate extends Extension {
this.lastChecked[data.device.ieeeAddr] = Date.now();
let availableResult: zhc.OtaUpdateAvailableResult = null;
try {
availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, logger, data.data);
// @ts-expect-error typing guaranteed by data.type
const dataData: zhc.ota.ImageInfo = data.data;
availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, logger, dataData);
} catch (e) {
supportsOTA = false;
logger.debug(`Failed to check if update available for '${data.device.name}' (${e.message})`);
@@ -201,7 +202,7 @@ export default class OTAUpdate extends Extension {
}
try {
const availableResult = await device.definition.ota.isUpdateAvailable(device.zh, logger);
const availableResult = await device.definition.ota.isUpdateAvailable(device.zh, logger, null);
const msg = `${availableResult.available ? 'Update' : 'No update'} available for '${device.name}'`;
logger.info(msg);
+26 -25
View File
@@ -1,6 +1,6 @@
import * as settings from '../util/settings';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import * as zhc from 'zigbee-herdsman-converters';
import * as philips from 'zigbee-herdsman-converters/lib/philips';
import logger from '../util/logger';
import utils from '../util/utils';
@@ -17,19 +17,19 @@ const sceneConverterKeys = ['scene_store', 'scene_add', 'scene_remove', 'scene_r
// Legacy: don't provide default converters anymore, this is required by older z2m installs not saving group members
const defaultGroupConverters = [
zigbeeHerdsmanConverters.toZigbeeConverters.light_onoff_brightness,
zigbeeHerdsmanConverters.toZigbeeConverters.light_color_colortemp,
zhc.toZigbee.light_onoff_brightness,
zhc.toZigbee.light_color_colortemp,
philips.tz.effect, // Support Hue effects for groups
zigbeeHerdsmanConverters.toZigbeeConverters.ignore_transition,
zigbeeHerdsmanConverters.toZigbeeConverters.cover_position_tilt,
zigbeeHerdsmanConverters.toZigbeeConverters.thermostat_occupied_heating_setpoint,
zigbeeHerdsmanConverters.toZigbeeConverters.tint_scene,
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_step,
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_step,
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_step,
zhc.toZigbee.ignore_transition,
zhc.toZigbee.cover_position_tilt,
zhc.toZigbee.thermostat_occupied_heating_setpoint,
zhc.toZigbee.tint_scene,
zhc.toZigbee.light_brightness_move,
zhc.toZigbee.light_brightness_step,
zhc.toZigbee.light_colortemp_step,
zhc.toZigbee.light_colortemp_move,
zhc.toZigbee.light_hue_saturation_move,
zhc.toZigbee.light_hue_saturation_step,
];
interface ParsedTopic {ID: string, endpoint: string, attribute: string, type: 'get' | 'set'}
@@ -81,8 +81,8 @@ export default class Publish extends Extension {
}
}
legacyRetrieveState(re: Device | Group, converter: zhc.ToZigbeeConverter, result: zhc.ToZigbeeConverterResult,
target: zh.Endpoint | zh.Group, key: string, meta: zhc.ToZigbeeConverterGetMeta): void {
legacyRetrieveState(re: Device | Group, converter: zhc.Tz.Converter, result: zhc.Tz.ConvertSetResult,
target: zh.Endpoint | zh.Group, key: string, meta: zhc.Tz.Meta): void {
// It's possible for devices to get out of sync when writing an attribute that's not reportable.
// So here we re-read the value after a specified timeout, this timeout could for example be the
// transition time of a color change or for forcing a state read for devices that don't
@@ -140,18 +140,14 @@ export default class Publish extends Extension {
const membersState = re instanceof Group ?
Object.fromEntries(re.zh.members.map((e) => [e.getDevice().ieeeAddr,
this.state.get(this.zigbee.resolveEntity(e.getDevice().ieeeAddr))])) : null;
let converters: zhc.ToZigbeeConverter[];
let converters: zhc.Tz.Converter[];
{
if (Array.isArray(definition)) {
const c = new Set(definition.map((d) => d.toZigbee).flat());
// @ts-expect-error
if (c.size == 0) converters = defaultGroupConverters;
else converters = Array.from(c);
} else if (definition) {
converters = definition.toZigbee;
} else {
converters = [zigbeeHerdsmanConverters.toZigbeeConverters.read,
zigbeeHerdsmanConverters.toZigbeeConverters.write];
converters = definition.toZigbee;
}
}
@@ -177,7 +173,7 @@ export default class Publish extends Extension {
entries.sort((a) => (['state', 'brightness', 'brightness_percent'].includes(a[0]) ? sorter : sorter * -1));
// For each attribute call the corresponding converter
const usedConverters: {[s: number]: zhc.ToZigbeeConverter[]} = {};
const usedConverters: {[s: number]: zhc.Tz.Converter[]} = {};
const toPublish: {[s: number | string]: KeyValue} = {};
const toPublishEntity: {[s: number | string]: Device | Group} = {};
const addToToPublish = (entity: Device | Group, payload: KeyValue): void => {
@@ -208,7 +204,9 @@ export default class Publish extends Extension {
}
if (!usedConverters.hasOwnProperty(endpointOrGroupID)) usedConverters[endpointOrGroupID] = [];
const converter = converters.find((c) => c.key.includes(key));
/* istanbul ignore next */
const converter = converters.find((c) =>
c.key.includes(key) && (!c.endpoint || c.endpoint == endpointName));
if (parsedTopic.type === 'set' && usedConverters[endpointOrGroupID].includes(converter)) {
// Use a converter for set only once
@@ -228,8 +226,11 @@ export default class Publish extends Extension {
}
// Converter didn't return a result, skip
const meta = {endpoint_name: endpointName, options: entitySettings, message: {...message}, logger, device,
state: entityState, membersState, mapped: definition};
const entitySettingsKeyValue: KeyValue = entitySettings;
const meta = {
endpoint_name: endpointName, options: entitySettingsKeyValue,
message: {...message}, logger, device, state: entityState, membersState, mapped: definition,
};
// Strip endpoint name from meta.message properties.
if (endpointName) {
+9 -12
View File
@@ -5,6 +5,7 @@ import Extension from './extension';
import stringify from 'json-stable-stringify-without-jsonify';
import bind from 'bind-decorator';
import utils from '../util/utils';
import * as zhc from 'zigbee-herdsman-converters';
type DebounceFunction = (() => void) & { clear(): void; } & { flush(): void; };
@@ -77,17 +78,8 @@ export default class Receive extends Extension {
}
shouldProcess(data: eventdata.DeviceMessage): boolean {
if (!data.device.definition) {
if (data.device.zh.interviewing) {
logger.debug(`Skipping message, definition is undefined and still interviewing`);
} else {
logger.warn(
`Received message from unsupported device with Zigbee model '${data.device.zh.modelID}' ` +
`and manufacturer name '${data.device.zh.manufacturerName}'`);
// eslint-disable-next-line max-len
logger.warn(`Please see: https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html`);
}
if (!data.device.definition || data.device.zh.interviewing) {
logger.debug(`Skipping message, still interviewing`);
return false;
}
@@ -125,6 +117,9 @@ export default class Receive extends Extension {
// - If NO payload is returned do nothing. This is for non-standard behaviour
// for e.g. click switches where we need to count number of clicks and detect long presses.
const publish = (payload: KeyValue): void => {
const options: KeyValue = data.device.options;
zhc.postProcessConvertedFromZigbeeMessage(data.device.definition, payload, options);
if (settings.get().advanced.elapsed) {
const now = Date.now();
if (this.elapsed[data.device.ieeeAddr]) {
@@ -147,8 +142,10 @@ export default class Receive extends Extension {
let payload: KeyValue = {};
for (const converter of converters) {
try {
const convertData = {...data, device: data.device.zh};
const options: KeyValue = data.device.options;
const converted = await converter.convert(
data.device.definition, data, publish, data.device.options, meta);
data.device.definition, convertData, publish, options, meta);
if (converted) {
payload = {...payload, ...converted};
}
+14 -12
View File
@@ -1,10 +1,10 @@
/* eslint-disable brace-style */
import * as settings from '../util/settings';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import * as zhc from 'zigbee-herdsman-converters';
export default class Device {
public zh: zh.Device;
private _definition: zhc.Definition;
public definition: zhc.Definition;
private _definitionModelID: string;
get ieeeAddr(): string {return this.zh.ieeeAddr;}
@@ -13,29 +13,31 @@ export default class Device {
get name(): string {
return this.zh.type === 'Coordinator' ? 'Coordinator' : this.options?.friendly_name || this.ieeeAddr;
}
get definition(): zhc.Definition {
// 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;
get isSupported(): boolean {
return this.zh.type === 'Coordinator' || (this.definition && !this.definition.generated);
}
constructor(device: zh.Device) {
this.zh = device;
}
exposes(): zhc.DefinitionExpose[] {
exposes(): zhc.Expose[] {
/* istanbul ignore if */
if (typeof this.definition.exposes == 'function') {
return this.definition.exposes(this.zh, this.options);
const options: KeyValue = this.options;
return this.definition.exposes(this.zh, options);
} else {
return this.definition.exposes;
}
}
async resolveDefinition(): Promise<void> {
if (!this.zh.interviewing && (!this.definition || this._definitionModelID !== this.zh.modelID)) {
this.definition = await zhc.findByDevice(this.zh, true);
this._definitionModelID = this.zh.modelID;
}
}
ensureInSettings(): void {
if (this.zh.type !== 'Coordinator' && !settings.getDevice(this.zh.ieeeAddr)) {
settings.addDevice(this.zh.ieeeAddr);
+2 -3
View File
@@ -1,6 +1,6 @@
/* eslint-disable brace-style */
import * as settings from '../util/settings';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import * as zhc from 'zigbee-herdsman-converters';
export default class Group {
public zh: zh.Group;
@@ -24,8 +24,7 @@ export default class Group {
}
membersDefinitions(): zhc.Definition[] {
return this.zh.members.map((m) =>
zigbeeHerdsmanConverters.findByDevice(m.getDevice())).filter((d) => d) as zhc.Definition[];
return this.membersDevices().map((d) => d.definition).filter((d) => d);
}
isDevice(): this is Device {return false;}
+5 -68
View File
@@ -21,6 +21,8 @@ import type {
FrameControl as ZHFrameControl,
} from 'zigbee-herdsman/dist/zcl';
import type * as zhc from 'zigbee-herdsman-converters';
import type * as ZHEvents from 'zigbee-herdsman/dist/controller/events';
import type TypeEventBus from 'lib/eventBus';
@@ -44,8 +46,11 @@ declare global {
type Extension = TypeExtension;
// Types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ExternalDefinition = zhc.Definition & {homeassistant: any};
interface MQTTResponse {data: KeyValue, status: 'error' | 'ok', error?: string, transaction?: string}
interface MQTTOptions {qos?: QoS, retain?: boolean, properties?: {messageExpiryInterval: number}}
type Scene = {id: number, name: string};
type StateChangeReason = 'publishDebounce' | 'groupOptimistic' | 'lastSeenChanged' | 'publishCached';
type PublishEntityState = (entity: Device | Group, payload: KeyValue,
stateChangeReason?: StateChangeReason) => Promise<void>;
@@ -70,73 +75,6 @@ declare global {
}
}
// zigbee-herdsman-converters
namespace zhc {
interface Logger {
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
debug: (message: string) => void;
}
interface ToZigbeeConverterGetMeta {message?: KeyValue, mapped?: Definition | Definition[]}
interface ToZigbeeConverterResult {state: KeyValue,
membersState: {[s: string]: KeyValue}, readAfterWriteTime?: number}
interface ToZigbeeConverter {
key: string[],
convertGet?: (entity: zh.Endpoint | zh.Group, key: string, meta: ToZigbeeConverterGetMeta) => Promise<void>
convertSet?: (entity: zh.Endpoint | zh.Group, key: string, value: KeyValue | string | number,
meta: {state: KeyValue}) => Promise<ToZigbeeConverterResult>
}
interface FromZigbeeConverter {
cluster: string,
type: string[] | string,
convert: (model: Definition, msg: KeyValue, publish: (payload: KeyValue) => void, options: KeyValue,
meta: {state: KeyValue, logger: Logger, device: zh.Device}) => Promise<KeyValue>,
}
interface DefinitionExposeFeature {name: string, label: string, endpoint?: string,
property: string, value_max?: number, value_min?: number, unit?: string,
value_off?: string, value_on?: string, value_step?: number, values: string[], access: number}
interface DefinitionExpose {
type: string, name?: string, label?: string, features?: DefinitionExposeFeature[],
endpoint?: string, values?: string[], value_off?: string, value_on?: string, value_step?: number,
access: number, property: string, unit?: string,
value_min?: number, value_max?: number}
interface OtaUpdateAvailableResult {available: boolean, currentFileVersion: number, otaFileVersion: number}
interface Definition {
model: string,
zigbeeModel: string[],
endpoint?: (device: zh.Device) => {[s: string]: number}
toZigbee: ToZigbeeConverter[]
fromZigbee: FromZigbeeConverter[]
icon?: string
description: string
options: zhc.DefinitionExpose[],
vendor: string
exposes: DefinitionExpose[] | ((device: zh.Device, options: KeyValue) => DefinitionExpose[])
configure?: (device: zh.Device, coordinatorEndpoint: zh.Endpoint, logger: Logger,
options?: DeviceOptions) => Promise<void>;
onEvent?: (type: string, data: KeyValue, device: zh.Device,
settings: KeyValue, state: KeyValue) => Promise<void>;
ota?: {
isUpdateAvailable: (device: zh.Device, logger: Logger, data?: KeyValue)
=> Promise<OtaUpdateAvailableResult>;
updateToLatest: (device: zh.Device, logger: Logger,
onProgress: (progress: number, remaining: number) => void) => Promise<number>;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ExternalDefinition = Definition & {homeassistant: any};
}
namespace eventdata {
type EntityRenamed = { entity: Device | Group, homeAssisantRename: boolean, from: string, to: string };
type DeviceRemoved = { ieeeAddr: string, name: string };
@@ -316,4 +254,3 @@ declare global {
qos?: 0 | 1 | 2,
}
}
-9
View File
@@ -1,9 +0,0 @@
declare module 'zigbee-herdsman-converters' {
export function findByDevice(device: zh.Device): zhc.Definition;
export function getConfigureKey(definition: zhc.Definition): string | number;
export const toZigbeeConverters: {[s: string]: zhc.ToZigbeeConverter};
export const definitions: zhc.Definition[];
export function addDeviceDefinition(definition: zhc.Definition): Promise<void>;
export function onEvent(type: string, data: KeyValue, device: zh.Device, settings: KeyValue,
state: KeyValue): Promise<void>;
}
+3 -4
View File
@@ -345,7 +345,7 @@
"default": false
},
"zigbee_ota_override_index_location": {
"type": "string",
"type": ["string", "null"],
"title": "OTA index override file name",
"requiresRestart": true,
"description": "Location of override OTA index file",
@@ -371,11 +371,10 @@
"requiresRestart": true
},
"host": {
"type": "string",
"type": ["string", "null"],
"title": "Bind host",
"description": "Frontend binding host. Binds to a unix socket when an absolute path is given instead.",
"examples": ["127.0.0.1", "/run/zigbee2mqtt/zigbee2mqtt.sock"],
"default": "0.0.0.0",
"examples": ["127.0.0.1", "::1", "/run/zigbee2mqtt/zigbee2mqtt.sock"],
"requiresRestart": true
},
"auth_token": {
+1 -1
View File
@@ -150,7 +150,7 @@ function loadSettingsWithDefaults(): void {
}
if (_settingsWithDefaults.frontend) {
const defaults = {port: 8080, auth_token: false, host: '0.0.0.0'};
const defaults = {port: 8080, auth_token: false};
const s = typeof _settingsWithDefaults.frontend === 'object' ? _settingsWithDefaults.frontend : {};
// @ts-ignore
_settingsWithDefaults.frontend = {};
+46 -18
View File
@@ -6,6 +6,7 @@ import fs from 'fs';
import path from 'path';
import {detailedDiff} from 'deep-object-diff';
import objectAssignDeep from 'object-assign-deep';
import type * as zhc from 'zigbee-herdsman-converters';
// construct a local ISO8601 string (instead of UTC-based)
// Example:
@@ -161,25 +162,21 @@ function loadModuleFromFile(modulePath: string): unknown {
return loadModuleFromText(moduleCode);
}
function* getExternalConvertersDefinitions(settings: Settings): Generator<zhc.ExternalDefinition> {
const externalConverters = settings.external_converters;
export function* loadExternalConverter(moduleName: string): Generator<ExternalDefinition> {
let converter;
for (const moduleName of externalConverters) {
let converter;
if (moduleName.endsWith('.js')) {
converter = loadModuleFromFile(data.joinPath(moduleName));
} else {
converter = require(moduleName);
}
if (moduleName.endsWith('.js')) {
converter = loadModuleFromFile(data.joinPath(moduleName));
} else {
converter = require(moduleName);
}
if (Array.isArray(converter)) {
for (const item of converter) {
yield item;
}
} else {
yield converter;
if (Array.isArray(converter)) {
for (const item of converter) {
yield item;
}
} else {
yield converter;
}
}
@@ -371,6 +368,18 @@ function clone(obj: KeyValue): KeyValue {
return JSON.parse(JSON.stringify(obj));
}
export function isNumericExposeFeature(feature: zhc.Feature): feature is zhc.Numeric {
return feature?.type === 'numeric';
}
export function isEnumExposeFeature(feature: zhc.Feature): feature is zhc.Enum {
return feature?.type === 'enum';
}
export function isBinaryExposeFeature(feature: zhc.Feature): feature is zhc.Binary {
return feature?.type === 'binary';
}
function computeSettingsToChange(current: KeyValue, new_: KeyValue): KeyValue {
const diff: KeyValue = detailedDiff(current, new_);
@@ -404,11 +413,30 @@ function computeSettingsToChange(current: KeyValue, new_: KeyValue): KeyValue {
return newSettings;
}
function getScenes(entity: zh.Endpoint | zh.Group): Scene[] {
const scenes: {[id: number]: Scene} = {};
const endpoints = isEndpoint(entity) ? [entity] : entity.members;
const groupID = isEndpoint(entity) ? 0 : entity.groupID;
for (const endpoint of endpoints) {
for (const [key, data] of Object.entries(endpoint.meta?.scenes || {})) {
const split = key.split('_');
const sceneID = parseInt(split[0], 10);
const sceneGroupID = parseInt(split[1], 10);
if (sceneGroupID === groupID) {
scenes[sceneID] = {id: sceneID, name: (data as KeyValue).name || `Scene ${sceneID}`};
}
}
}
return Object.values(scenes);
}
export default {
endpointNames, capitalize, getZigbee2MQTTVersion, getDependencyVersion, formatDate, objectHasProperties,
equalsPartial, getObjectProperty, getResponse, parseJSON, loadModuleFromText, loadModuleFromFile,
getExternalConvertersDefinitions, removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase,
removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase,
parseEntityID, isEndpoint, isZHGroup, hours, minutes, seconds, validateFriendlyName, sleep,
sanitizeImageParameter, isAvailabilityEnabledForEntity, publishLastSeen, availabilityPayload,
getAllFiles, filterProperties, flatten, arrayUnique, clone, computeSettingsToChange,
getAllFiles, filterProperties, flatten, arrayUnique, clone, computeSettingsToChange, getScenes,
};
+2 -1
View File
@@ -4,7 +4,8 @@ import equals from 'fast-deep-equal/es6';
function read(file: string): KeyValue {
try {
return yaml.load(fs.readFileSync(file, 'utf8'));
const result = yaml.load(fs.readFileSync(file, 'utf8'));
return result as KeyValue ?? {};
} catch (error) {
if (error.name === 'YAMLException') {
error.file = file;
+11 -4
View File
@@ -63,6 +63,10 @@ export default class Zigbee {
throw error;
}
for (const device of this.devices(false)) {
await device.resolveDefinition();
}
this.herdsman.on('adapterDisconnected', () => this.eventBus.emitAdapterDisconnected());
this.herdsman.on('lastSeenChanged', (data: ZHEvents.LastSeenChangedPayload) => {
this.eventBus.emitLastSeenChanged({device: this.resolveDevice(data.device.ieeeAddr), reason: data.reason});
@@ -80,15 +84,17 @@ export default class Zigbee {
logger.debug(`Device '${device.name}' announced itself`);
this.eventBus.emitDeviceAnnounce({device});
});
this.herdsman.on('deviceInterview', (data: ZHEvents.DeviceInterviewPayload) => {
this.herdsman.on('deviceInterview', async (data: ZHEvents.DeviceInterviewPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
await device.resolveDefinition();
/* istanbul ignore if */ if (!device) return; // Prevent potential race
const d = {device, status: data.status};
this.logDeviceInterview(d);
this.eventBus.emitDeviceInterview(d);
});
this.herdsman.on('deviceJoined', (data: ZHEvents.DeviceJoinedPayload) => {
this.herdsman.on('deviceJoined', async (data: ZHEvents.DeviceJoinedPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
await device.resolveDefinition();
/* istanbul ignore if */ if (!device) return; // Prevent potential race
logger.info(`Device '${device.name}' joined`);
this.eventBus.emitDeviceJoined({device});
@@ -98,8 +104,9 @@ export default class Zigbee {
logger.warn(`Device '${name}' left the network`);
this.eventBus.emitDeviceLeave({ieeeAddr: data.ieeeAddr, name});
});
this.herdsman.on('message', (data: ZHEvents.MessagePayload) => {
this.herdsman.on('message', async (data: ZHEvents.MessagePayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
await device.resolveDefinition();
logger.debug(`Received Zigbee message from '${device.name}', type '${data.type}', ` +
`cluster '${data.cluster}', data '${stringify(data.data)}' from endpoint ${data.endpoint.ID}` +
(data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``) +
@@ -149,7 +156,7 @@ export default class Zigbee {
if (data.status === 'successful') {
logger.info(`Successfully interviewed '${name}', device has successfully been paired`);
if (data.device.definition) {
if (data.device.isSupported) {
const {vendor, description, model} = data.device.definition;
logger.info(`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`);
} else {
+1224 -8547
View File
File diff suppressed because it is too large Load Diff
+33 -29
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "1.33.2",
"version": "1.35.0",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"repository": {
@@ -8,7 +8,7 @@
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
},
"engines": {
"node": "^16 || ^18 || ^19 || ^20"
"node": "^18 || ^20 || ^21"
},
"keywords": [
"xiaomi",
@@ -39,19 +39,19 @@
"dependencies": {
"ajv": "^8.12.0",
"bind-decorator": "^1.0.11",
"connect-gzip-static": "2.1.1",
"debounce": "^1.2.1",
"connect-gzip-static": "3.0.1",
"debounce": "^2.0.0",
"deep-object-diff": "^1.1.9",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.2.0",
"git-last-commit": "^1.0.1",
"humanize-duration": "^3.30.0",
"humanize-duration": "^3.31.0",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"jszip": "^3.10.1",
"mkdir-recursive": "^0.4.0",
"moment": "^2.29.4",
"mqtt": "5.1.3",
"moment": "^2.30.1",
"mqtt": "^5.3.4",
"object-assign-deep": "^0.4.0",
"rimraf": "^5.0.5",
"semver": "^7.5.4",
@@ -60,36 +60,40 @@
"winston": "^3.11.0",
"winston-syslog": "^2.7.0",
"winston-transport": "^4.6.0",
"ws": "^8.14.2",
"zigbee-herdsman": "0.21.0",
"zigbee-herdsman-converters": "15.106.0",
"zigbee2mqtt-frontend": "0.6.142"
"ws": "^8.16.0",
"zigbee-herdsman": "0.30.0",
"zigbee-herdsman-converters": "18.3.0",
"zigbee2mqtt-frontend": "0.6.150"
},
"devDependencies": {
"@babel/core": "^7.23.2",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@babel/preset-typescript": "^7.23.2",
"@types/debounce": "^1.2.3",
"@types/finalhandler": "^1.2.2",
"@types/humanize-duration": "^3.27.2",
"@types/jest": "^29.5.6",
"@types/js-yaml": "^4.0.8",
"@types/node": "^20.8.9",
"@types/object-assign-deep": "^0.4.2",
"@types/readable-stream": "4.0.4",
"@babel/core": "^7.23.7",
"@babel/plugin-proposal-decorators": "^7.23.7",
"@babel/preset-env": "^7.23.7",
"@babel/preset-typescript": "^7.23.3",
"@types/debounce": "^1.2.4",
"@types/finalhandler": "^1.2.3",
"@types/humanize-duration": "^3.27.3",
"@types/jest": "^29.5.11",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.10.6",
"@types/object-assign-deep": "^0.4.3",
"@types/readable-stream": "4.0.10",
"@types/rimraf": "^3.0.2",
"@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"babel-jest": "^29.7.0",
"eslint": "^8.52.0",
"eslint": "^8.56.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-jest": "^27.6.0",
"jest": "^29.7.0",
"tmp": "^0.2.1",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
},
"overrides": {
"zigbee-herdsman-converters": {
"zigbee-herdsman": "$zigbee-herdsman"
}
},
"jest": {
"coverageThreshold": {
+43 -31
View File
@@ -8,6 +8,7 @@ const zhc = require('zigbee-herdsman-converters');
const z2mTillVersion = process.argv[2];
const zhcTillVersion = process.argv[3];
const zhTillVersion = process.argv[4];
const frontendTillVersion = process.argv[5];
const changelogs = [
{tillVersion: z2mTillVersion, project: 'koenkk/zigbee2mqtt',
@@ -16,16 +17,19 @@ const changelogs = [
contents: fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'zigbee-herdsman-converters', 'CHANGELOG.md'), 'utf-8').split('\n')},
{tillVersion: zhTillVersion, project: 'koenkk/zigbee-herdsman',
contents: fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'zigbee-herdsman', 'CHANGELOG.md'), 'utf-8').split('\n')},
{tillVersion: frontendTillVersion, project: 'nurikk/zigbee2mqtt-frontend', isFrontend: true,
contents: fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'zigbee2mqtt-frontend', 'CHANGELOG.md'), 'utf-8').split('\n')},
];
const releaseRe = /## \[(.+)\]/;
const changes = {features: [], fixes: [], detect: [], add: [], error: []};
const changes = {features: [], fixes: [], detect: [], add: [], error: [], frontend: []};
let context = null;
const changeRe = [
/^\* (\*\*(.+):\*\*)?(.+)\((\[#\d+\]\(.+\))\) \(\[.+\]\(https:.+\/(.+)\)\)$/,
/^\* (\*\*(.+):\*\*)?(.+)(https:\/\/github\.com.+) \(\[.+\]\(https:.+\/(.+)\)\)$/,
/^\* (\*\*(.+):\*\*)?(.+)() \(\[.+\]\(https:.+\/(.+)\)\)$/,
];
const frontendChangeRe = /^\* (\*\*.+:\*\* )()?(.+) \(\[.+\]\(https:.+\/()(.+)\)\)(.+)?$/;
let commitUserLookup = {};
const commitUserFile = path.join(__dirname, 'commit-user-lookup.json');
@@ -36,7 +40,7 @@ if (fs.existsSync(commitUserFile)) {
for (const changelog of changelogs) {
for (const line of changelog.contents) {
const releaseMatch = line.match(releaseRe);
const changeMatch = changeRe.map((re) => line.match(re)).find((e) => e);
const changeMatch = changelog.isFrontend ? line.match(frontendChangeRe) : changeRe.map((re) => line.match(re)).find((e) => e);
if (releaseMatch) {
if (releaseMatch[1] === changelog.tillVersion) {
break;
@@ -48,45 +52,52 @@ for (const changelog of changelogs) {
} else if (line.startsWith('* **ignore:**')) {
continue;
} else if (changeMatch) {
let localContext = changeMatch[2] ? changeMatch[2] : context;
if (!changes[localContext]) throw new Error(`Unknown context: ${localContext}`);
let localContext = changelog.isFrontend ? 'frontend' : (changeMatch[2] ? changeMatch[2] : context);
if (!changes[localContext]) localContext = 'error';
const commitUserKey = `${changelog.project}-${changeMatch[5]} `;
const commit = changeMatch[5];
const commitUserKey = `${changelog.project}-${commit} `;
let user = commitUserKey in commitUserLookup ? commitUserLookup[commitUserKey] :
execSync(`curl -s https://api.github.com/repos/${changelog.project}/commits/${changeMatch[5]} | jq -r '.author.login'`).toString().trim();
execSync(`curl -s https://api.github.com/repos/${changelog.project}/commits/${commit} | jq -r '.author.login'`).toString().trim();
if (user !== 'null') commitUserLookup[commitUserKey] = user;
const messages = [];
let message = changeMatch[3].trim();
if (message.endsWith('.')) message = message.substring(0, message.length - 1);
message = message.charAt(0).toUpperCase() + message.slice(1);
const otherUser = message.match(/\[@(.+)\]\(https:\/\/github.com\/.+\)/) || message.match(/@(.+)/);
if (otherUser) {
user = otherUser[1];
message = message.replace(otherUser[0], '');
}
if (localContext === 'add') {
for (const model of message.split(',')) {
const definition = zhc.definitions.find((d) => d.model === model.trim());
if (definition) {
messages.push(`\`${definition.model}\` ${definition.vendor} ${definition.description}`);
} else {
changes['error'].push(`${line} (model '${model}' does not exist)`);
}
}
} else {
if (changelog.isFrontend) {
changes[localContext].push(`- [${commit.slice(0, 7)}](https://github.com/${changelog.project}/commit/${commit}) ${message} (@${user})`);
messages.push(message);
}
} else {
const otherUser = message.match(/\[@(.+)\]\(https:\/\/github.com\/.+\)/) || message.match(/@(.+)/);
if (otherUser) {
user = otherUser[1];
message = message.replace(otherUser[0], '');
}
let issue = changeMatch[4].trim();
if (issue && !issue.startsWith('[#')) issue = `[#${issue.split('/').pop()}](${issue})`;
if (!issue) {
issue = '_NO_ISSUE_';
localContext = 'error';
}
if (localContext === 'add') {
for (const model of message.split(',')) {
const definition = zhc.definitions.find((d) => d.model === model.trim());
if (definition) {
messages.push(`\`${definition.model}\` ${definition.vendor} ${definition.description}`);
} else {
changes['error'].push(`${line} (model '${model}' does not exist)`);
}
}
} else {
messages.push(message);
}
messages.forEach((m) => changes[localContext].push(`- ${issue} ${m} (@${user})`));
} else if (line === '# Changelog' || !line) {
let issue = changeMatch[4].trim();
if (issue && !issue.startsWith('[#')) issue = `[#${issue.split('/').pop()}](${issue})`;
if (!issue) {
issue = '_NO_ISSUE_';
localContext = 'error';
}
messages.forEach((m) => changes[localContext].push(`- ${issue} ${m} (@${user})`));
}
} else if (line === '# Changelog' || line === '### ⚠ BREAKING CHANGES' || !line) {
continue;
} else {
throw new Error(`Unmatched line: ${line}`);
@@ -98,6 +109,7 @@ let result = '';
const names = [
['features', 'Improvements'],
['fixes', 'Fixes'],
['frontend', 'Frontend'],
['add', 'New supported devices'],
['detect', 'Fixed device detections'],
['error', 'Changelog generator error'],
-12
View File
@@ -1,12 +0,0 @@
const fs = require('fs');
const filename = process.argv[2];
const text = fs.readFileSync(filename, 'utf8');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
for (const line of text.split('\n')) {
const [modelNumber, user] = line.split(',').map((t) => t.trim());
const model = zigbeeHerdsmanConverters.devices.find((d) => d.model === modelNumber);
if (!model) throw new Error(`${line} does not exist`);
console.log(`- \`${modelNumber}\` ${model.vendor} ${model.description} (${user})`);
}
+1 -1
View File
@@ -24,7 +24,7 @@ echo "Cloning Zigbee2MQTT git repository"
git clone https://github.com/Koenkk/zigbee2mqtt.git /opt/zigbee2mqtt
chown -R pi:pi /opt/zigbee2mqtt
echo "Running install. This might take a while and can produce som expected errors"
echo "Running install. This might take a while and can produce some expected errors"
cd /opt/zigbee2mqtt || exit
su pi -c "npm ci"
+25
View File
@@ -6,6 +6,7 @@ import zigbeeHerdsman from './stub/zigbeeHerdsman';
import utils from '../lib/util/utils';
import * as settings from '../lib/util/settings';
import Controller from '../lib/controller';
import Availability from '../lib/extension/availability';
import flushPromises from './lib/flushPromises';
import stringify from 'json-stable-stringify-without-jsonify';
@@ -344,4 +345,28 @@ describe('Availability', () => {
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'online', {retain: true, qos: 1}, expect.any(Function));
});
it('Should clear the ping queue on stop', async () => {
const availability = controller.extensions.find((extension) => extension instanceof Availability);
const publishAvailabilitySpy = jest.spyOn(availability, 'publishAvailability');
devices.bulb_color.zh = { ping: jest.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 1000)))};
availability.addToPingQueue(devices.bulb_color);
availability.addToPingQueue(devices.bulb_color_2);
await availability.stop();
await advancedTime(utils.minutes(1));
expect(availability.pingQueue).toEqual([]);
// Validate the stop-interrupt implicitly by checking that it prevents further function invocations
expect(publishAvailabilitySpy).not.toHaveBeenCalled();
});
it('Should prevent instance restart', async () => {
const availability = controller.extensions.find((extension) => extension instanceof Availability);
await availability.stop();
await expect(() => availability.start()).rejects.toThrowError();
});
});
+1 -1
View File
@@ -157,7 +157,7 @@ describe('Bind', () => {
expect(target.configureReporting).toHaveBeenCalledWith("genOnOff",[{"attribute": "onOff", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 0, "reportableChange": 0}]);
expect(target.configureReporting).toHaveBeenCalledWith("genLevelCtrl",[{"attribute": "currentLevel", "maximumReportInterval": 0xFFFF, "minimumReportInterval": 5, "reportableChange": 1}]);
expect(target.configureReporting).toHaveBeenCalledWith("lightingColorCtrl",[{"attribute":"colorTemperature","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1},{"attribute":"currentX","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1},{"attribute":"currentY","minimumReportInterval":5,"maximumReportInterval":0xFFFF,"reportableChange":1}]);
expect(zigbeeHerdsman.devices.bulb_color.meta.configured).toBe(2);
expect(zigbeeHerdsman.devices.bulb_color.meta.configured).toBe(1324213189);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}),
+43 -5
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -546,7 +546,7 @@ describe('Controller', () => {
device = controller.zigbee.resolveEntity('unsupported2');
await controller.publishEntityState(device, {state: 'ON'});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/unsupported2', stringify({"state":"ON","device":{"friendlyName":"unsupported2","model":"unknown","ieeeAddr":"0x0017880104e45529","networkAddress":6536,"type":"EndDevice","manufacturerID":0,"powerSource":"Battery","dateCode":null, "softwareBuildID": null}}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/unsupported2', stringify({"state":"ON","device":{"friendlyName":"unsupported2","model":"notSupportedModelID","ieeeAddr":"0x0017880104e45529","networkAddress":6536,"type":"EndDevice","manufacturerID":0,"powerSource":"Battery","dateCode":null, "softwareBuildID": null}}), {"qos": 0, "retain": false}, expect.any(Function));
});
it('Should publish entity state without retain', async () => {
+21 -15
View File
@@ -10,9 +10,9 @@ const flushPromises = require('./lib/flushPromises');
const path = require('path');
const fs = require('fs');
zigbeeHerdsmanConverters.addDeviceDefinition = jest.fn();
zigbeeHerdsmanConverters.addDefinition = jest.fn();
const mocksClear = [zigbeeHerdsmanConverters.addDeviceDefinition, zigbeeHerdsman.permitJoin,
const mocksClear = [zigbeeHerdsmanConverters.addDefinition, zigbeeHerdsman.permitJoin,
mockExit, MQTT.end, zigbeeHerdsman.stop, logger.debug,
MQTT.publish, MQTT.connect, zigbeeHerdsman.devices.bulb_color.removeFromNetwork,
zigbeeHerdsman.devices.bulb.removeFromNetwork, logger.error,
@@ -67,16 +67,15 @@ describe('Loads external converters', () => {
it('Does not load external converters', async () => {
settings.set(['external_converters'], []);
await resetExtension();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(0);
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenCalledTimes(0);
});
it('Loads external converters', async () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter.js'), path.join(data.mockDir, 'mock-external-converter.js'));
const devicesCount = zigbeeHerdsman.devices.lenght;
settings.set(['external_converters'], ['mock-external-converter.js']);
await resetExtension();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledWith({
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenCalledWith({
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
@@ -90,11 +89,10 @@ describe('Loads external converters', () => {
it('Loads multiple external converters', async () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
const devicesCount = zigbeeHerdsman.devices.lenght;
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
await resetExtension();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2);
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(1, {
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenCalledTimes(2);
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenNthCalledWith(1, {
mock: 1,
model: 'external_converters_device_1',
zigbeeModel: ['external_converter_device_1'],
@@ -104,7 +102,7 @@ describe('Loads external converters', () => {
toZigbee: [],
exposes: []
});
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(2, {
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenNthCalledWith(2, {
mock: 2,
model: 'external_converters_device_2',
zigbeeModel: ['external_converter_device_2'],
@@ -119,8 +117,8 @@ describe('Loads external converters', () => {
it('Loads external converters from package', async () => {
settings.set(['external_converters'], ['mock-external-converter-module']);
await resetExtension();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledWith({
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenCalledWith({
mock: true
});
});
@@ -128,12 +126,20 @@ describe('Loads external converters', () => {
it('Loads multiple external converters from package', async () => {
settings.set(['external_converters'], ['mock-multiple-external-converter-module']);
await resetExtension();
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2);
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(1, {
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenCalledTimes(2);
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenNthCalledWith(1, {
mock: 1
});
expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(2, {
expect(zigbeeHerdsmanConverters.addDefinition).toHaveBeenNthCalledWith(2, {
mock: 2
});
});
it('Loads external converters with error', async () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter.js'), path.join(data.mockDir, 'mock-external-converter.js'));
settings.set(['external_converters'], ['mock-external-converter.js']);
zigbeeHerdsmanConverters.addDefinition.mockImplementationOnce(() => {throw new Error('Invalid definition!')});
await resetExtension();
expect(logger.error).toHaveBeenCalledWith(`Failed to load external converter file 'mock-external-converter.js' (Invalid definition!)`);
});
});
+24
View File
@@ -135,6 +135,30 @@ describe('Frontend', () => {
mockHTTPS.implementation.listen.mockClear();
});
it('Start/stop without host', async () => {
settings.set(['frontend'], {port: 8081});
controller = new Controller(jest.fn(), jest.fn());
await controller.start();
expect(mockNodeStatic.variables.path).toBe("my/dummy/path");
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081);
const mockWSClient = {
implementation: {
terminate: jest.fn(),
send: jest.fn(),
},
events: {},
};
mockWS.implementation.clients.push(mockWSClient.implementation);
await controller.stop();
expect(mockWSClient.implementation.terminate).toHaveBeenCalledTimes(1);
expect(mockHTTP.implementation.close).toHaveBeenCalledTimes(1);
expect(mockWS.implementation.close).toHaveBeenCalledTimes(1);
mockWS.implementation.close.mockClear();
mockHTTP.implementation.close.mockClear();
mockHTTP.implementation.listen.mockClear();
mockHTTPS.implementation.listen.mockClear();
});
it('Start/stop unix socket', async () => {
settings.set(['frontend'], {host: "/tmp/zigbee2mqtt.sock"});
controller = new Controller(jest.fn(), jest.fn());
+1 -1
View File
@@ -41,7 +41,7 @@ describe('Groups', () => {
settings.reRead();
MQTT.publish.mockClear();
zigbeeHerdsman.groups.gledopto_group.command.mockClear();
zigbeeHerdsmanConverters.toZigbeeConverters.__clearStore__();
zigbeeHerdsmanConverters.toZigbee.__clearStore__();
controller.state.state = {};
})
+412 -32
View File
@@ -11,6 +11,7 @@ const path = require('path');
describe('HomeAssistant extension', () => {
let version;
let z2m_version;
let controller;
let extension;
let origin;
@@ -32,9 +33,9 @@ describe('HomeAssistant extension', () => {
});
beforeAll(async () => {
version = await require('../lib/util/utils').default.getZigbee2MQTTVersion();
origin = {name: 'Zigbee2MQTT', sw: version.version, url: 'https://www.zigbee2mqtt.io'}
version = `Zigbee2MQTT ${version.version}`;
z2m_version = (await require('../lib/util/utils').default.getZigbee2MQTTVersion()).version;
origin = {name: 'Zigbee2MQTT', sw: z2m_version, url: 'https://www.zigbee2mqtt.io'};
version = `Zigbee2MQTT ${z2m_version}`;
jest.useFakeTimers();
settings.set(['homeassistant'], true);
data.writeDefaultConfiguration();
@@ -51,9 +52,9 @@ describe('HomeAssistant extension', () => {
it('Should not have duplicate type/object_ids in a mapping', () => {
const duplicated = [];
require('zigbee-herdsman-converters').devices.forEach((d) => {
require('zigbee-herdsman-converters').definitions.forEach((d) => {
const exposes = typeof d.exposes == 'function' ? d.exposes() : d.exposes;
const device = {definition: d, isDevice: () => true, options: {}, exposes: () => exposes};
const device = {definition: d, isDevice: () => true, isGroup: () => false, options: {}, exposes: () => exposes, zh: {endpoints: []}};
const configs = extension.getConfigs(device);
const cfg_type_object_ids = [];
@@ -87,6 +88,9 @@ describe('HomeAssistant extension', () => {
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group",
"sw_version": version,
"model": "Group",
"manufacturer": "Zigbee2MQTT",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"max_mireds": 454,
"min_mireds": 250,
@@ -130,6 +134,9 @@ describe('HomeAssistant extension', () => {
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group",
"sw_version": version,
"model": "Group",
"manufacturer": "Zigbee2MQTT",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"json_attributes_topic":"zigbee2mqtt/ha_discovery_group",
"name":null,
@@ -165,6 +172,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'enabled_by_default': true,
@@ -194,6 +202,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -222,6 +231,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -251,6 +261,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -281,6 +292,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -302,7 +314,8 @@ describe('HomeAssistant extension', () => {
"manufacturer":"Xiaomi",
"model":"Aqara double key wired wall switch without neutral wire. Doesn't work as a router and doesn't support power meter (QBKG03LM)",
"name":"wall_switch_double",
"sw_version": null
"sw_version": null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"json_attributes_topic":"zigbee2mqtt/wall_switch_double",
"name":"Left",
@@ -332,7 +345,8 @@ describe('HomeAssistant extension', () => {
"manufacturer":"Xiaomi",
"model":"Aqara double key wired wall switch without neutral wire. Doesn't work as a router and doesn't support power meter (QBKG03LM)",
"name":"wall_switch_double",
"sw_version": null
"sw_version": null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"json_attributes_topic":"zigbee2mqtt/wall_switch_double",
"name":"Right",
@@ -369,6 +383,7 @@ describe('HomeAssistant extension', () => {
"model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)",
"name":"bulb",
"sw_version": null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"effect":true,
"effect_list":[
@@ -427,6 +442,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -455,6 +471,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -483,6 +500,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -495,7 +513,7 @@ describe('HomeAssistant extension', () => {
);
});
it('Should discover devices with overriden user configuration', async () => {
it('Should discover devices with overridden user configuration', async () => {
settings.set(['devices', '0x0017880104e45522'], {
homeassistant: {
expire_after: 30,
@@ -541,6 +559,7 @@ describe('HomeAssistant extension', () => {
'sw_version': 'test',
'model': 'custom model',
'manufacturer': 'From Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'expire_after': 90,
@@ -568,6 +587,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'custom model',
'manufacturer': 'Not from Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'origin': origin,
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
@@ -584,7 +604,7 @@ describe('HomeAssistant extension', () => {
);
});
it('Should discover devices with overriden name', async () => {
it('Should discover devices with overridden name', async () => {
settings.set(['devices', '0x0017880104e45522'], {
homeassistant: {
name: "Weather Sensor",
@@ -614,6 +634,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'enabled_by_default': true,
@@ -643,6 +664,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -655,7 +677,7 @@ describe('HomeAssistant extension', () => {
);
});
it('Should discover devices with overriden user configuration affecting type and object_id', async () => {
it('Should discover devices with overridden user configuration affecting type and object_id', async () => {
settings.set(['devices', '0x0017880104e45541'], {
friendly_name: 'my_switch',
homeassistant: {
@@ -685,7 +707,8 @@ describe('HomeAssistant extension', () => {
"manufacturer": "Xiaomi",
"model": "Aqara single key wired wall switch without neutral wire. Doesn't work as a router and doesn't support power meter (QBKG04LM)",
"name": "my_switch",
"sw_version": null
"sw_version": null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"json_attributes_topic": "zigbee2mqtt/my_switch",
"name": "my_light_name_override",
@@ -768,7 +791,8 @@ describe('HomeAssistant extension', () => {
"name":"fan",
"sw_version": null,
"model":"Universal wink enabled white ceiling fan premier remote control (99432)",
"manufacturer":"Hampton Bay"
"manufacturer":"Hampton Bay",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -785,7 +809,7 @@ describe('HomeAssistant extension', () => {
let payload;
payload = {
"action_template":"{% set values = {None:None,'idle':'off','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
"action_template":"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
"action_topic":"zigbee2mqtt/TS0601_thermostat",
"availability":[
{
@@ -801,7 +825,8 @@ describe('HomeAssistant extension', () => {
"manufacturer":"TuYa",
"model":"Radiator valve with thermostat (TS0601_thermostat)",
"name":"TS0601_thermostat",
"sw_version":null
"sw_version":null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"preset_mode_command_topic":"zigbee2mqtt/TS0601_thermostat/set/preset",
"preset_modes":[
@@ -870,7 +895,8 @@ describe('HomeAssistant extension', () => {
name: 'smart vent',
sw_version: null,
model: 'Smart vent (SV01)',
manufacturer: 'Keen Home'
manufacturer: 'Keen Home',
via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -890,7 +916,8 @@ describe('HomeAssistant extension', () => {
"manufacturer": "Siglis",
"model": "zigfred plus smart in-wall switch (ZFP-1A-CH)",
"name": "zigfred_plus",
"sw_version": null
"sw_version": null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"json_attributes_topic": "zigbee2mqtt/zigfred_plus/l6",
"name": "L6",
@@ -942,6 +969,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -1070,6 +1098,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -1207,6 +1236,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability_mode': 'all',
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
@@ -1257,13 +1287,6 @@ describe('HomeAssistant extension', () => {
);
});
it('Should not clear discovery when unsupported device is removed', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/remove', 'unsupported2');
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
});
it('Should refresh discovery when device is renamed', async () => {
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45522/action_double/config', stringify({topic: 'zigbee2mqtt/weather_sensor/action'}));
await flushPromises();
@@ -1290,6 +1313,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -1324,7 +1348,8 @@ describe('HomeAssistant extension', () => {
"name":"weather_sensor_renamed",
"sw_version": null,
"model":"Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)",
"manufacturer":"Xiaomi"
"manufacturer":"Xiaomi",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
}
}),
{ retain: true, qos: 1 },
@@ -1349,6 +1374,9 @@ describe('HomeAssistant extension', () => {
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group_new",
"sw_version": version,
"model": "Group",
"manufacturer": "Zigbee2MQTT",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"json_attributes_topic":"zigbee2mqtt/ha_discovery_group_new",
"max_mireds": 454,
@@ -1422,6 +1450,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -1453,7 +1482,8 @@ describe('HomeAssistant extension', () => {
"name":"bulb",
'sw_version': null,
"model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)",
"manufacturer":"IKEA"
"manufacturer":"IKEA",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'device_class': 'update',
@@ -1495,7 +1525,8 @@ describe('HomeAssistant extension', () => {
"name":"button",
"sw_version": null,
"model":"Aqara wireless switch (WXKG11LM)",
"manufacturer":"Xiaomi"
"manufacturer":"Xiaomi",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
}
};
@@ -1520,7 +1551,8 @@ describe('HomeAssistant extension', () => {
"name":"button",
"sw_version": null,
"model":"Aqara wireless switch (WXKG11LM)",
"manufacturer":"Xiaomi"
"manufacturer":"Xiaomi",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
}
};
@@ -1609,7 +1641,7 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith('homeassistant/device_automation/0x0017880104e45520/action_double/config', expect.any(String), expect.any(Object), expect.any(Function));
// Should rediscover when already discovered in previous session but with diferent name
// Should rediscover when already discovered in previous session but with different name
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant')._clearDiscoveredTrigger();
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45520/action_double/config', stringify({topic: 'zigbee2mqtt/button_other_name/action'}));
await flushPromises();
@@ -1685,7 +1717,8 @@ describe('HomeAssistant extension', () => {
"name":"button",
"sw_version": null,
"model":"Aqara wireless switch (WXKG11LM)",
"manufacturer":"Xiaomi"
"manufacturer":"Xiaomi",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
}
};
@@ -1860,6 +1893,7 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae'
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -1887,6 +1921,9 @@ describe('HomeAssistant extension', () => {
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group",
"sw_version": version,
"model": "Group",
"manufacturer": "Zigbee2MQTT",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"json_attributes_topic":"zigbee2mqtt/ha_discovery_group",
"max_mireds": 454,
@@ -1938,6 +1975,9 @@ describe('HomeAssistant extension', () => {
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group",
"sw_version": version,
"model": "Group",
"manufacturer": "Zigbee2MQTT",
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"max_mireds": 454,
"min_mireds": 250,
@@ -1998,7 +2038,8 @@ describe('HomeAssistant extension', () => {
"manufacturer":"IKEA",
"model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)",
"name":"bulb",
"sw_version":null
"sw_version":null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"effect":true,
"effect_list":[
@@ -2048,7 +2089,8 @@ describe('HomeAssistant extension', () => {
"manufacturer":"IKEA",
"model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)",
"name":"bulb",
"sw_version": null
"sw_version": null,
"via_device": "zigbee2mqtt_bridge_0x00124b00120144ae",
},
"enabled_by_default":false,
"icon":"mdi:clock",
@@ -2096,7 +2138,8 @@ describe('HomeAssistant extension', () => {
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
'configuration_url': 'http://zigbee.mqtt/#/device/0x0017880104e45522/info'
'configuration_url': 'http://zigbee.mqtt/#/device/0x0017880104e45522/info',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
@@ -2108,4 +2151,341 @@ describe('HomeAssistant extension', () => {
expect.any(Function),
);
});
it('Should rediscover scenes when a scene is changed', async () => {
MQTT.publish.mockClear();
controller.eventBus.emitScenesChanged();
await flushPromises();
// Discovery messages for scenes have been purged.
expect(MQTT.publish).toHaveBeenCalledWith(
`homeassistant/scene/0x000b57fffec6a5b4/scene_1/config`,
null,
{retain: true, qos: 1},
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
`homeassistant/scene/1221051039810110150109113116116_9/scene_4/config`,
null,
{retain: true, qos: 1},
expect.any(Function),
);
jest.runOnlyPendingTimers();
await flushPromises();
let payload = {
'name': 'Chill scene',
'command_topic': 'zigbee2mqtt/bulb_color_2/set',
'payload_on': '{ "scene_recall": 1 }',
'json_attributes_topic': 'zigbee2mqtt/bulb_color_2',
'object_id': 'bulb_color_2_1_chill_scene',
'unique_id': '0x000b57fffec6a5b4_scene_1_zigbee2mqtt',
'device': {
'identifiers': [ 'zigbee2mqtt_0x000b57fffec6a5b4' ],
'name': 'bulb_color_2',
'sw_version': '5.127.1.26581',
'model': 'Hue Go (7146060PH)',
'manufacturer': 'Philips',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae',
},
'origin': origin,
'availability': [ { 'topic': 'zigbee2mqtt/bridge/state' } ]
}
expect(MQTT.publish).toHaveBeenCalledWith(
`homeassistant/scene/0x000b57fffec6a5b4/scene_1/config`,
stringify(payload),
{retain: true, qos: 1},
expect.any(Function),
);
payload = {
'name': 'Scene 4',
'command_topic': 'zigbee2mqtt/ha_discovery_group/set',
'payload_on': '{ "scene_recall": 4 }',
'json_attributes_topic':'zigbee2mqtt/ha_discovery_group',
'object_id': 'ha_discovery_group_4_scene_4',
'unique_id': '9_scene_4_zigbee2mqtt',
'device': {
'identifiers': [ 'zigbee2mqtt_1221051039810110150109113116116_9' ],
'name': 'ha_discovery_group',
'sw_version': version,
'model': 'Group',
'manufacturer': 'Zigbee2MQTT',
'via_device': 'zigbee2mqtt_bridge_0x00124b00120144ae'
},
'origin': origin,
'availability': [ { 'topic': 'zigbee2mqtt/bridge/state' } ]
}
expect(MQTT.publish).toHaveBeenCalledWith(
`homeassistant/scene/1221051039810110150109113116116_9/scene_4/config`,
stringify(payload),
{retain: true, qos: 1},
expect.any(Function),
);
// Only discovery entries for entities with scenes need to be republished.
expect(MQTT.publish).toHaveBeenCalledTimes( 16 );
});
it('Should not clear bridge entities unnecessarily', async () => {
MQTT.publish.mockClear();
const topic = 'homeassistant/button/1221051039810110150109113116116_0x00124b00120144ae/restart/config';
const payload = {
'name': 'Restart',
'object_id': 'zigbee2mqtt_bridge_restart',
'unique_id': 'bridge_0x00124b00120144ae_restart_zigbee2mqtt',
'device_class': 'restart',
'command_topic': 'zigbee2mqtt/bridge/request/restart',
'payload_press': '',
'origin': origin,
'device': {
'name': 'Zigbee2MQTT Bridge',
'identifiers': ['zigbee2mqtt_bridge_0x00124b00120144ae'],
'manufacturer': 'Zigbee2MQTT',
'model': 'Bridge',
'hw_version': 'z-Stack 20190425',
'sw_version': z2m_version,
},
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
controller.eventBus.emitMQTTMessage({
topic: topic,
message: stringify(payload),
});
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith(
topic,
null,
{retain: true, qos: 1},
expect.any(Function),
);
});
it('Should discover bridge entities', async () => {
settings.set(['advanced', 'homeassistant_legacy_entity_attributes'], false);
await resetExtension();
const devicePayload = {
'name': 'Zigbee2MQTT Bridge',
'identifiers': ['zigbee2mqtt_bridge_0x00124b00120144ae'],
'manufacturer': 'Zigbee2MQTT',
'model': 'Bridge',
'hw_version': 'z-Stack 20190425',
'sw_version': z2m_version,
};
// Binary sensors.
let payload;
payload = {
'name': 'Connection state',
'object_id': 'zigbee2mqtt_bridge_connection_state',
'entity_category': 'diagnostic',
'device_class': 'connectivity',
'unique_id': 'bridge_0x00124b00120144ae_connection_state_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/state',
'value_template': '{{ value_json.state }}',
'payload_on': 'online',
'payload_off': 'offline',
'origin': origin,
'device': devicePayload,
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/binary_sensor/1221051039810110150109113116116_0x00124b00120144ae/connection_state/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
payload = {
'name': 'Restart required',
'object_id': 'zigbee2mqtt_bridge_restart_required',
'entity_category': 'diagnostic',
'device_class': 'problem',
'enabled_by_default': false,
'unique_id': 'bridge_0x00124b00120144ae_restart_required_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/info',
'value_template': '{{ value_json.restart_required }}',
'payload_on': true,
'payload_off': false,
'origin': origin,
'device': devicePayload,
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/binary_sensor/1221051039810110150109113116116_0x00124b00120144ae/restart_required/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
// Buttons.
payload = {
'name': 'Restart',
'object_id': 'zigbee2mqtt_bridge_restart',
'unique_id': 'bridge_0x00124b00120144ae_restart_zigbee2mqtt',
'device_class': 'restart',
'command_topic': 'zigbee2mqtt/bridge/request/restart',
'payload_press': '',
'origin': origin,
'device': devicePayload,
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/button/1221051039810110150109113116116_0x00124b00120144ae/restart/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
// Selects.
payload = {
'name': 'Log level',
'object_id': 'zigbee2mqtt_bridge_log_level',
'entity_category': 'config',
'unique_id': 'bridge_0x00124b00120144ae_log_level_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/info',
'value_template': '{{ value_json.log_level | lower }}',
'command_topic': 'zigbee2mqtt/bridge/request/options',
'command_template':
'{"options": {"advanced": {"log_level": "{{ value }}" } } }',
'options': ['info', 'warn', 'error', 'debug'],
'origin': origin,
'device': devicePayload,
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/select/1221051039810110150109113116116_0x00124b00120144ae/log_level/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
// Sensors.
payload = {
'name': 'Version',
'object_id': 'zigbee2mqtt_bridge_version',
'entity_category': 'diagnostic',
'icon': 'mdi:zigbee',
'unique_id': 'bridge_0x00124b00120144ae_version_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/info',
'value_template': '{{ value_json.version }}',
'origin': origin,
'device': devicePayload,
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/1221051039810110150109113116116_0x00124b00120144ae/version/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
payload = {
'name': 'Coordinator version',
'object_id': 'zigbee2mqtt_bridge_coordinator_version',
'entity_category': 'diagnostic',
'enabled_by_default': false,
'icon': 'mdi:chip',
'unique_id': 'bridge_0x00124b00120144ae_coordinator_version_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/info',
'value_template': '{{ value_json.coordinator.meta.revision }}',
'origin': origin,
'device': devicePayload,
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/1221051039810110150109113116116_0x00124b00120144ae/coordinator_version/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
payload = {
'name': 'Network map',
'object_id': 'zigbee2mqtt_bridge_network_map',
'entity_category': 'diagnostic',
'enabled_by_default': false,
'unique_id': 'bridge_0x00124b00120144ae_network_map_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/response/networkmap',
'value_template': "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}",
'json_attributes_topic': 'zigbee2mqtt/bridge/response/networkmap',
'json_attributes_template': '{{ value_json.data.value | tojson }}',
'origin': origin,
'device': devicePayload,
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/1221051039810110150109113116116_0x00124b00120144ae/network_map/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
payload = {
'name': 'Permit join timeout',
'object_id': 'zigbee2mqtt_bridge_permit_join_timeout',
'entity_category': 'diagnostic',
'device_class': 'duration',
'unit_of_measurement': 's',
'unique_id': 'bridge_0x00124b00120144ae_permit_join_timeout_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/info',
'value_template': '{{ iif(value_json.permit_join_timeout is defined, value_json.permit_join_timeout, None) }}',
'origin': origin,
'device': devicePayload,
'availability': [{'topic': 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/1221051039810110150109113116116_0x00124b00120144ae/permit_join_timeout/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
// Switches.
payload = {
'name': 'Permit join',
'object_id': 'zigbee2mqtt_bridge_permit_join',
'icon': 'mdi:human-greeting-proximity',
'unique_id': 'bridge_0x00124b00120144ae_permit_join_zigbee2mqtt',
'state_topic': 'zigbee2mqtt/bridge/info',
'value_template': '{{ value_json.permit_join | lower }}',
'command_topic': 'zigbee2mqtt/bridge/request/permit_join',
'payload_on': 'true',
'payload_off': 'false',
'origin': origin,
'device': devicePayload,
'availability': [ { 'topic': 'zigbee2mqtt/bridge/state' } ],
'availability_mode': 'all'
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/switch/1221051039810110150109113116116_0x00124b00120144ae/permit_join/config',
stringify(payload),
{ retain: true, qos: 1 },
expect.any(Function),
);
});
it('Should remove discovery entries for removed exposes when device options change', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/options', stringify({"id": "0xf4ce368a38be56a1", "options": {"dimmer_1_enabled": "false", "dimmer_1_dimming_enabled": "false"}}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/0xf4ce368a38be56a1/light_l2/config',
null,
{ retain: true, qos: 1 },
expect.any(Function),
);
});
});
+6 -6
View File
@@ -102,7 +102,7 @@ describe('Networkmap', () => {
let call = MQTT.publish.mock.calls[0];
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/raw');
const expected = {"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, power_on_behavior, color_options, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, power_outage_count, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description": "external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]};
const expected = {"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, power_on_behavior, color_options, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, power_outage_count, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":{"description":"Automatically generated definition","model":"notSupportedModelID","supports":"linkquality","vendor":"Boef"},"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]};
expect(JSON.parse(call[1])).toStrictEqual(expected);
/**
@@ -150,7 +150,7 @@ describe('Networkmap', () => {
"0x000b57fffec6a5b3" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="110"]
"0x0017880104e45521" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{button_double_key|0x0017880104e45521 (0x198a)|Xiaomi Aqara double key wireless wall switch (2016 model) (WXKG02LM_rev1)|9 seconds ago}"];
"0x0017880104e45521" -> "0x0017880104e45559" [penwidth=1, weight=0, color="#994444", label="130"]
"0x0017880104e45525" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{0x0017880104e45525|0x0017880104e45525 (0x1988)failed: lqi,routingTable|Boef notSupportedModelID|9 seconds ago}"];
"0x0017880104e45525" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{0x0017880104e45525|0x0017880104e45525 (0x1988)failed: lqi,routingTable|Boef Automatically generated definition (notSupportedModelID)|9 seconds ago}"];
"0x0017880104e45559" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{cc2530_router|0x0017880104e45559 (0x198c)|Custom devices (DiY) [CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/) (CC2530.ROUTER)|9 seconds ago}"];
"0x0017880104e45559" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="100"]
"0x0017880104e45511" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{0x0017880104e45511|0x0017880104e45511 (0x045a)|external external (external_converter_device)|9 seconds ago}"];
@@ -197,7 +197,7 @@ describe('Networkmap', () => {
---
0x0017880104e45525 (0x1988) failed: lqi,routingTable
---
Boef notSupportedModelID
Boef Automatically generated definition (notSupportedModelID)
---
9 seconds ago
]
@@ -276,12 +276,12 @@ describe('Networkmap', () => {
let call = MQTT.publish.mock.calls[0];
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, power_on_behavior, color_options, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, power_outage_count, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"};
const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":110,"lqi":110,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":100,"lqi":100,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"sourceIeeeAddr":"0x0017880104e45559","sourceNwkAddr":6540,"target":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"targetIeeeAddr":"0x000b57fffec6a5b2"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","model":"LED1545G12","supports":"light (state, brightness, color_temp, color_temp_startup), effect, power_on_behavior, color_options, linkquality","vendor":"IKEA"},"failed":[],"friendlyName":"bulb","ieeeAddr":"0x000b57fffec6a5b2","lastSeen":1000,"modelID":"TRADFRI bulb E27 WS opal 980lm","networkAddress":40369,"type":"Router"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, power_outage_count, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":{"description":"Automatically generated definition","model":"notSupportedModelID","supports":"linkquality","vendor":"Boef"},"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"};
const actual = JSON.parse(call[1]);
expect(actual).toStrictEqual(expected);
});
it('Should throw error when rquesting invalid type', async () => {
it('Should throw error when requesting invalid type', async () => {
mock();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', 'not_existing');
@@ -303,7 +303,7 @@ describe('Networkmap', () => {
let call = MQTT.publish.mock.calls[0];
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, power_outage_count, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"};
const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, power_outage_count, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":{"description":"Automatically generated definition","model":"notSupportedModelID","supports":"linkquality","vendor":"Boef"},"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"}
const actual = JSON.parse(call[1]);
expect(actual).toStrictEqual(expected);
});
+2 -3
View File
@@ -7,13 +7,12 @@ const MQTT = require('./stub/mqtt');
const settings = require('../lib/util/settings');
const Controller = require('../lib/controller');
const flushPromises = require('./lib/flushPromises');
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
const mocksClear = [MQTT.publish, logger.warn, logger.debug];
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const mockOnEvent = jest.fn();
const mappedLivolo = zigbeeHerdsmanConverters.findByDevice(zigbeeHerdsman.devices.LIVOLO);
const mappedLivolo = zigbeeHerdsmanConverters.findByModel('TI0001');
mappedLivolo.onEvent = mockOnEvent;
zigbeeHerdsmanConverters.onEvent = jest.fn();
@@ -76,6 +75,6 @@ describe('On event', () => {
await zigbeeHerdsman.events.deviceAnnounce({device});
await flushPromises();
expect(zigbeeHerdsmanConverters.onEvent).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsmanConverters.onEvent).toHaveBeenCalledWith('deviceAnnounce', {device}, device, settings.getDevice(device.ieeeAddr), {});
expect(zigbeeHerdsmanConverters.onEvent).toHaveBeenCalledWith('deviceAnnounce', {device}, device);
});
});
+20 -20
View File
@@ -65,7 +65,7 @@ describe('OTA update', () => {
count++;
return {swBuildId: count, dateCode: '2019010' + count}
});
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
logger.info.mockClear();
device.save.mockClear();
@@ -120,7 +120,7 @@ describe('OTA update', () => {
const device = zigbeeHerdsman.devices.bulb;
const endpoint = device.endpoints[0];
endpoint.read.mockImplementation(() => {return {swBuildId: 1, dateCode: '2019010'}});
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
device.save.mockClear();
mapped.ota.updateToLatest.mockImplementationOnce((a, b, onUpdate) => {
@@ -143,7 +143,7 @@ describe('OTA update', () => {
it('Should be able to check if OTA update is available', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: false, currentFileVersion: 10, otaFileVersion: 10});
@@ -172,9 +172,9 @@ describe('OTA update', () => {
it('Should handle if OTA update check fails', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {throw new Error('RF singals disturbed because of dogs barking')});
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {throw new Error('RF signals disturbed because of dogs barking')});
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', "bulb");
await flushPromises();
@@ -182,7 +182,7 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({"data":{"id": "bulb"},"status":"error","error": `Failed to check if update available for 'bulb' (RF singals disturbed because of dogs barking)`}),
stringify({"data":{"id": "bulb"},"status":"error","error": `Failed to check if update available for 'bulb' (RF signals disturbed because of dogs barking)`}),
{retain: false, qos: 0}, expect.any(Function)
);
});
@@ -209,7 +209,7 @@ describe('OTA update', () => {
it('Should refuse to check/update when already in progress', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {
@@ -234,7 +234,7 @@ describe('OTA update', () => {
const endpoint = device.endpoints[0];
endpoint.read.mockImplementation(() => {throw new Error('Failed!')});
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', "bulb");
await flushPromises();
@@ -249,7 +249,7 @@ describe('OTA update', () => {
const device = zigbeeHerdsman.devices.bulb;
device.endpoints[0].commandResponse.mockClear();
const data = {imageType: 12382};
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 12});
const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}};
@@ -283,7 +283,7 @@ describe('OTA update', () => {
const device = zigbeeHerdsman.devices.bulb;
device.endpoints[0].commandResponse.mockClear();
const data = {imageType: 12382};
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {throw new Error('Nothing to find here')})
const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}};
@@ -305,7 +305,7 @@ describe('OTA update', () => {
const device = zigbeeHerdsman.devices.bulb;
device.endpoints[0].commandResponse.mockClear();
const data = {imageType: 12382};
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: false, currentFileVersion: 13, otaFileVersion: 13});
const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}};
@@ -327,7 +327,7 @@ describe('OTA update', () => {
settings.set(['ota', 'disable_automatic_update_check'], true);
const device = zigbeeHerdsman.devices.bulb;
const data = {imageType: 12382};
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 13});
const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}};
@@ -366,7 +366,7 @@ describe('OTA update', () => {
count++;
return {swBuildId: count, dateCode: '2019010' + count}
});
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
logger.info.mockClear();
logger.error.mockClear();
@@ -386,7 +386,7 @@ describe('OTA update', () => {
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'`);
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}'`);
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'], {'sendWhen': 'immediate'});
@@ -396,7 +396,7 @@ describe('OTA update', () => {
const device = zigbeeHerdsman.devices.bulb;
const endpoint = device.endpoints[0];
endpoint.read.mockImplementation(() => {return {swBuildId: 1, dateCode: '2019010'}});
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
logger.info.mockClear();
logger.error.mockClear();
@@ -413,7 +413,7 @@ describe('OTA update', () => {
it('Legacy api: Should be able to check if OTA update is available', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
logger.info.mockClear();
@@ -435,16 +435,16 @@ describe('OTA update', () => {
it('Legacy api: Should handle if OTA update check fails', async () => {
const device = zigbeeHerdsman.devices.bulb;
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
logger.error.mockClear();
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {throw new Error('RF singals disturbed because of dogs barking')});
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {throw new Error('RF signals disturbed because of dogs barking')});
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'bulb');
await flushPromises();
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
expect(logger.error).toHaveBeenCalledWith(`Failed to check if update available for 'bulb' (RF singals disturbed because of dogs barking)`);
expect(logger.error).toHaveBeenCalledWith(`Failed to check if update available for 'bulb' (RF signals disturbed because of dogs barking)`);
});
it('Legacy api: Should not check for OTA when device does not support it', async () => {
@@ -463,7 +463,7 @@ describe('OTA update', () => {
return {swBuildId: 1, dateCode: '2019010'}
});
const mapped = zigbeeHerdsmanConverters.findByDevice(device)
const mapped = await zigbeeHerdsmanConverters.findByDevice(device)
mockClear(mapped);
logger.info.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/ota_update/update', 'bulb');
+4 -4
View File
@@ -50,7 +50,7 @@ describe('Publish', () => {
g.command.mockClear();
});
zigbeeHerdsmanConverters.toZigbeeConverters.__clearStore__();
zigbeeHerdsmanConverters.toZigbee.__clearStore__();
});
afterAll(async () => {
@@ -145,7 +145,7 @@ describe('Publish', () => {
it('Should publish messages to zigbee devices with color_temp in %', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
const endpoint = device.getEndpoint(1);
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color_temp_percent: '100'}));
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color_temp_percent: 100}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColorTemp", {colortemp: 500, transtime: 0}, {});
@@ -1276,7 +1276,7 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledWith("ssIasWd", "startWarning", {"startwarninginfo": 53, "warningduration": 10, "strobedutycycle": 0, "strobelevel": 1}, {disableDefaultResponse: true});
});
it('Shouldnt do anythign when device is not supported', async () => {
it('Shouldnt do anything when device is not supported', async () => {
const payload = {state: 'ON'};
await MQTT.events.message('zigbee2mqtt/unsupported2/set', stringify(payload));
await flushPromises();
@@ -1343,7 +1343,7 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', brightness: 20, transition: 0.0}));
await flushPromises();
zigbeeHerdsmanConverters.toZigbeeConverters.__clearStore__();
zigbeeHerdsmanConverters.toZigbee.__clearStore__();
await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "transition": 1.0}));
await flushPromises();
+6 -18
View File
@@ -182,7 +182,7 @@ describe('Receive', () => {
// Summary:
// First send multiple measurements to device that is debouncing. Make sure only one message is sent out to MQTT. This also ensures first message is cached to "State".
// Then send another measurement to that same device and trigger asyncronous event to push data from Cache. Newest value should be sent out.
// Then send another measurement to that same device and trigger asynchronous event to push data from Cache. Newest value should be sent out.
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
settings.set(['devices', device.ieeeAddr, 'debounce'], 0.1);
@@ -201,11 +201,11 @@ describe('Receive', () => {
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
// Send another Zigbee messages...
// Send another Zigbee message...
await zigbeeHerdsman.events.message({data: {measuredValue: 9}, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10});
const realDevice = controller.zigbee.resolveEntity(device);
// Trigger asyncronous event while device is "debouncing" to trigger Message to be sent out from State cache.
// Trigger asynchronous event while device is "debouncing" to trigger Message to be sent out from State cache.
await controller.publishEntityState( realDevice, {} );
jest.runOnlyPendingTimers();
await flushPromises();
@@ -213,7 +213,7 @@ describe('Receive', () => {
// Total of 3 messages should have triggered.
expect(MQTT.publish).toHaveBeenCalledTimes(3);
// Test that message pushed by asyncronous message contains NEW measurement and not old.
// Test that message pushed by asynchronous message contains NEW measurement and not old.
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2});
// Test that messages after debouncing contains NEW measurement and not old.
expect(JSON.parse(MQTT.publish.mock.calls[2][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2});
@@ -388,18 +388,6 @@ describe('Receive', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(0);
});
it('Should not handle messages from unsupported devices and link to docs', async () => {
const device = zigbeeHerdsman.devices.unsupported;
const data = {onOff: 1};
logger.warn.mockClear();
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(logger.warn).toHaveBeenCalledWith(`Received message from unsupported device with Zigbee model 'notSupportedModelID' and manufacturer name 'notSupportedMfg'`);
expect(logger.warn).toHaveBeenCalledWith(`Please see: https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html`);
expect(MQTT.publish).toHaveBeenCalledTimes(0);
});
it('Should not handle messages from still interviewing devices with unknown definition', async () => {
const device = zigbeeHerdsman.devices.interviewing;
const data = {onOff: 1};
@@ -408,7 +396,7 @@ describe('Receive', () => {
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(0);
expect(logger.debug).toHaveBeenCalledWith(`Skipping message, definition is undefined and still interviewing`);
expect(logger.debug).toHaveBeenCalledWith(`Skipping message, still interviewing`);
});
it('Should handle a command', async () => {
@@ -474,6 +462,6 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/SP600_OLD');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({energy: 6.648, power: 496});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({energy: 6.65, power: 496});
});
});
+19 -13
View File
@@ -335,22 +335,15 @@ describe('Settings', () => {
expect(read(devicesFile)).toStrictEqual(expected);
});
it('Should add devices for first file when using 2 separates file', () => {
function extractFromMultipleDeviceConfigs(contentDevices2) {
const contentConfiguration = {
devices: ['devices.yaml', 'devices2.yaml']
devices: ['devices.yaml', 'devices2.yaml'],
};
const contentDevices = {
'0x12345678': {
friendly_name: '0x12345678',
retain: false,
},
};
const contentDevices2 = {
'0x87654321': {
friendly_name: '0x87654321',
retain: false,
retain: false,
},
};
@@ -365,15 +358,28 @@ describe('Settings', () => {
const expected = {
'0x12345678': {
friendly_name: '0x12345678',
retain: false,
retain: false,
},
'0x1234': {
'0x1234': {
friendly_name: '0x1234',
},
};
expect(read(devicesFile)).toStrictEqual(expected);
expect(read(devicesFile2)).toStrictEqual(contentDevices2);
}
it('Should add devices for first file when using 2 separates file', () => {
extractFromMultipleDeviceConfigs({
'0x87654321': {
friendly_name: '0x87654321',
retain: false,
},
});
});
it('Should add devices for first file when using 2 separates file and the second file is empty', () => {
extractFromMultipleDeviceConfigs(null)
});
it('Should add devices to a separate file if devices.yaml doesnt exist', () => {
@@ -935,7 +941,7 @@ describe('Settings', () => {
});
settings.reRead();
expect(settings.get().frontend).toStrictEqual({port: 8080, auth_token: false, host: '0.0.0.0'})
expect(settings.get().frontend).toStrictEqual({port: 8080, auth_token: false})
});
it('Baudrate config', () => {
+1 -1
View File
@@ -134,7 +134,7 @@ const bulb_color = new Device('Router', '0x000b57fffec6a5b3', 40399, 4107, [new
const bulb_color_2 = new Device('Router', '0x000b57fffec6a5b4', 401292, 4107, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b4', [], {lightingColorCtrl: {colorCapabilities: 254}}, [], null, null, {'scenes': {'1_0': {name: 'Chill scene', state: {state: 'ON'}}, '4_9': {state: {state: 'OFF'}}}})], true, "Mains (single phase)", "LLC020", false, 'Philips', '2019.09', '5.127.1.26581');
const bulb_2 = new Device('Router', '0x000b57fffec6a5b7', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b7', [], {lightingColorCtrl: {colorCapabilities: 17}})], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm");
const TS0601_thermostat = new Device('EndDevice', '0x0017882104a44559', 6544,4151, [new Endpoint(1, [], [], '0x0017882104a44559')], true, "Mains (single phase)", 'kud7u2l');
const ZNCZ02LM = new Device('Router', '0x0017880104e45524', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45524')], true, "Mains (single phase)", "lumi.plug");
const ZNCZ02LM = new Device('Router', '0x0017880104e45524', 6540,4151, [new Endpoint(1, [0, 6], [], '0x0017880104e45524')], true, "Mains (single phase)", "lumi.plug");
const GLEDOPTO_2ID = new Device('Router', '0x0017880104e45724', 6540,4151, [new Endpoint(11, [0,3,4,5,6,8,768], [], '0x0017880104e45724', [], {}, [], 49246, 528), new Endpoint(12, [0, 3, 4, 5, 6, 8, 768], [], '0x0017880104e45724', [], {}, [], 260, 258), new Endpoint(13, [4096], [4096], '0x0017880104e45724', [], {}, [], 49246, 57694), new Endpoint(15, [0, 3, 4, 5, 6, 8, 768], [], '0x0017880104e45724', [], {}, [], 49246, 256)], true, "Mains (single phase)", 'GL-C-007', false, 'GLEDOPTO');
const QBKG03LM = new Device('Router', '0x0017880104e45542', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45542'), new Endpoint(2, [0, 6], [], '0x0017880104e45542'), new Endpoint(3, [0, 6], [], '0x0017880104e45542')], true, "Mains (single phase)", 'lumi.ctrl_neutral2');
const zigfred_plus = new Device('Router', '0xf4ce368a38be56a1', 6589, 0x129C, [new Endpoint(5, [0, 3, 4, 5, 6, 8, 0x0300, 0xFC42], [0xFC42], '0xf4ce368a38be56a1'), new Endpoint(7, [0, 3, 4, 5, 6, 8], [], '0xf4ce368a38be56a1'), new Endpoint(8, [0, 3, 4, 5, 6, 8], [], '0xf4ce368a38be56a1'), new Endpoint(9, [0, 3, 4, 5, 6, 8], [], '0xf4ce368a38be56a1'), new Endpoint(10, [0, 3, 4, 5, 6, 8], [], '0xf4ce368a38be56a1'), new Endpoint(11, [0, 3, 4, 5, 0x0102], [], '0xf4ce368a38be56a1'), new Endpoint(12, [0, 3, 4, 5, 0x0102], [], '0xf4ce368a38be56a1')], true, "Mains (single phase)", 'zigfred plus', false, 'Siglis');
+3 -2
View File
@@ -1,9 +1,10 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"module": "commonjs",
"esModuleInterop": true,
"target": "es2018",
"lib": ["es2018"],
"target": "ES2022",
"lib": ["ES2022"],
"noImplicitAny": true,
"noImplicitThis": true,
"moduleResolution": "node",
+4
View File
@@ -1,4 +1,5 @@
#!/bin/bash -e
cd "$(dirname "$0")"
if [ -d data-backup ]; then
echo "ERROR: Backup directory exists. May be previous restoring was failed?"
@@ -26,6 +27,9 @@ git pull
echo "Installing dependencies..."
npm ci
echo "Building..."
npm run build
echo "Restore configuration..."
cp -R data-backup/* data
rm -rf data-backup