Compare commits

...
468 Commits
Author SHA1 Message Date
Koen Kanters 3c5854fa9f 1.25.2 2022-06-01 17:38:09 +02:00
Koen Kanters 900175af47 Undev 2022-06-01 17:37:39 +02:00
Koen Kanters 9b9e2289d3 Merge remote-tracking branch 'origin/master' into dev 2022-06-01 17:36:50 +02:00
github-actions[bot]andKoenkk ea10936641 Update zigbee-herdsman-converters to 14.0.533 (#12672)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-06-01 15:14:02 +00:00
github-actions[bot]andKoenkk 6b56b0bcf8 Update zigbee-herdsman-converters to 14.0.532 (#12671)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-06-01 15:03:10 +00:00
Koen Kanters 975eb7d6ae Revert "Revert "Temp: disable ghcr.io since it makes build fail""
This reverts commit 4e28df88ae.
2022-06-01 16:57:47 +02:00
Koen Kanters 4de04c19c0 Update ghcr_cleanup.yml 2022-06-01 15:12:32 +02:00
Koen Kanters 1db12612e6 Add GHCR cleanup (#12669)
* Create ghcr_cleanup.yml

* Update ghcr_cleanup.yml

* Update ghcr_cleanup.yml

* Update ghcr_cleanup.yml

* Update ghcr_cleanup.yml
2022-06-01 14:18:41 +02:00
github-actions[bot]andKoenkk ec010df79a Update zigbee-herdsman-converters to 14.0.531 (#12659)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-31 15:17:42 +00:00
github-actions[bot]andKoenkk 88b65625de Update zigbee-herdsman to 0.14.34 (#12658)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-31 14:58:08 +00:00
Koen Kanters 4e28df88ae Revert "Temp: disable ghcr.io since it makes build fail"
This reverts commit a1db076c02.
2022-05-31 08:28:36 +02:00
github-actions[bot]andKoenkk 44815435e7 Update zigbee-herdsman-converters to 14.0.530 (#12645)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-30 16:11:37 +00:00
Koen Kanters fe011ba7b3 Disable node 18 testing for now (fails quite often) 2022-05-29 10:21:23 +02:00
Koen Kanters 68035a1256 Fix restart not working (#12629) 2022-05-29 08:15:47 +00:00
github-actions[bot]andKoenkk cc535f11c6 Update zigbee-herdsman-converters to 14.0.529 (#12628)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-29 07:23:01 +00:00
github-actions[bot]andKoenkk 7001b2fc79 Update dependencies (#12627)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-29 06:46:27 +00:00
Koen Kanters 05975109a4 Lock connect-gzip-static https://github.com/Koenkk/zigbee2mqtt/pull/12624/files#r884205347 2022-05-29 08:43:02 +02:00
github-actions[bot]andKoenkk 502443aae8 Update zigbee-herdsman-converters to 14.0.528 (#12615)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-27 19:15:31 +00:00
Koen Kanters 43a82ad835 Delete ghcr_delete_untagged.yml 2022-05-27 16:29:50 +02:00
Koen Kanters a1db076c02 Temp: disable ghcr.io since it makes build fail 2022-05-27 16:23:34 +02:00
Koen Kanters bfc70c3bb8 Update ghcr_delete_untagged.yml 2022-05-27 15:09:48 +02:00
Koen Kanters b268b1ed75 Update ghcr_delete_untagged.yml 2022-05-27 12:08:55 +02:00
Koen Kanters 64769b3e9f Update ghcr_delete_untagged.yml 2022-05-27 12:05:51 +02:00
Koen Kanters 66e804d71e Create ghcr_delete_untagged.yml 2022-05-27 12:01:21 +02:00
github-actions[bot]andKoenkk 1c30a4f4bc Update zigbee-herdsman to 0.14.33 (#12612)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-27 09:51:49 +00:00
Koen Kanters be55e3ee80 Revert "Temp disable ghcr.io push (seems to make build fail)"
This reverts commit c1372852e9.
2022-05-27 11:47:43 +02:00
github-actions[bot]andnurikk 1aa7971ca1 Update zigbee2mqtt-frontend to 0.6.97 (#12605)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-05-26 11:25:29 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 6e2ab644a0 Bump peter-evans/create-pull-request from 3 to 4 (#12602)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3 to 4.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  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>
2022-05-26 09:54:13 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4fa6c33622 Bump peter-evans/enable-pull-request-automerge from 1 to 2 (#12600)
Bumps [peter-evans/enable-pull-request-automerge](https://github.com/peter-evans/enable-pull-request-automerge) from 1 to 2.
- [Release notes](https://github.com/peter-evans/enable-pull-request-automerge/releases)
- [Commits](https://github.com/peter-evans/enable-pull-request-automerge/compare/v1...v2)

---
updated-dependencies:
- dependency-name: peter-evans/enable-pull-request-automerge
  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>
2022-05-26 09:53:39 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3fd405e570 Bump actions/stale from 3 to 5 (#12601)
Bumps [actions/stale](https://github.com/actions/stale) from 3 to 5.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v3...v5)

---
updated-dependencies:
- dependency-name: actions/stale
  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>
2022-05-26 09:53:15 +02:00
Koen Kanters c1372852e9 Temp disable ghcr.io push (seems to make build fail) 2022-05-26 09:50:39 +02:00
Koen Kanters 95eedc9ec1 Add dependabot.yml 2022-05-26 09:48:43 +02:00
Koen Kanters d7b926d47f Update ci.yml dependencies 2022-05-26 09:43:11 +02:00
github-actions[bot]andKoenkk 67039a1a94 Update zigbee-herdsman-converters to 14.0.527 (#12599)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-26 07:27:42 +00:00
github-actions[bot]andKoenkk f79fb16e41 Update zigbee-herdsman-converters to 14.0.526 (#12595)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-25 16:58:18 +00:00
Koen Kanters d910c05e6e Improve tests 2022-05-25 11:38:37 +02:00
github-actions[bot]andnurikk 4858e06060 Update zigbee2mqtt-frontend to 0.6.96 (#12592)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-05-25 09:31:15 +00:00
f71db6816a Update zigbee-herdsman-converters to 14.0.525 (#12585)
* Update zigbee-herdsman-converters to 14.0.525

* Fix tests

* Ignore

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-05-24 18:27:17 +00:00
github-actions[bot]andnurikk c6b129c68d Update zigbee2mqtt-frontend to 0.6.95 (#12572)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-05-23 04:59:27 +00:00
e5dbe01329 Update zigbee-herdsman-converters to 14.0.522 (#12568)
* Update zigbee-herdsman-converters to 14.0.522

* Fix

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-05-22 20:32:15 +00:00
Koen Kanters c00768b7c7 Move ci before tests 2022-05-21 15:08:07 +02:00
Koen Kanters ad070c99ef Add official support for node 18 and drop for node 10, 12, 15 (#12553) 2022-05-21 08:29:22 +00:00
aec082c0cf Update zigbee-herdsman-converters to 14.0.521 (#12552)
* Update zigbee-herdsman-converters to 14.0.521

* Fixes

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-05-21 08:20:37 +00:00
github-actions[bot]andKoenkk cc84452f23 Update zigbee-herdsman to 0.14.32 (#12551)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-21 07:45:02 +00:00
John Doeandnurikk 84bac2b8d8 Add compatibility tests (#12527)
* Add tests gh action

* Fix zigbee_ota_override_index_location

* Fix Shouldnt crash when it cannot save state test

* Add node17

* Update tests.yml

* Drop old versions

* Move to ci.yml

* Update controller.test.js

* Fix linebraks on windows

* Ignore windows and node 17

* Fail on error

* Add comment

* Mark node 18 as supported

Co-authored-by: nurikk <ainur@yojee.com>
2022-05-21 08:37:39 +02:00
github-actions[bot]andKoenkk 29a9d227de Update zigbee-herdsman-converters to 14.0.520 (#12546)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-20 14:58:14 +00:00
github-actions[bot]andnurikk 39ae0c775a Update zigbee2mqtt-frontend to 0.6.94 (#12540)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-05-20 02:44:04 +00:00
github-actions[bot]andKoenkk f1eaf2d7aa Update zigbee-herdsman-converters to 14.0.519 (#12530)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-18 15:35:44 +00:00
Koen Kanters c6840a9a43 Mark node 17 as supported 2022-05-18 14:56:17 +02:00
John Doeandnurikk 3a1c64a88e Downgrade connect-gzip-static to 2.1.1 (#12525)
Co-authored-by: nurikk <ainur@yojee.com>
2022-05-18 17:30:04 +08:00
github-actions[bot]andnurikk d4a0c93c91 Update zigbee2mqtt-frontend to 0.6.86 (#12524)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-05-18 08:14:56 +00:00
github-actions[bot]andKoenkk 5ae8347317 Update zigbee-herdsman-converters to 14.0.518 (#12518)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-17 15:06:53 +00:00
github-actions[bot]andKoenkk afbfa4631f Update zigbee-herdsman to 0.14.31 (#12504)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-16 16:05:24 +00:00
github-actions[bot]andKoenkk 096f906ac7 Update zigbee-herdsman-converters to 14.0.517 (#12503)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-16 14:44:14 +00:00
github-actions[bot]andnurikk b844f607ee Update zigbee2mqtt-frontend to 0.6.85 (#12502)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-05-16 13:59:21 +00:00
github-actions[bot]andKoenkk 6002bc475b Update dependencies (#12479)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-16 15:19:07 +02:00
github-actions[bot]andKoenkk 24cfdf4594 Update zigbee-herdsman to 0.14.30 (#12466)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-13 13:15:51 +00:00
github-actions[bot]andKoenkk bc5fd1a52f Update zigbee-herdsman to 0.14.29 (#12464)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-13 08:30:16 +00:00
Koen Kanters fd344a3017 Revert "Additional logging for https://github.com/Koenkk/zigbee2mqtt/issues/12432"
This reverts commit 920b4924b0.
2022-05-13 10:05:49 +02:00
github-actions[bot]andKoenkk f9bd184543 Update zigbee-herdsman-converters to 14.0.516 (#12460)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-12 17:53:53 +00:00
github-actions[bot]andKoenkk f2c1e85b4b Update zigbee-herdsman-converters to 14.0.515 (#12447)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-11 18:21:27 +00:00
Koen Kanters 920b4924b0 Additional logging for https://github.com/Koenkk/zigbee2mqtt/issues/12432 2022-05-11 17:22:48 +02:00
github-actions[bot]andKoenkk 99e67b1495 Update zigbee-herdsman-converters to 14.0.514 (#12435)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-10 18:03:45 +00:00
github-actions[bot]andKoenkk e65344c9fa Update zigbee-herdsman-converters to 14.0.513 (#12429)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-09 18:15:01 +00:00
147fe22f08 Update dependencies (#12416)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-05-08 10:01:25 +02:00
Koen Kanters 7f1f1497c6 Always return a device state 2022-05-08 09:58:30 +02:00
Koen Kanters 6278c86d02 Pass device options in global on event. https://github.com/Koenkk/zigbee2mqtt/commit/723dd8358f3cee0b90d624f27989a8bb587e25b0#comments 2022-05-07 14:40:25 +02:00
6f09d45a7b Update zigbee-herdsman-converters to 14.0.512 (#12413)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-05-07 12:39:32 +00:00
Koen Kanters 49a730f77d Update tests for https://github.com/Koenkk/zigbee2mqtt/pull/12413 2022-05-07 14:37:29 +02:00
github-actions[bot]andKoenkk e5a74894d2 Update zigbee-herdsman-converters to 14.0.511 (#12409)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-07 08:43:02 +00:00
Jorge SchrauwenandKoen Kanters 723dd8358f Pass device state to zhc onEvent (#12407)
* onEvent: want device state as argument

* onEvent: test should check deviceOptions and deviceState returns

* Update onEvent.ts

* Update onEvent.ts

* Fix

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-05-07 08:36:03 +00:00
github-actions[bot]andKoenkk 47a22cce2a Update zigbee-herdsman-converters to 14.0.510 (#12403)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-06 15:24:31 +00:00
github-actions[bot]andKoenkk b3791684fd Update zigbee-herdsman-converters to 14.0.509 (#12392)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-05 16:43:18 +00:00
github-actions[bot]andKoenkk ebd3255fc6 Update zigbee-herdsman to 0.14.28 (#12390)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-05 16:17:04 +00:00
github-actions[bot]andKoenkk 9b594a4cba Update zigbee-herdsman-converters to 14.0.508 (#12380)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-04 19:08:08 +00:00
Koen Kanters d0530b8214 Don't allow friendly names to include control character. #12379 2022-05-04 21:00:29 +02:00
5c7e968028 Update zigbee-herdsman-converters to 14.0.507 (#12368)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-05-03 17:42:17 +00:00
Koen Kanters 40ef638e0e Fix null not allowed for Home Assistant device options. #12340 2022-05-03 19:39:30 +02:00
github-actions[bot]andKoenkk b9499618d3 Update zigbee-herdsman-converters to 14.0.506 (#12354)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-02 17:35:37 +00:00
Alex Kay 6618872839 Add new config and diagnostic entities to HA (#12353) 2022-05-02 19:23:40 +02:00
Alex Kay b977dd1140 Add battery2 for HA (#12332) 2022-05-02 14:20:45 +00:00
github-actions[bot]andnurikk 71d5c7b4a2 Update zigbee2mqtt-frontend to 0.6.84 (#12351)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-05-02 12:31:28 +00:00
github-actions[bot]andKoenkk dd8a83e592 Update zigbee-herdsman-converters to 14.0.505 (#12338)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-01 18:45:05 +00:00
Koen Kanters 664182def2 Promote to dev 2022-05-01 20:16:18 +02:00
Koen Kanters 3f6a13786e 1.25.1 2022-05-01 20:14:19 +02:00
Koen Kanters a800197262 Undev 2022-05-01 20:14:12 +02:00
github-actions[bot]andKoenkk 0c145d81cf Update zigbee-herdsman-converters to 14.0.504 (#12336)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-05-01 17:53:38 +00:00
github-actions[bot]andKoenkk 7d7f8aabfe Update zigbee-herdsman-converters to 14.0.503 (#12321)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-30 07:50:44 +00:00
github-actions[bot]andKoenkk 95c2fcfb00 Update zigbee-herdsman-converters to 14.0.502 (#12317)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-29 20:45:59 +00:00
github-actions[bot]andKoenkk 2a6a6c43ce Update zigbee-herdsman-converters to 14.0.501 (#12309)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-28 16:26:57 +00:00
github-actions[bot]andKoenkk 47ed86d735 Update zigbee-herdsman-converters to 14.0.500 (#12294)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-27 07:34:32 +00:00
github-actions[bot]andKoenkk 50990d1f7a Update zigbee-herdsman-converters to 14.0.499 (#12288)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-26 18:05:33 +00:00
Koen Kanters f7b484f24f Log stack trace of "Failed to execute LQI" error. https://github.com/Koenkk/zigbee2mqtt/issues/11968 2022-04-26 20:02:18 +02:00
github-actions[bot]andKoenkk f6ad3c9926 Update zigbee-herdsman-converters to 14.0.498 (#12280)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-25 18:31:28 +00:00
11b415634c Update zigbee-herdsman to 0.14.27 (#12267)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-04-24 18:40:24 +00:00
github-actions[bot]andKoenkk d980d38747 Update zigbee-herdsman-converters to 14.0.497 (#12266)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-24 18:32:09 +00:00
github-actions[bot]andKoenkk 808671dd59 Update dependencies (#12256)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-24 13:55:36 +02:00
Koen Kanters cf22dd60a0 Always respond to OTA request (#12247)
* Always respond to OTA request

* Improve

* Rename
2022-04-23 10:12:29 +02:00
github-actions[bot]andKoenkk 3b820bae9f Update zigbee-herdsman-converters to 14.0.496 (#12249)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-22 18:35:06 +00:00
github-actions[bot]andKoenkk 60a987c004 Update zigbee-herdsman-converters to 14.0.495 (#12240)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-21 15:46:27 +00:00
github-actions[bot]andKoenkk d90be2dc66 Update zigbee-herdsman-converters to 14.0.494 (#12233)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-20 17:09:34 +00:00
Koen Kanters e305ecf571 Improve #ec2bf1a95d6c45c093f68df832bc6bcbdd372a5a 2022-04-20 17:59:16 +02:00
Koen Kanters ec2bf1a95d Fix out of memory error when starting on system with low RAM. #12034 2022-04-20 16:59:30 +02:00
github-actions[bot]andKoenkk 7d5c44a5bd Update zigbee-herdsman-converters to 14.0.493 (#12213)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-19 07:09:28 +00:00
github-actions[bot]andKoenkk 8c4312db72 Update zigbee-herdsman-converters to 14.0.492 (#12210)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-18 17:32:40 +00:00
github-actions[bot]andKoenkk 037a72e0f3 Update zigbee-herdsman-converters to 14.0.491 (#12200)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-17 18:18:25 +00:00
github-actions[bot]andKoenkk d657ea48cf Update dependencies (#12195)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-17 11:47:00 +02:00
github-actions[bot]andKoenkk b4e0c3afcf Update zigbee-herdsman-converters to 14.0.490 (#12193)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-16 07:32:45 +00:00
John Doeandnurikk 27bc646cf6 Add home assistant name to settings schema (#12191)
Co-authored-by: nurikk <ainur@yojee.com>
2022-04-16 10:27:41 +08:00
github-actions[bot]andKoenkk 23ef22e9ba Update zigbee-herdsman-converters to 14.0.489 (#12190)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-15 22:09:50 +00:00
github-actions[bot]andKoenkk 84c8da2532 Update zigbee-herdsman-converters to 14.0.488 (#12186)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-15 15:05:35 +00:00
github-actions[bot]andnurikk e6ec66c844 Update zigbee2mqtt-frontend to 0.6.83 (#12185)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-04-15 14:16:14 +00:00
github-actions[bot]andnurikk d1c26f9aa0 Update zigbee2mqtt-frontend to 0.6.82 (#12181)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-04-15 12:44:03 +00:00
github-actions[bot]andnurikk a7704b9038 Update zigbee2mqtt-frontend to 0.6.81 (#12179)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-04-15 09:36:36 +00:00
github-actions[bot]andKoenkk eabd662757 Update zigbee-herdsman-converters to 14.0.487 (#12172)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-14 15:38:56 +00:00
github-actions[bot]andKoenkk 764895b9d2 Update zigbee-herdsman-converters to 14.0.486 (#12164)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-13 17:48:12 +00:00
Míguel Ángel Mulero MartínezandKoen Kanters 8f5b26a2fb Mark calibration, calibration_time and motor_reversal as config sensors (#12146)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-04-13 19:37:41 +02:00
github-actions[bot]andKoenkk 533cd36ffd Update zigbee-herdsman to 0.14.26 (#12159)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-12 19:26:50 +02:00
github-actions[bot]andKoenkk 3a77ebf52d Update zigbee-herdsman-converters to 14.0.485 (#12156)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-12 15:11:50 +00:00
github-actions[bot]andKoenkk 0e3bef7e42 Update zigbee-herdsman to 0.14.25 (#12155)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-12 15:00:48 +00:00
github-actions[bot]andKoenkk dda63bd65a Update zigbee-herdsman-converters to 14.0.484 (#12145)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-11 15:59:24 +00:00
github-actions[bot]andKoenkk 2aa1b4ead3 Update zigbee-herdsman-converters to 14.0.483 (#12133)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-10 08:12:57 +00:00
github-actions[bot]andKoenkk 422b7e0168 Update dependencies (#12128)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-10 09:48:40 +02:00
github-actions[bot]andKoenkk 8053eceb84 Update zigbee-herdsman-converters to 14.0.482 (#12124)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-09 16:14:15 +00:00
github-actions[bot]andKoenkk d10d3c9b56 Update zigbee-herdsman-converters to 14.0.481 (#12118)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-08 14:54:54 +00:00
github-actions[bot]andKoenkk 6ff45b1006 Update zigbee-herdsman-converters to 14.0.480 (#12109)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-07 20:39:48 +00:00
github-actions[bot]andKoenkk c901e66e7a Update zigbee-herdsman-converters to 14.0.479 (#12103)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-07 16:01:10 +00:00
github-actions[bot]andKoenkk 6ee7f56df3 Update zigbee-herdsman-converters to 14.0.478 (#12094)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-06 15:38:55 +00:00
github-actions[bot]andKoenkk d7073409d4 Update zigbee-herdsman to 0.14.24 (#12092)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-06 15:27:58 +00:00
github-actions[bot]andKoenkk e46ddb8c79 Update zigbee-herdsman-converters to 14.0.477 (#12091)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-06 15:12:18 +00:00
github-actions[bot]andKoenkk f91d0f6bfe Update zigbee-herdsman-converters to 14.0.476 (#12084)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-05 18:21:15 +00:00
github-actions[bot]andKoenkk 7e61252d11 Update zigbee-herdsman-converters to 14.0.475 (#12069)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-04 18:26:35 +00:00
github-actions[bot]andKoenkk 67c5481965 Update zigbee-herdsman-converters to 14.0.474 (#12068)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-04 18:22:54 +00:00
github-actions[bot]andKoenkk e251fc0803 Update zigbee-herdsman-converters to 14.0.473 (#12060)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-03 19:27:11 +00:00
Koen Kanters 5d4268515c Fix garage door status incorrect in Home Assistant. https://github.com/Koenkk/zigbee2mqtt/issues/12038 2022-04-03 21:09:26 +02:00
github-actions[bot]andKoenkk 91d80f1bb0 Update zigbee-herdsman-converters to 14.0.472 (#12056)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-03 12:14:53 +00:00
github-actions[bot]andKoenkk c9fbc6d52e Update zigbee-herdsman to 0.14.23 (#12055)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-03 12:08:18 +00:00
github-actions[bot]andKoenkk b84ad454e5 Update zigbee-herdsman-converters to 14.0.471 (#12049)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-03 07:31:13 +00:00
github-actions[bot]andKoenkk 51ae801cb8 Update dependencies (#12047)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-03 08:49:18 +02:00
Koen Kanters cabef43421 Revert "Switch to package-lock.json"
This reverts commit 0163b7281a.
2022-04-03 08:49:03 +02:00
Koen Kanters 8fd8df75be Reduce Docker image size again. 2022-04-02 21:50:12 +02:00
Koen Kanters 51f8c1c487 Fix Docker build 2022-04-02 21:13:22 +02:00
Koen Kanters aeb9927c20 Reduce Docker image build time 2022-04-02 21:09:02 +02:00
Koen Kanters 0163b7281a Switch to package-lock.json 2022-04-02 20:57:44 +02:00
Koen Kanters 0471eb5501 Switch to package-lock.json 2022-04-02 20:51:32 +02:00
2679219d87 Update zigbee-herdsman to 0.14.22 (#12046)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2022-04-02 18:47:51 +00:00
Koen Kanters d83eb72c4b Add registry url 2022-04-02 20:44:16 +02:00
Koen Kanters 414ea10b14 Update actions 2022-04-02 20:41:18 +02:00
github-actions[bot]andKoenkk d6e29538b1 Update zigbee-herdsman-converters to 14.0.470 (#12045)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-02 18:38:36 +00:00
Koen Kanters eb9b764cc5 Anoter attempt to fix shrinkwrap 2022-04-02 20:25:02 +02:00
Koen Kanters db642832f8 Update shrinkwrap 2022-04-02 19:59:40 +02:00
github-actions[bot]andKoenkk a56e5e0f36 Update zigbee-herdsman to 0.14.21 (#12043)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-04-02 17:58:11 +00:00
Koen Kanters 1b0fbf556b Fix invalid Home Assistant device class for garage_door_contact. #12038 2022-04-02 19:31:32 +02:00
Artem Draft 3e7eddf3ce Improve handle motor_state in HA (#12028) 2022-04-02 19:17:36 +02:00
Koen Kanters ad24c4f677 Promote to dev 2022-04-01 08:28:18 +02:00
Koen Kanters 6f1460e47b 1.25.0 2022-04-01 08:26:35 +02:00
Koen Kanters 91239291ec Undev 2022-04-01 08:26:33 +02:00
Koen Kanters beccc18ba7 Merge branch 'master' into dev 2022-03-31 22:02:47 +02:00
github-actions[bot]andKoenkk 2901df37fb Update zigbee-herdsman-converters to 14.0.467 (#12008)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-31 19:28:24 +00:00
github-actions[bot]andnurikk 1c1ca6faea Update zigbee2mqtt-frontend to 0.6.80 (#12001)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2022-03-31 07:49:42 +00:00
github-actions[bot]andKoenkk 85308daf5f Update zigbee-herdsman-converters to 14.0.466 (#11991)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-30 15:31:23 +00:00
github-actions[bot]andKoenkk 4631f6fc22 Update zigbee-herdsman-converters to 14.0.465 (#11984)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-29 19:05:17 +00:00
github-actions[bot]andKoenkk 95c698758d Update zigbee-herdsman-converters to 14.0.464 (#11974)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-28 18:32:35 +00:00
github-actions[bot]andKoenkk 95c48309a0 Update dependencies (#11962)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-27 09:55:27 +02:00
github-actions[bot]andKoenkk 2e42f50d6a Update zigbee-herdsman-converters to 14.0.463 (#11961)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-26 22:05:47 +00:00
github-actions[bot]andKoenkk 62a7b5115c Update zigbee-herdsman-converters to 14.0.462 (#11959)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-26 14:01:19 +00:00
github-actions[bot]andKoenkk b805dadceb Update zigbee-herdsman-converters to 14.0.461 (#11947)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-03-24 18:18:15 +00:00
Artem Draft f95c0bb048 Handle cover motor_state in HA (#11926)
* Handle cover motor_state in HA

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

* Updates

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

* add running if the expose

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

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

* Fix tests

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

* Improvements

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

* fix tests

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

* Fix tests

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

device class.

* fix lint

* Update homeassistant.ts

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

* Fix unit tests

* Update homeassistant.ts

* Update homeassistant.ts

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

* Update homeassistant.ts

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

* remove leading "[Problem] " in the title

We already set a tag for this

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

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

* Revert "Fix endpoint match (#10898)"

This reverts commit 5b918d9e3474c313878ff4275b551f79f39c85e2.

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

* Fix tests

* Better solution

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

* Fix lint/test coverage

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

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

* updates

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

We already set a tag for this

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

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

* deep copy schema

* -

* -

* -

* -

* -

* -

* -

* -

* -

* -

* -

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

* Cleanup

* Code review corrections

* Move zigbee_ota_override_index_location to ota section of the configuration file

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

* Updates

* updates

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

* Update publish.test.js

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

* Fix typo: discovered
2022-01-02 07:11:39 +00:00
Koen Kanters 15f1df8fa4 Promote to dev 2022-01-01 10:47:30 +01:00
Koen Kanters 414c51f5b2 1.22.2 2022-01-01 10:46:42 +01:00
Koen Kanters 0c42067006 Undev 2022-01-01 10:45:46 +01:00
Koen Kanters 5741904e75 Merge branch 'master' into dev 2022-01-01 10:44:52 +01:00
github-actions[bot]andKoenkk c60fdfd39b Update zigbee-herdsman-converters to 14.0.366 (#10472)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2022-01-01 09:37:27 +00:00
Koen Kanters a7682498ef Fix set message with endpoint in payload where the endpoint name ends with a number not working. #10423 2021-12-31 14:05:15 +01:00
github-actions[bot]andnurikk c6a3b90c2f Update zigbee2mqtt-frontend to 0.6.67 (#10466)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-31 12:17:44 +00:00
github-actions[bot]andKoenkk ebd35052c0 Update zigbee-herdsman-converters to 14.0.365 (#10463)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-31 11:02:13 +00:00
github-actions[bot]andnurikk 2849f30417 Update zigbee2mqtt-frontend to 0.6.66 (#10458)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-31 06:04:35 +00:00
Koen Kanters e8526b7b58 Better test for #10355 2021-12-30 17:32:02 +01:00
github-actions[bot]andKoenkk 3166d37096 Update zigbee-herdsman-converters to 14.0.364 (#10446)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-30 14:43:59 +00:00
github-actions[bot]andKoenkk 2d2c01981e Update zigbee-herdsman-converters to 14.0.363 (#10441)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-30 10:37:06 +00:00
58b702e8ef Update zigbee-herdsman to 0.13.188 (#10440)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-30 10:11:08 +00:00
github-actions[bot]andKoenkk 6efa20fa1d Update zigbee-herdsman-converters to 14.0.362 (#10439)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-30 10:06:22 +00:00
github-actions[bot]andKoenkk bf72347468 Update zigbee-herdsman-converters to 14.0.361 (#10433)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-29 18:14:08 +00:00
Ryan Kim(True-World) 73a6ad4cf8 add HA expose, binary sensor card (#10406)
* add binary sensor card

* fix lint
2021-12-29 16:21:59 +01:00
Koen Kanters 1e75dd5628 Test case for #10355 2021-12-27 20:29:16 +01:00
bc92bd73e6 Update zigbee-herdsman to 0.13.187 (#10394)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-27 19:08:38 +00:00
github-actions[bot]andKoenkk 8756367666 Update zigbee-herdsman-converters to 14.0.360 (#10393)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-27 19:03:17 +00:00
Alex CherkayevandOleksandr Cherkayev 79e5f0844a Add 4417 to bind poll (#10379)
Co-authored-by: Oleksandr Cherkayev <ocherkayev@luxoft.com>
2021-12-27 18:11:21 +01:00
github-actions[bot]andKoenkk b0585172f4 Update zigbee-herdsman-converters to 14.0.359 (#10369)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-26 10:28:37 +00:00
Koen Kanters 89996835ed Update new device support template 2021-12-26 11:21:52 +01:00
Koen Kanters 4f2a1e55e5 Update new device support template 2021-12-26 11:18:56 +01:00
Koen Kanters d488563f2a Update new device support template 2021-12-26 11:18:10 +01:00
Koen Kanters 4310171abc Update new device support template 2021-12-26 11:16:14 +01:00
Koen Kanters 36d7b4b022 Update new device support template 2021-12-26 11:15:11 +01:00
Koen Kanters 50a6ed14d4 Allow read/write to unsupported devices. 2021-12-26 10:54:39 +01:00
github-actions[bot]andKoenkk ee623c2484 Update dependencies (#10362)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-26 10:10:22 +01:00
github-actions[bot]andKoenkk 2e6588719b Update zigbee-herdsman-converters to 14.0.358 (#10356)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-25 15:00:19 +00:00
github-actions[bot]andnurikk fb35b52ca6 Update zigbee2mqtt-frontend to 0.6.65 (#10349)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-25 04:43:38 +00:00
github-actions[bot]andKoenkk 3306b63299 Update zigbee-herdsman-converters to 14.0.357 (#10345)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-24 19:58:58 +00:00
Koen Kanters 7a4ecadb6a Change coordinator ieee addr place. 2021-12-24 17:56:18 +01:00
Koen Kanters 511f3b09af Expose coordinator_ieeeaddr on bridge/info 2021-12-24 17:52:30 +01:00
github-actions[bot]andnurikk 828982d36c Update zigbee2mqtt-frontend to 0.6.64 (#10344)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-24 15:37:18 +00:00
github-actions[bot]andKoenkk 61bd95f8b8 Update zigbee-herdsman to 0.13.186 (#10338)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-23 20:27:48 +00:00
ValentinandKoen Kanters 74cff6a983 Call onEvent when device options change (#10322)
* Notify when options change

* publish device info after options change

* Refactor

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-23 21:18:27 +01:00
Koen Kanters fdf7d10a41 Update problem report template 2021-12-23 21:12:06 +01:00
Koen Kanters 1145e4b81b Update 2021-12-23 21:08:31 +01:00
Koen Kanters 93f01d545e Fix issue templates 2021-12-23 21:04:38 +01:00
Koen Kanters 30e7385cac Update issue templates 2021-12-23 21:02:15 +01:00
Koen Kanters 513314963c Update issue template 2021-12-23 20:31:14 +01:00
Koen Kanters 3c0cd8ebfa Fix issue template syntax 2021-12-23 20:27:00 +01:00
Koen Kanters 5bedc044d6 Update issue templates 2021-12-23 20:24:06 +01:00
2a8a98f9a3 Update zigbee-herdsman-converters to 14.0.356 (#10329)
* Update zigbee-herdsman-converters to 14.0.356

* Fix tests

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-23 19:00:19 +00:00
github-actions[bot]andKoenkk 03f24a63d5 Update zigbee-herdsman to 0.13.185 (#10330)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-23 18:46:45 +00:00
53003b3d0d Update zigbee-herdsman-converters to 14.0.355 (#10317)
* Update zigbee-herdsman-converters to 14.0.355

* Fix tests

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-22 20:34:24 +00:00
ValentinandKoen Kanters cd5dd39971 Dynamically expose attributes (#10132)
* Dynamically expose attributes

* Rollback options in configure step

Use event instead, as proposed in https://github.com/Koenkk/zigbee2mqtt/pull/10132/files#r771801648

* Simplify code

* Switch to already existing event

* Fix all unit tests

* Remove redundant code

* Perfect rollback

* Fix typescript types

* Updates

* Updates

* Updates

* updates

* Improve

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-22 19:31:41 +00:00
github-actions[bot]andKoenkk 41b150af82 Update zigbee-herdsman-converters to 14.0.354 (#10314)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-22 19:12:34 +00:00
github-actions[bot]andnurikk 3ba1f8cad0 Update zigbee2mqtt-frontend to 0.6.63 (#10311)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-22 14:58:41 +00:00
Koen Kanters f9aa525048 Refactor Home Assistant extension mapping, prepares for #10132 2021-12-21 22:02:05 +01:00
github-actions[bot]andKoenkk 8b4be3d6ad Update zigbee-herdsman-converters to 14.0.353 (#10301)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-21 20:28:49 +00:00
github-actions[bot]andKoenkk 0efe869fcb Update zigbee-herdsman-converters to 14.0.352 (#10277)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-20 20:13:36 +00:00
github-actions[bot]andKoenkk 18cfbae9ca Update zigbee-herdsman-converters to 14.0.351 (#10272)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-20 17:49:39 +00:00
7625d261a5 Update zigbee-herdsman to 0.13.184 (#10247)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-19 17:14:25 +00:00
Koen Kanters 6d8eea2142 Fix tests 2021-12-19 18:12:16 +01:00
Koen Kanters 2a9d22e2b3 Software/datacode is now updated by zigbee-herdsman. #10131 2021-12-19 18:00:27 +01:00
github-actions[bot]andKoenkk 656446c828 Update zigbee-herdsman-converters to 14.0.350 (#10239)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-19 10:15:31 +00:00
github-actions[bot]andnurikk f1ffc1d265 Update zigbee2mqtt-frontend to 0.6.62 (#10237)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-19 09:36:53 +00:00
github-actions[bot]andKoenkk 2d1df8f0cb Update dependencies (#10234)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-19 10:28:09 +01:00
github-actions[bot]andKoenkk 253e985388 Update zigbee-herdsman-converters to 14.0.349 (#10220)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-18 09:08:56 +00:00
Koen Kanters 1dd1163288 Fix list not overridable in Home Assistant discovery payload. #10120 2021-12-17 21:37:19 +01:00
github-actions[bot]andKoenkk bb5627d33b Update zigbee-herdsman-converters to 14.0.348 (#10211)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-17 20:28:39 +00:00
github-actions[bot]andKoenkk 38f42bb3c8 Update zigbee-herdsman-converters to 14.0.347 (#10205)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-16 18:34:01 +00:00
github-actions[bot]andKoenkk 5f37145a02 Update zigbee-herdsman-converters to 14.0.346 (#10194)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-15 18:55:43 +00:00
Koen Kanters a56061b36d Attempt to fix crash due to availability state read. #10177 2021-12-15 19:48:32 +01:00
github-actions[bot]andKoenkk b890b1bcb7 Update zigbee-herdsman to 0.13.183 (#10193)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-15 18:33:09 +00:00
Koen Kanters aa84a140e1 Fix null race condition exception. #10108 2021-12-15 19:13:14 +01:00
github-actions[bot]andKoenkk d875b297ad Update zigbee-herdsman to 0.13.182 (#10182)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-14 21:18:08 +00:00
Koen Kanters 1c6ca6519c Delete branch-switcher.yml 2021-12-14 22:14:43 +01:00
github-actions[bot]andKoenkk 900c9f302e Update zigbee-herdsman-converters to 14.0.345 (#10183)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-14 21:13:20 +00:00
Koen Kanters ea04021438 Cleanup availability experimental log. #9907 2021-12-14 20:38:44 +01:00
Koen Kanters 5a624ae188 Fix error for Home Assistant last_seen sensor when epoch is used. #8287 2021-12-14 20:27:49 +01:00
Dmitriy Fishman 42569d6c39 Fix a typo in CONTRIBUTING.md (#10159) 2021-12-14 16:49:23 +01:00
github-actions[bot]andKoenkk 46e06dbbee Update dependencies (#10147)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-14 11:30:45 +01:00
github-actions[bot]andKoenkk a282221e40 Update zigbee-herdsman to 0.13.181 (#10164)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-13 14:30:32 +00:00
github-actions[bot]andnurikk bb22216d09 Update zigbee2mqtt-frontend to 0.6.61 (#10134)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-11 02:54:47 +00:00
github-actions[bot]andnurikk 28cb8b08fb Update zigbee2mqtt-frontend to 0.6.60 (#10122)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-10 04:36:26 +00:00
594a463f8b Update zigbee-herdsman-converters to 14.0.344 (#10116)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-09 18:56:19 +00:00
Pavel Sokolov 9c1a15465d Tuya zcl refactor (#10112)
* Rename Tuya command setData->dataRequest
https://github.com/Koenkk/zigbee-herdsman/issues/466

* Fix Tuya datapoint commands invalid format (status,transid -> seq). Tuya has `uint16` type for the sequence(transaction) number.
https://github.com/Koenkk/zigbee-herdsman/issues/466

* Tuya zcl refactoring: fix zigbe2mqtt tests
https://github.com/Koenkk/zigbee-herdsman/issues/466
2021-12-09 19:54:06 +01:00
Koen Kanters 22836f224d Fix filtered attributes being published as null. #10099 2021-12-09 19:52:21 +01:00
github-actions[bot]andKoenkk aafd937f8a Update zigbee-herdsman to 0.13.180 (#10115)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-09 18:39:54 +00:00
github-actions[bot]andKoenkk d0195d0ff9 Update zigbee-herdsman-converters to 14.0.343 (#10092)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-08 17:14:53 +00:00
Koen Kanters 7592c627c2 Fix typo in settings.schema.json. https://github.com/Koenkk/zigbee2mqtt/pull/9803 2021-12-08 17:46:17 +01:00
github-actions[bot]andnurikk 98a57b4540 Update zigbee2mqtt-frontend to 0.6.59 (#10082)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-08 01:50:06 +00:00
github-actions[bot]andKoenkk 70b9d29ddf Update zigbee-herdsman-converters to 14.0.342 (#10079)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-07 21:11:32 +00:00
Koen Kanters ac16d7ce69 Reconfigure device after OTA update. #7716 #7150 2021-12-07 21:58:11 +01:00
Koen Kanters d4f560d198 Validate QOS value. https://github.com/Koenkk/zigbee2mqtt/issues/9962 2021-12-07 18:52:54 +01:00
github-actions[bot]andKoenkk 4136b1e539 Update zigbee-herdsman to 0.13.179 (#10072)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-07 17:30:28 +00:00
github-actions[bot]andKoenkk b4af8329e7 Update zigbee-herdsman-converters to 14.0.341 (#10049)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-06 20:46:06 +00:00
github-actions[bot]andKoenkk ac0aca59a3 Update zigbee-herdsman-converters to 14.0.340 (#10046)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-06 19:40:28 +00:00
github-actions[bot]andnurikk bc8274b9fd Update zigbee2mqtt-frontend to 0.6.58 (#10035)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-06 03:48:12 +00:00
github-actions[bot]andKoenkk e5321d3f20 Update zigbee-herdsman-converters to 14.0.339 (#10021)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-05 14:49:20 +00:00
github-actions[bot]andnurikk 7a81a04b2f Update zigbee2mqtt-frontend to 0.6.57 (#10023)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-05 14:44:58 +00:00
github-actions[bot]andKoenkk 3b3d886121 Update zigbee-herdsman to 0.13.178 (#10022)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-05 14:30:05 +00:00
TilmanK 48b790cbf3 Adds link to web interface to the readme (#10015) 2021-12-05 15:05:26 +01:00
9c93103fb7 Update dependencies (#10011)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2021-12-05 15:01:53 +01:00
github-actions[bot]andnurikk 33b35c1e61 Update zigbee2mqtt-frontend to 0.6.56 (#10017)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-05 10:36:58 +00:00
github-actions[bot]andKoenkk 073c7aedf7 Update zigbee-herdsman-converters to 14.0.337 (#10000)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-04 15:20:48 +00:00
github-actions[bot]andKoenkk e789514777 Update zigbee-herdsman to 0.13.177 (#9999)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2021-12-04 14:31:30 +00:00
github-actions[bot]andnurikk fd762fcf10 Update zigbee2mqtt-frontend to 0.6.55 (#9998)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-04 13:26:16 +00:00
github-actions[bot]andnurikk 45b49bac0a Update zigbee2mqtt-frontend to 0.6.54 (#9997)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-04 12:30:48 +00:00
github-actions[bot]andnurikk 055fba259a Update zigbee2mqtt-frontend to 0.6.53 (#9996)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-04 09:49:59 +00:00
github-actions[bot]andnurikk b206dc910f Update zigbee2mqtt-frontend to 0.6.52 (#9995)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-04 09:40:34 +00:00
github-actions[bot]andnurikk 70db18f3ab Update zigbee2mqtt-frontend to 0.6.51 (#9992)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-04 03:33:58 +00:00
Koen Kanters cb2ef51df8 Fix last seen not always updated. #7423 2021-12-03 19:21:52 +01:00
Koen Kanters 68e5b39172 Mock update.state for Home Asisstant sensor. https://github.com/Koenkk/zigbee2mqtt/issues/9939 2021-12-03 19:08:00 +01:00
github-actions[bot]andnurikk b17ea2e4d9 Update zigbee2mqtt-frontend to 0.6.50 (#9982)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-03 16:17:11 +00:00
github-actions[bot]andnurikk 41f9361922 Update zigbee2mqtt-frontend to 0.6.49 (#9975)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-03 11:45:25 +00:00
github-actions[bot]andnurikk dae66fd3fc Update zigbee2mqtt-frontend to 0.6.48 (#9955)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-02 13:28:03 +00:00
github-actions[bot]andnurikk d6ea54b8df Update zigbee2mqtt-frontend to 0.6.47 (#9953)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2021-12-02 12:25:07 +00:00
Koen Kanters c47b9c7c9b Promote to dev 2021-12-01 17:21:30 +01:00
67 changed files with 14418 additions and 19618 deletions
+1
View File
@@ -15,6 +15,7 @@ module.exports = {
'indent': ['error', 4],
'max-len': ['error', {'code': 120}],
'no-prototype-builtins': 'off',
'linebreak-style': ['error', (process.platform === 'win32' ? 'windows' : 'unix')], // https://stackoverflow.com/q/39114446/2771889
},
'plugins': [
'jest',
-46
View File
@@ -1,46 +0,0 @@
<!--
Before submitting an issue make sure you've searched for a similar issue and read the documentation: https://www.zigbee2mqtt.io/.
Rules (don't ignore these, your issue will be closed without further notice):
- English only
- Make sure you are on the latest Zigbee2MQTT version
- Provide a clear description of the problem
- DON'T copy logs directly here, post a link to https://hastebin.com/ or https://pastebin.com/.
- Make sure you are running the latest firmware https://github.com/koenkk/z-stack-firmware.
Did you read the FAQ?
- https://www.zigbee2mqtt.io/guide/faq
Zigbee2MQTT fails to start?
- https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html
Having issues when using a CC2531?
- Make sure the CC2531 is connected through a USB extension cable
- Try the source routing firmware: https://github.com/Koenkk/Z-Stack-firmware/tree/master/coordinator/Z-Stack_Home_1.2/bin/source_routing
- With larger networks (30/40+) devices, your CC2531 might not be powerful enough. This will cause weird issues, in this case it's advised to use a more powerful adapter: https://www.zigbee2mqtt.io/guide/adapters/
Unsupported device?
- https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html
Device does not pair or interview fails?
- https://www.zigbee2mqtt.io/guide/faq/#why-does-my-device-not-or-fail-to-pair
Bug report?
- If applicable, provide steps how to reproduce the problem.
- Provide the herdsman debug logging: https://www.zigbee2mqtt.io/guide/usage/debug.html
-->
<!--Start your bug report below this line-->
# Bug Report
## What happened
## What did you expect to happen
## How to reproduce it (minimal and precise)
## Debug Info
Zigbee2MQTT version:
Adapter hardware: CC2531, CC2530, CC26X2R1 or CC1352P-2
Adapter firmware version:
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
## Is your feature request related to a problem? Please describe
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
## Describe the solution you'd like
A clear and concise description of what you want to happen.
## Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
## Additional context
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,38 @@
name: Feature request
description: Suggest an idea for this project
title: "[Feature request]: "
labels: [feature request]
body:
- type: markdown
attributes:
value: |
**IMPORTANT:** Before submitting:
- Is your feature request related to the fronted? Then click [here](https://github.com/nurikk/zigbee2mqtt-frontend/issues/new?assignees=&labels=&template=feature_request.md&title=)
- type: textarea
id: textarea1
attributes:
label: Is your feature request related to a problem? Please describe
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: textarea2
attributes:
label: Describe the solution you'd like
placeholder: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: textarea3
attributes:
label: Describe alternatives you've considered
placeholder: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
id: textarea4
attributes:
label: Additional context
placeholder: Add any other context or screenshots about the feature request here.
validations:
required: true
@@ -1,21 +0,0 @@
---
name: New device support request
about: Request support for a new device
title: ''
labels: new device support
assignees: ''
---
<!--
VERY IMPORTANT: Before submitting a request, first (try to) follow: https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html
Dumping just the info about this device without actually attemping adding support yourself will likely lead into this issue being ignored.
Clearly indicate what you tried and where you got stuck.
-->
## Information about the device + link
..
## data/database.db entry of the device
..
@@ -0,0 +1,54 @@
name: New device support request
description: Request support for a new device
title: "[New device support]: "
labels: [new device support]
body:
- type: markdown
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)
- 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)
- type: input
id: link
attributes:
label: Link
description: Link of this device (product page)
placeholder: https://www.linktomydevice.org
validations:
required: true
- type: input
id: database
attributes:
label: Database entry
description: Entry of this device in `data/database.db` after pairing it
placeholder: '{"id":53,"type":"Router","ieeeAddr":"0x10458d00024284f69","nwkAddr":10148,"manufId":4151,"manufName":"LUMI","powerSource":"DC Source","modelId":"lumi.relay.c2acn01","epList":[1,2],"endpoints":{"1":{"profId":260,"epId":1,"devId":257,"inClusterList":[0,3,4,5,1,2,10,6,16,2820,12],"outClusterList":[25,10],"clusters":{"genBasic":{"attributes":{"modelId":"lumi.relay.c2acn01","appVersion":1,"manufacturerName":"LUMI","powerSource":4,"zclVersion":0,"stackVersion":2,"hwVersion":18,"dateCode":"8-6-2020"}},"genAnalogInput":{"attributes":{"presentValue":129.04425048828125}},"genOnOff":{"attributes":{"61440":117440715,"onOff":1}}},"binds":[],"configuredReportings":[],"meta":{}},"2":{"profId":260,"epId":2,"devId":257,"inClusterList":[6,16,4,5],"outClusterList":[],"clusters":{"genOnOff":{"attributes":{"61440":237478966,"onOff":0}}},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":1,"stackVersion":2,"hwVersion":18,"dateCode":"8-6-2020","zclVersion":0,"interviewCompleted":true,"meta":{},"lastSeen":1640285631405}'
validations:
required: true
- type: textarea
id: notes
attributes:
label: Comments
placeholder: I tried to follow the supporting new device page but got stuck at...
validations:
required: true
- type: textarea
attributes:
label: External converter
description: In case you tried to create an external converter, put it below
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}'
-54
View File
@@ -1,54 +0,0 @@
---
name: Problem report
about: Create a report to help us improve
title: ''
labels: problem
assignees: ''
---
<!--
Before submitting an issue make sure you've searched for a similar issue and read the documentation: https://www.zigbee2mqtt.io/.
Rules (don't ignore these, your issue will be closed without further notice):
- English only
- Make sure you are on the latest Zigbee2MQTT version
- Provide a clear description of the problem
- DON'T copy logs directly here, post a link to https://hastebin.com/ or https://pastebin.com/.
- Make sure you are running the latest firmware https://github.com/koenkk/z-stack-firmware.
Did you read the FAQ?
- https://www.zigbee2mqtt.io/guide/faq/
Zigbee2MQTT fails to start?
- https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html
Having issues when using a CC2531?
- Make sure the CC2531 is connected through a USB extension cable
- Try the source routing firmware: https://github.com/Koenkk/Z-Stack-firmware/tree/master/coordinator/Z-Stack_Home_1.2/bin/source_routing
- With larger networks (30/40+) devices, your CC2531 might not be powerful enough. This will cause weird issues, in this case it's advised to use a more powerful adapter: https://www.zigbee2mqtt.io/guide/adapters/
Unsupported device?
- https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html
Device does not pair or interview fails?
- https://www.zigbee2mqtt.io/guide/faq/#why-does-my-device-not-or-fail-to-pair
Bug report?
- If applicable, provide steps how to reproduce the problem.
- Provide the herdsman debug logging: https://www.zigbee2mqtt.io/guide/usage/debug.html
-->
<!--Start your bug report below this line-->
## What happened
## What did you expect to happen
## How to reproduce it (minimal and precise)
## Debug info
Zigbee2MQTT version:
Adapter hardware: CC2531, CC2530, CC26X2R1, CC1352P-2, Conbee II
Adapter firmware version:
@@ -0,0 +1,67 @@
name: Problem report
description: Create a report to help us improve
labels: [problem]
body:
- type: markdown
attributes:
value: |
**IMPORTANT:** Before submitting:
- You read the [FAQ](https://www.zigbee2mqtt.io/guide/faq/)
- Zigbee2MQTT fails to start? Read [this](https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html)
- Make sure the bug also occurs in the [dev branch](https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html)
- Make sure you are using the [latest firmware](https://www.zigbee2mqtt.io/guide/adapters/#recommended) on your adapter
- The issue has not been [reported already](https://github.com/Koenkk/zigbee2mqtt/issues)
- Is your issue related to the fronted? Then click [here](https://github.com/nurikk/zigbee2mqtt-frontend/issues/new?assignees=&labels=bug%2Ctriage&template=bug_report.yaml&title=%5BBug%5D%3A+)
- 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)
- type: textarea
id: what_happend
attributes:
label: What happened?
validations:
required: true
- type: textarea
id: expect_to_happen
attributes:
label: What did you expect to happen?
placeholder: I expected that ...
validations:
required: false
- type: textarea
id: reproduce
attributes:
label: How to reproduce it (minimal and precise)
placeholder: First do this, than this..
validations:
required: false
- type: input
id: z2m_version
attributes:
label: Zigbee2MQTT version
description: Can be found in the frontend -> settings -> about -> Zigbee2MQTT version. Are you running Zigbee2MQTT 1.18.1? Then read [this](https://github.com/Koenkk/zigbee2mqtt/releases/tag/1.19.0).
placeholder: '1.22.1'
validations:
required: true
- type: input
id: adapter_fwversion
attributes:
label: Adapter firmware version
description: Can be found in the frontend -> settings -> about -> coordinator revision
placeholder: '20211210'
validations:
required: true
- type: input
id: adapter
attributes:
label: Adapter
description: The adapter you are using
placeholder: Electrolama zig-a-zig-ah! (zzh!), Slaesh's CC2652RB stick, ...
validations:
required: true
- type: textarea
id: log
attributes:
label: Debug log
description: After enabling [debug logging](https://www.zigbee2mqtt.io/guide/usage/debug.html) the log can be found under `data/log`. Attach the file below
placeholder: Click here and drag the file into it or click on "Attach files by.." below
validations:
required: false
-1
View File
@@ -1 +0,0 @@
preferredBranch: dev
+13
View File
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
target-branch: dev
- package-ecosystem: "docker"
directory: "/docker"
schedule:
interval: "daily"
target-branch: dev
+40 -8
View File
@@ -6,10 +6,10 @@ jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
registry-url: https://registry.npmjs.org/
- name: Get NodeJS Version
id: nodeversion
@@ -17,7 +17,7 @@ jobs:
echo "::set-output name=version::$(node --version)"
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ steps.nodeversion.outputs.version }}-${{ hashFiles('npm-shrinkwrap.json') }}
@@ -38,13 +38,13 @@ jobs:
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u koenkk --password-stdin
- name: Docker setup - QEMU
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
with:
platforms: all
- name: Docker setup - Buildx
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
with:
version: latest
- name: Docker build dev
@@ -55,7 +55,7 @@ jobs:
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest-dev -t ghcr.io/koenkk/zigbee2mqtt:latest-dev \
-t koenkk/zigbee2mqtt:latest-dev \
.
- name: Docker build release
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
@@ -66,7 +66,7 @@ jobs:
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--push \
-t koenkk/zigbee2mqtt:latest -t "koenkk/zigbee2mqtt:$TAG" -t ghcr.io/koenkk/zigbee2mqtt:latest -t "ghcr.io/koenkk/zigbee2mqtt:$TAG" \
-t koenkk/zigbee2mqtt:latest -t "koenkk/zigbee2mqtt:$TAG" \
.
- name: Publish to npm
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
@@ -82,3 +82,35 @@ jobs:
--data '{"definition": {"id": 9}}' \
-u ${{ secrets.HASSIO_TRIGGER_SECRET }} \
"https://dev.azure.com/zigbee2mqtt/Zigbee2mqtt%20Add-on/_apis/build/builds?api-version=5.1"
tests:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [14, 16, 17]
exclude:
- os: windows-latest # https://github.com/serialport/node-serialport/issues/2344
node: 17
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
registry-url: https://registry.npmjs.org/
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('npm-shrinkwrap.json') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Lint
run: npm run eslint
- name: Build
run: npm run build
- name: Test
run: npm run test-with-coverage
+36
View File
@@ -0,0 +1,36 @@
on:
workflow_dispatch:
schedule:
- cron: "*/10 * * * *"
#- cron: "0 4 * * *"
name: GHCR cleanup
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Delete untagged images
uses: actions/github-script@v3
with:
github-token: ${{ secrets.CR_PAT }}
script: |
const response = await github.request("GET /${{ env.OWNER }}/packages/container/${{ env.PACKAGE_NAME }}/versions",
{ per_page: ${{ env.PER_PAGE }}
});
for(version of response.data) {
if (version.metadata.container.tags.length == 0) {
try {
console.log("delete " + version.id)
const deleteResponse = await github.request("DELETE /${{ env.OWNER }}/packages/container/${{ env.PACKAGE_NAME }}/versions/" + version.id, { });
console.log("status " + deleteResponse.status)
} catch (e) {
console.log("failed")
}
}
}
env:
OWNER: user
PACKAGE_NAME: zigbee2mqtt
PER_PAGE: 2000
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
- uses: actions/stale@v5
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'
+5 -5
View File
@@ -8,17 +8,17 @@ jobs:
update_deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: dev
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
- run: rm -rf npm-shrinkwrap.json
- run: npx npm-check-updates -u
- run: npx npm-check-updates -u -x connect-gzip-static # connect-gzip-static only supports node 16
- run: npm install && npm shrinkwrap --dev
- uses: peter-evans/create-pull-request@v3
- uses: peter-evans/create-pull-request@v4
with:
commit-message: Update dependencies
branch: "update_deps"
+5 -5
View File
@@ -7,22 +7,22 @@ jobs:
update_frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: dev
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
- run: npm install zigbee2mqtt-frontend@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v3
- uses: peter-evans/create-pull-request@v4
id: cpr
with:
commit-message: Update zigbee2mqtt-frontend to ${{ github.event.client_payload.version }}
branch: "update_frontend"
title: Update zigbee-frontend to ${{ github.event.client_payload.version }}
- run: sleep 5 # Otherwise pull request may not exist yet causing automerge to fail
- uses: peter-evans/enable-pull-request-automerge@v1
- uses: peter-evans/enable-pull-request-automerge@v2
if: steps.cpr.outputs.pull-request-operation == 'created'
with:
token: ${{ secrets.CR_PAT }}
+5 -5
View File
@@ -7,22 +7,22 @@ jobs:
update_zh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: dev
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
- run: npm install zigbee-herdsman@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v3
- uses: peter-evans/create-pull-request@v4
id: cpr
with:
commit-message: Update zigbee-herdsman to ${{ github.event.client_payload.version }}
branch: "update_zh"
title: Update zigbee-herdsman to ${{ github.event.client_payload.version }}
- run: sleep 5 # Otherwise pull request may not exist yet causing automerge to fail
- uses: peter-evans/enable-pull-request-automerge@v1
- uses: peter-evans/enable-pull-request-automerge@v2
if: steps.cpr.outputs.pull-request-operation == 'created'
with:
token: ${{ secrets.CR_PAT }}
+5 -5
View File
@@ -7,22 +7,22 @@ jobs:
update_zhc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: dev
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
- run: npm install zigbee-herdsman-converters@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v3
- uses: peter-evans/create-pull-request@v4
id: cpr
with:
commit-message: Update zigbee-herdsman-converters to ${{ github.event.client_payload.version }}
branch: "update_zhc"
title: Update zigbee-herdsman-converters to ${{ github.event.client_payload.version }}
- run: sleep 5 # Otherwise pull request may not exist yet causing automerge to fail
- uses: peter-evans/enable-pull-request-automerge@v1
- uses: peter-evans/enable-pull-request-automerge@v2
if: steps.cpr.outputs.pull-request-operation == 'created'
with:
token: ${{ secrets.CR_PAT }}
+1 -1
View File
@@ -4,5 +4,5 @@ Everybody is invited and welcome to contribute to Zigbee2MQTT. Zigbee2MQTT is wr
- Pull requests are always created against the [**dev**](https://github.com/Koenkk/zigbee2mqtt/tree/dev) branch.
- Easiest way to start developing Zigbee2MQTT is by setting up a development environment (aka bare-metal installation). You can follow this [guide](https://www.zigbee2mqtt.io/guide/installation/01_linux.html) to do this.
- You can run the tests locally by executing `npm test`. Zigbee2MQTT enforces 100% code coverage, in case you add new code check if you code is covered by running `npm run test-with-coverage`. The coverage report can be found under `coverage/lcov-report/index.html`. Linting is also enforced and can be run with `npm run eslint`.
- You can run the tests locally by executing `npm test`. Zigbee2MQTT enforces 100% code coverage, in case you add new code check if your code is covered by running `npm run test-with-coverage`. The coverage report can be found under `coverage/lcov-report/index.html`. Linting is also enforced and can be run with `npm run eslint`.
- When you want to add support for a new devices no changes to Zigbee2MQTT have to be made, only to zigbee-herdsman-converters. You can find a guide for it [here](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html).
+1 -1
View File
@@ -67,7 +67,7 @@ Zigbee2MQTT integrates well with (almost) every home automation solution because
![Architecture](images/architecture.png)
### Internal Architecture
Zigbee2MQTT is made up of three modules, each developed in its own Github project. Starting from the hardware (adapter) and moving up; [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) connects to your Zigbee adapter and makes an API available to the higher levels of the stack. For e.g. Texas Instruments hardware, zigbee-herdsman uses the [TI zStack monitoring and test API](https://github.com/koenkk/zigbee-herdsman/raw/master/docs/Z-Stack%20Monitor%20and%20Test%20API.pdf) to communicate with the adapter. Zigbee-herdsman handles the core Zigbee communication. The module [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters) handles the mapping from individual device models to the Zigbee clusters they support. [Zigbee clusters](https://github.com/Koenkk/zigbee-herdsman/blob/master/docs/07-5123-08-Zigbee-Cluster-Library.pdf) are the layers of the Zigbee protocol on top of the base protocol that define things like how lights, sensors and switches talk to each other over the Zigbee network. Finally, the Zigbee2MQTT module drives zigbee-herdsman and maps the zigbee messages to MQTT messages. Zigbee2MQTT also keeps track of the state of the system. It uses a `database.db` file to store this state; a text file with a JSON database of connected devices and their capabilities.
Zigbee2MQTT is made up of three modules, each developed in its own Github project. Starting from the hardware (adapter) and moving up; [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) connects to your Zigbee adapter and makes an API available to the higher levels of the stack. For e.g. Texas Instruments hardware, zigbee-herdsman uses the [TI zStack monitoring and test API](https://github.com/koenkk/zigbee-herdsman/raw/master/docs/Z-Stack%20Monitor%20and%20Test%20API.pdf) to communicate with the adapter. Zigbee-herdsman handles the core Zigbee communication. The module [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters) handles the mapping from individual device models to the Zigbee clusters they support. [Zigbee clusters](https://github.com/Koenkk/zigbee-herdsman/blob/master/docs/07-5123-08-Zigbee-Cluster-Library.pdf) are the layers of the Zigbee protocol on top of the base protocol that define things like how lights, sensors and switches talk to each other over the Zigbee network. Finally, the Zigbee2MQTT module drives zigbee-herdsman and maps the zigbee messages to MQTT messages. Zigbee2MQTT also keeps track of the state of the system. It uses a `database.db` file to store this state; a text file with a JSON database of connected devices and their capabilities. Zigbee2MQTT provides a [web-based interface](https://github.com/nurikk/zigbee2mqtt-frontend) that allows monitoring and configuration.
### Developing
Zigbee2MQTT uses TypeScript (partially for now). Therefore after making changes to files in the `lib/` directory you need to recompile Zigbee2MQTT. This can be done by executing `npm run build`. For faster development instead of running `npm run build` you can run `npm run build-watch` in another terminal session, this will recompile as you change files.
+3 -6
View File
@@ -1,4 +1,4 @@
FROM node:14-alpine3.12 as base
FROM node:16-alpine3.15 as base
WORKDIR /app
RUN apk add --no-cache tzdata eudev tini
@@ -12,9 +12,6 @@ COPY npm-shrinkwrap.json tsconfig.json index.js ./
COPY lib ./lib
RUN apk add --no-cache --virtual .buildtools make gcc g++ python3 linux-headers git && \
npm ci --no-audit --no-optional --no-update-notifier && \
npm run build && \
rm -rf node_modules && \
npm ci --production --no-audit --no-optional --no-update-notifier && \
apk del .buildtools
@@ -22,7 +19,7 @@ RUN apk add --no-cache --virtual .buildtools make gcc g++ python3 linux-headers
FROM base as release
COPY --from=dependencies_and_build /app/node_modules ./node_modules
COPY --from=dependencies_and_build /app/dist ./dist
COPY dist ./dist
COPY LICENSE index.js data/configuration.yaml ./
COPY docker/docker-entrypoint.sh /usr/local/bin/
@@ -34,4 +31,4 @@ ARG COMMIT
RUN echo "$COMMIT" > dist/.hash
ENTRYPOINT ["docker-entrypoint.sh"]
CMD [ "/sbin/tini", "--", "node", "index.js"]
CMD [ "/sbin/tini", "--", "node", "index.js"]
+20 -8
View File
@@ -2,8 +2,8 @@ require('core-js/features/object/from-entries');
require('core-js/features/array/flat');
const semver = require('semver');
const engines = require('./package.json').engines;
const indexJsRestart = 'indexjs.restart';
const fs = require('fs');
const os = require('os');
const path = require('path');
const {exec} = require('child_process');
const rimraf = require('rimraf');
@@ -15,12 +15,12 @@ let stopping = false;
const hashFile = path.join(__dirname, 'dist', '.hash');
async function restart() {
await stop(indexJsRestart);
await stop(true);
await start();
}
async function exit(code, reason) {
if (reason !== indexJsRestart) {
async function exit(code, restart) {
if (!restart) {
process.exit(code);
}
}
@@ -44,9 +44,21 @@ async function build(reason) {
return new Promise((resolve, reject) => {
process.stdout.write(`Building Zigbee2MQTT... (${reason})`);
rimraf.sync('dist');
exec('npm run build', {cwd: __dirname}, async (err, stdout, stderr) => {
const env = {...process.env};
const _600mb = 629145600;
if (_600mb > os.totalmem() && !env.NODE_OPTIONS) {
// Prevent OOM on tsc compile for system with low memory
// https://github.com/Koenkk/zigbee2mqtt/issues/12034
env.NODE_OPTIONS = '--max_old_space_size=256';
}
exec('npm run build', {env, cwd: __dirname}, async (err, stdout, stderr) => {
if (err) {
process.stdout.write(', failed\n');
if (err.code === 134) {
process.stderr.write(
'\n\nBuild failed; ran out-of-memory, free some memory (RAM) and start again\n\n');
}
reject(err);
} else {
process.stdout.write(', finished\n');
@@ -97,14 +109,14 @@ async function start() {
await controller.start();
}
async function stop(reason=null) {
await controller.stop(reason);
async function stop(restart) {
await controller.stop(restart);
}
async function handleQuit() {
if (!stopping && controller) {
stopping = true;
await stop();
await stop(false);
}
}
+22 -15
View File
@@ -45,11 +45,12 @@ class Controller {
private state: State;
private mqtt: MQTT;
private restartCallback: () => void;
private exitCallback: (code: number) => void;
private exitCallback: (code: number, restart: boolean) => void;
private extensions: Extension[];
private extensionArgs: ExtensionArgs;
constructor(restartCallback: () => void, exitCallback: (code: number) => void) {
logger.init();
this.eventBus = new EventBus( /* istanbul ignore next */ (error) => {
logger.error(`Error: ${error.message}`);
logger.debug(error.stack);
@@ -104,13 +105,14 @@ class Controller {
logger.error('Check https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html for possible solutions'); /* eslint-disable-line max-len */
logger.error('Exiting...');
logger.error(error.stack);
this.exitCallback(1);
await this.exit(1);
}
// Disable some legacy options on new network creation
if (startResult === 'reset') {
settings.set(['advanced', 'homeassistant_legacy_entity_attributes'], false);
settings.set(['advanced', 'legacy_api'], false);
settings.set(['advanced', 'legacy_availability_payload'], false);
settings.set(['device_options', 'legacy'], false);
this.enableDisableExtension(false, 'BridgeLegacy');
}
@@ -145,7 +147,7 @@ class Controller {
logger.error(`MQTT failed to connect: ${error.message}`);
logger.error('Exiting...');
await this.zigbee.stop();
this.exitCallback(1);
await this.exit(1);
}
// Call extensions
@@ -185,7 +187,7 @@ class Controller {
await this.callExtensions('start', [extension]);
}
async stop(): Promise<void> {
async stop(restart = false): Promise<void> {
// Call extensions
await this.callExtensions('stop', this.extensions);
this.eventBus.removeListeners(this);
@@ -197,13 +199,18 @@ class Controller {
try {
await this.zigbee.stop();
logger.info('Stopped Zigbee2MQTT');
this.exitCallback(0);
await this.exit(0, restart);
} catch (error) {
logger.error('Failed to stop Zigbee2MQTT');
this.exitCallback(1);
await this.exit(1, restart);
}
}
async exit(code: number, restart = false): Promise<void> {
await logger.end();
this.exitCallback(code, restart);
}
@bind async onZigbeeAdapterDisconnected(): Promise<void> {
logger.error('Adapter disconnected, stopping');
await this.stop();
@@ -222,11 +229,11 @@ class Controller {
}
const options: MQTTOptions = {
retain: utils.getObjectProperty(entity.settings, 'retain', false) as boolean,
qos: utils.getObjectProperty(entity.settings, 'qos', 0) as 0 | 1 | 2,
retain: utils.getObjectProperty(entity.options, 'retain', false) as boolean,
qos: utils.getObjectProperty(entity.options, 'qos', 0) as 0 | 1 | 2,
};
const retention = utils.getObjectProperty(entity.settings, 'retention', false);
const retention = utils.getObjectProperty(entity.options, 'retention', false);
if (retention !== false) {
options.properties = {messageExpiryInterval: retention as number};
}
@@ -254,17 +261,17 @@ class Controller {
message.linkquality = entity.zh.linkquality;
}
// filter mqtt message attributes
if (entity.settings.filtered_attributes) {
entity.settings.filtered_attributes.forEach((a) => delete message[a]);
}
for (const extension of this.extensions) {
extension.adjustMessageBeforePublish?.(entity, message);
}
// filter mqtt message attributes
if (entity.options.filtered_attributes) {
entity.options.filtered_attributes.forEach((a) => delete message[a]);
}
if (Object.entries(message).length) {
const output = settings.get().experimental.output;
const output = settings.get().advanced.output;
if (output === 'attribute_and_json' || output === 'json') {
await this.mqtt.publish(entity.name, stringify(message), options);
}
+20 -6
View File
@@ -27,10 +27,17 @@ export default class EventBus {
this.on('permitJoinChanged', callback, key);
}
public emitDeviceRenamed(data: eventdata.DeviceRenamed): void {
public emitPublishAvailability(): void {
this.emitter.emit('publishAvailability');
}
public onPublishAvailability(key: ListenerKey, callback: () => void): void {
this.on('publishAvailability', callback, key);
}
public emitEntityRenamed(data: eventdata.EntityRenamed): void {
this.emitter.emit('deviceRenamed', data);
}
public onDeviceRenamed(key: ListenerKey, callback: (data: eventdata.DeviceRenamed) => void): void {
public onEntityRenamed(key: ListenerKey, callback: (data: eventdata.EntityRenamed) => void): void {
this.on('deviceRenamed', callback, key);
}
@@ -77,6 +84,13 @@ export default class EventBus {
this.on('deviceJoined', callback, key);
}
public emitEntityOptionsChanged(data: eventdata.EntityOptionsChanged): void {
this.emitter.emit('entityOptionsChanged', data);
}
public onEntityOptionsChanged(key: ListenerKey, callback: (data: eventdata.EntityOptionsChanged) => void): void {
this.on('entityOptionsChanged', callback, key);
}
public emitDeviceLeave(data: eventdata.DeviceLeave): void {
this.emitter.emit('deviceLeave', data);
}
@@ -133,11 +147,11 @@ export default class EventBus {
this.on('scenesChanged', callback, key);
}
public emitReportingDisabled(data: eventdata.ReportingDisabled): void {
this.emitter.emit('reportingDisabled', data);
public emitReconfigure(data: eventdata.Reconfigure): void {
this.emitter.emit('reconfigure', data);
}
public onReportingDisabled(key: ListenerKey, callback: (data: eventdata.ReportingDisabled) => void): void {
this.on('reportingDisabled', callback, key);
public onReconfigure(key: ListenerKey, callback: (data: eventdata.Reconfigure) => void): void {
this.on('reconfigure', callback, key);
}
public emitStateChange(data: eventdata.StateChange): void {
+73 -47
View File
@@ -5,6 +5,12 @@ import * as settings from '../util/settings';
import debounce from 'debounce';
import bind from 'bind-decorator';
const retrieveOnReconnect = [
{keys: ['state']},
{keys: ['brightness'], condition: (state: KeyValue): boolean => state.state === 'ON'},
{keys: ['color', 'color_temp'], condition: (state: KeyValue): boolean => state.state === 'ON'},
];
export default class Availability extends Extension {
private timers: {[s: string]: NodeJS.Timeout} = {};
private availabilityCache: {[s: string]: boolean} = {};
@@ -13,17 +19,14 @@ export default class Availability extends Extension {
private pingQueueExecuting = false;
private getTimeout(device: Device): number {
if (typeof device.settings.availability === 'object' && device.settings.availability?.timeout != null) {
return utils.minutes(device.settings.availability.timeout);
if (typeof device.options.availability === 'object' && device.options.availability?.timeout != null) {
return utils.minutes(device.options.availability.timeout);
}
const key = this.isActiveDevice(device) ? 'active' : 'passive';
const availabilitySettings = settings.get().availability;
if (typeof availabilitySettings === 'object' && availabilitySettings[key]?.timeout != null) {
return utils.minutes(availabilitySettings[key]?.timeout);
}
return key === 'active' ? utils.minutes(10) : utils.hours(25);
let value = settings.get().availability?.[key]?.timeout;
if (value == null) value = key == 'active' ? 10 : 1500;
return utils.minutes(value);
}
private isActiveDevice(device: Device): boolean {
@@ -31,9 +34,14 @@ export default class Availability extends Extension {
device.zh.powerSource === 'Mains (single phase)';
}
private isAvailable(device: Device): boolean {
const ago = Date.now() - device.zh.lastSeen;
return ago < this.getTimeout(device);
private isAvailable(entity: Device | Group): boolean {
if (entity.isDevice()) {
const ago = Date.now() - entity.zh.lastSeen;
return ago < this.getTimeout(entity);
} else {
return entity.membersDevices().length === 0 ||
entity.membersDevices().map((d) => this.availabilityCache[d.ieeeAddr]).includes(true);
}
}
private resetTimer(device: Device): void {
@@ -95,60 +103,78 @@ export default class Availability extends Extension {
}
override async start(): Promise<void> {
logger.warn('Using experimental new availability feature');
this.eventBus.onEntityRenamed(this, (data) => {
if (utils.isAvailabilityEnabledForEntity(data.entity, settings.get())) {
this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 0});
this.publishAvailability(data.entity, false, true);
}
});
this.eventBus.onDeviceRenamed(this, (data) => this.publishAvailability(data.device, false, true));
this.eventBus.onDeviceRemoved(this, (data) => clearTimeout(this.timers[data.ieeeAddr]));
this.eventBus.onDeviceLeave(this, (data) => clearTimeout(this.timers[data.ieeeAddr]));
this.eventBus.onDeviceAnnounce(this, (data) => this.retrieveState(data.device));
this.eventBus.onLastSeenChanged(this, this.onLastSeenChanged);
this.eventBus.onPublishAvailability(this, this.publishAvailabilityForAllEntities);
this.eventBus.onGroupMembersChanged(this, (data) => this.publishAvailability(data.group, false));
this.publishAvailabilityForAllEntities();
}
for (const device of this.zigbee.devices(false)) {
if (utils.isAvailabilityEnabledForDevice(device, settings.get())) {
@bind private publishAvailabilityForAllEntities(): void {
for (const entity of [...this.zigbee.devices(false), ...this.zigbee.groups()]) {
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
// Publish initial availablility
this.publishAvailability(device, true);
this.publishAvailability(entity, true, false, true);
this.resetTimer(device);
if (entity.isDevice()) {
this.resetTimer(entity);
// If an active device is initially unavailable, ping it.
if (this.isActiveDevice(device) && !this.isAvailable(device)) {
this.addToPingQueue(device);
// If an active device is initially unavailable, ping it.
if (this.isActiveDevice(entity) && !this.isAvailable(entity)) {
this.addToPingQueue(entity);
}
}
}
}
}
private publishAvailability(device: Device, logLastSeen: boolean, forcePublish=false): void {
if (logLastSeen) {
const ago = Date.now() - device.zh.lastSeen;
if (this.isActiveDevice(device)) {
logger.debug(`Active device '${device.name}' was last seen ` +
private publishAvailability(entity: Device | Group, logLastSeen: boolean,
forcePublish=false, skipGroups=false): void {
if (logLastSeen && entity.isDevice()) {
const ago = Date.now() - entity.zh.lastSeen;
if (this.isActiveDevice(entity)) {
logger.debug(`Active device '${entity.name}' was last seen ` +
`'${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
} else {
logger.debug(
`Passive device '${device.name}' was last seen '${(ago / utils.hours(1)).toFixed(2)}' hours ago.`);
`Passive device '${entity.name}' was last seen '${(ago / utils.hours(1)).toFixed(2)}' hours ago.`);
}
}
const available = this.isAvailable(device);
if (!forcePublish && this.availabilityCache[device.ieeeAddr] == available) {
const available = this.isAvailable(entity);
if (!forcePublish && this.availabilityCache[entity.ID] == available) {
return;
}
if (device.ieeeAddr in this.availabilityCache && available &&
this.availabilityCache[device.ieeeAddr] === false) {
logger.debug(`Device '${device.name}' reconnected`);
this.retrieveState(device);
if (entity.isDevice() && entity.ieeeAddr in this.availabilityCache && available &&
this.availabilityCache[entity.ieeeAddr] === false) {
logger.debug(`Device '${entity.name}' reconnected`);
this.retrieveState(entity);
}
const topic = `${device.name}/availability`;
const payload = available ? 'online' : 'offline';
this.availabilityCache[device.ieeeAddr] = available;
const topic = `${entity.name}/availability`;
const payload = utils.availabilityPayload(available ? 'online' : 'offline', settings.get());
this.availabilityCache[entity.ID] = available;
this.mqtt.publish(topic, payload, {retain: true, qos: 0});
if (!skipGroups && entity.isDevice()) {
this.zigbee.groups().filter((g) => g.hasMember(entity))
.filter((g) => utils.isAvailabilityEnabledForEntity(g, settings.get()))
.forEach((g) => this.publishAvailability(g, false, forcePublish));
}
}
@bind private onLastSeenChanged(data: eventdata.LastSeenChanged): void {
if (utils.isAvailabilityEnabledForDevice(data.device, settings.get())) {
if (utils.isAvailabilityEnabledForEntity(data.device, settings.get())) {
// Remove from ping queue, not necessary anymore since we know the device is online.
this.removeFromPingQueue(data.device);
this.resetTimer(data.device);
@@ -168,17 +194,17 @@ export default class Availability extends Extension {
*/
if (device.definition && !device.zh.interviewing && !this.retrieveStateDebouncers[device.ieeeAddr]) {
this.retrieveStateDebouncers[device.ieeeAddr] = debounce(async () => {
try {
logger.debug(`Retrieving state of '${device.name}' after reconnect`);
// Color and color temperature converters do both, only needs to be called once.
const keySet = [['state'], ['brightness'], ['color', 'color_temp']];
for (const keys of keySet) {
const converter = device.definition.toZigbee.find((c) => c.key.find((k) => keys.includes(k)));
await converter?.convertGet?.(device.endpoint(), keys[0],
{message: this.state.get(device) || {}, mapped: device.definition});
}
} catch (error) {
logger.error(`Failed to read state of '${device.name}' after reconnect (${error.message})`);
logger.debug(`Retrieving state of '${device.name}' after reconnect`);
// Color and color temperature converters do both, only needs to be called once.
for (const item of retrieveOnReconnect) {
if (item.condition && this.state.get(device) && !item.condition(this.state.get(device))) continue;
const converter = device.definition.toZigbee.find((c) => c.key.find((k) => item.keys.includes(k)));
await converter?.convertGet?.(device.endpoint(), item.keys[0],
{message: this.state.get(device), mapped: device.definition})
.catch((e) => {
logger.error(`Failed to read state of '${device.name}' after reconnect (${e.message})`);
});
await utils.sleep(500);
}
}, utils.seconds(2));
}
+4 -1
View File
@@ -99,6 +99,7 @@ const pollOnMessage: PollOnMessage = [
zigbeeHersdman.Zcl.ManufacturerCode.ATMEL,
zigbeeHersdman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHersdman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
zigbeeHersdman.Zcl.ManufacturerCode.TELINK,
],
},
{
@@ -129,6 +130,7 @@ const pollOnMessage: PollOnMessage = [
zigbeeHersdman.Zcl.ManufacturerCode.ATMEL,
zigbeeHersdman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHersdman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
zigbeeHersdman.Zcl.ManufacturerCode.TELINK,
],
},
{
@@ -155,6 +157,7 @@ const pollOnMessage: PollOnMessage = [
zigbeeHersdman.Zcl.ManufacturerCode.ATMEL,
zigbeeHersdman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHersdman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
zigbeeHersdman.Zcl.ManufacturerCode.TELINK,
],
},
];
@@ -417,7 +420,7 @@ export default class Bind extends Extension {
}
}
this.eventBus.emitReportingDisabled({device});
this.eventBus.emitReconfigure({device});
}
}
+16 -9
View File
@@ -64,7 +64,7 @@ export default class Bridge extends Extension {
this.zigbee2mqttVersion = await utils.getZigbee2MQTTVersion();
this.coordinatorVersion = await this.zigbee.getCoordinatorVersion();
this.eventBus.onDeviceRenamed(this, () => this.publishInfo());
this.eventBus.onEntityRenamed(this, () => this.publishInfo());
this.eventBus.onGroupMembersChanged(this, () => this.publishGroups());
this.eventBus.onDevicesChanged(this, () => this.publishDevices() && this.publishInfo());
this.eventBus.onPermitJoinChanged(this, () => !this.zigbee.isStopping() && this.publishInfo());
@@ -392,13 +392,14 @@ export default class Bridge extends Extension {
const ID = message.id;
const entity = this.getEntity(entityType, ID);
const oldOptions = objectAssignDeep({}, cleanup(entity.settings));
const oldOptions = objectAssignDeep({}, cleanup(entity.options));
settings.changeEntityOptions(ID, message.options);
const newOptions = cleanup(entity.settings);
const newOptions = cleanup(entity.options);
await this.publishInfo();
logger.info(`Changed config for ${entityType} ${ID}`);
this.eventBus.emitEntityOptionsChanged({from: oldOptions, to: newOptions, entity});
return utils.getResponse(message, {from: oldOptions, to: newOptions, id: ID}, null);
}
@@ -418,7 +419,7 @@ export default class Bridge extends Extension {
await endpoint.configureReporting(message.cluster, [{
attribute: message.attribute, minimumReportInterval: message.minimum_report_interval,
maximumReportInterval: message.maximum_report_interval, reportableChange: message.reportable_change,
}]);
}], message.options);
this.publishDevices();
@@ -447,16 +448,17 @@ export default class Bridge extends Extension {
const homeAssisantRename = message.hasOwnProperty('homeassistant_rename') ?
message.homeassistant_rename : false;
const entity = this.getEntity(entityType, from);
const oldFriendlyName = entity.settings.friendly_name;
const oldFriendlyName = entity.options.friendly_name;
settings.changeFriendlyName(from, to);
// Clear retained messages
this.mqtt.publish(oldFriendlyName, '', {retain: true});
this.eventBus.emitEntityRenamed({entity: entity, homeAssisantRename, from: oldFriendlyName, to});
if (entity instanceof Device) {
this.publishDevices();
this.eventBus.emitDeviceRenamed({device: entity, homeAssisantRename, from: oldFriendlyName, to});
} else {
this.publishGroups();
this.publishInfo();
@@ -565,7 +567,10 @@ export default class Bridge extends Extension {
const payload = {
version: this.zigbee2mqttVersion.version,
commit: this.zigbee2mqttVersion.commitHash,
coordinator: this.coordinatorVersion,
coordinator: {
ieee_address: this.zigbee.firstCoordinatorEndpoint().getDevice().ieeeAddr,
...this.coordinatorVersion,
},
network: utils.toSnakeCase(await this.zigbee.getNetworkParameters()),
log_level: logger.getLevel(),
permit_join: this.zigbee.getPermitJoin(),
@@ -645,6 +650,7 @@ export default class Bridge extends Extension {
network_address: device.zh.networkAddress,
supported: !!device.definition,
friendly_name: device.name,
description: device.options.description,
definition: this.getDefinitionPayload(device),
power_source: device.zh.powerSource,
software_build_id: device.zh.softwareBuildID,
@@ -666,6 +672,7 @@ export default class Bridge extends Extension {
return {
id: g.ID,
friendly_name: g.ID === 901 ? 'default_bind_group' : g.name,
description: g.options.description,
scenes: this.getScenes(g.zh),
members: g.zh.members.map((e) => {
return {ieee_address: e.getDevice().ieeeAddr, endpoint: e.ID};
@@ -678,7 +685,7 @@ export default class Bridge extends Extension {
getDefinitionPayload(device: Device): DefinitionPayload {
if (!device.definition) return null;
let icon = device.settings.icon ? device.settings.icon : device.definition.icon;
let icon = device.options.icon ? device.options.icon : device.definition.icon;
if (icon) {
icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(device.zh.modelID));
icon = icon.replace('${model}', utils.sanitizeImageParameter(device.definition.model));
@@ -688,7 +695,7 @@ export default class Bridge extends Extension {
model: device.definition.model,
vendor: device.definition.vendor,
description: device.definition.description,
exposes: device.definition.exposes,
exposes: device.exposes(),
supports_ota: !!device.definition.ota,
options: device.definition.options,
icon,
+5 -4
View File
@@ -16,7 +16,7 @@ export default class Configure extends Extension {
private topic = `${settings.get().mqtt.base_topic}/bridge/request/device/configure`;
private legacyTopic = `${settings.get().mqtt.base_topic}/bridge/configure`;
@bind private async onReportingDisabled(data: eventdata.ReportingDisabled): Promise<void> {
@bind private async onReconfigure(data: eventdata.Reconfigure): Promise<void> {
// Disabling reporting unbinds some cluster which could be bound by configure, re-setup.
if (data.device.zh.meta?.hasOwnProperty('configured')) {
delete data.device.zh.meta.configured;
@@ -79,13 +79,13 @@ export default class Configure extends Extension {
this.eventBus.onDeviceInterview(this, (data) => this.configure(data.device, 'zigbee_event'));
this.eventBus.onLastSeenChanged(this, (data) => this.configure(data.device, 'zigbee_event'));
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onReportingDisabled(this, this.onReportingDisabled);
this.eventBus.onReconfigure(this, this.onReconfigure);
}
private async configure(device: Device, event: 'started' | 'zigbee_event' | 'reporting_disabled' | 'mqtt_message',
force=false, thowError=false): Promise<void> {
if (!force) {
if (!device.definition?.configure || device.zh.interviewing) {
if (!device.definition?.configure || !device.zh.interviewCompleted) {
return;
}
@@ -112,7 +112,8 @@ export default class Configure extends Extension {
logger.info(`Configuring '${device.name}'`);
try {
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger);
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger,
device.options);
logger.info(`Successfully configured '${device.name}'`);
device.zh.meta.configured = zhc.getConfigureKey(device.definition);
device.zh.save();
+4 -8
View File
@@ -17,9 +17,9 @@ import bind from 'bind-decorator';
*/
export default class Frontend extends Extension {
private mqttBaseTopic = settings.get().mqtt.base_topic;
private host = settings.get().frontend.host || '0.0.0.0';
private port = settings.get().frontend.port || 8080;
private authToken = settings.get().frontend.auth_token || false;
private host = settings.get().frontend.host;
private port = settings.get().frontend.port;
private authToken = settings.get().frontend.auth_token;
private retainedMessages = new Map();
private server: http.Server;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -100,11 +100,7 @@ export default class Frontend extends Extension {
}
for (const device of this.zigbee.devices(false)) {
let payload: KeyValue = {};
if (this.state.exists(device)) {
payload = {...payload, ...this.state.get(device)};
}
const payload = this.state.get(device);
const lastSeen = settings.get().advanced.last_seen;
/* istanbul ignore if */
if (lastSeen !== 'disable') {
+13 -12
View File
@@ -13,17 +13,17 @@ const topicRegex =
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, definition: zhc.Definition) => boolean} = {
const stateProperties: {[s: string]: (value: string, exposes: zhc.DefinitionExpose[]) => boolean} = {
'state': () => true,
'brightness': (value, definition) =>
!!definition.exposes.find((e) => e.type === 'light' && e.features.find((f) => f.name === 'brightness')),
'color_temp': (value, definition) =>
!!definition.exposes.find((e) => e.type === 'light' && e.features.find((f) => f.name === 'color_temp')),
'color': (value, definition) =>
!!definition.exposes.find((e) => e.type === 'light' &&
'brightness': (value, exposes) =>
!!exposes.find((e) => e.type === 'light' && e.features.find((f) => f.name === 'brightness')),
'color_temp': (value, exposes) =>
!!exposes.find((e) => e.type === 'light' && e.features.find((f) => f.name === 'color_temp')),
'color': (value, exposes) =>
!!exposes.find((e) => e.type === 'light' &&
e.features.find((f) => f.name === 'color_xy' || f.name === 'color_hs')),
'color_mode': (value, definition) =>
!!definition.exposes.find((e) => e.type === 'light' && (
'color_mode': (value, exposes) =>
!!exposes.find((e) => e.type === 'light' && (
(e.features.find((f) => f.name === `color_${value}`)) ||
(value === 'color_temp' && e.features.find((f) => f.name === 'color_temp')) )),
};
@@ -123,7 +123,7 @@ export default class Groups extends Extension {
if (Object.keys(payload).length) {
const entity = data.entity;
const groups = this.zigbee.groups().filter((g) => {
return g.settings && (!g.settings.hasOwnProperty('optimistic') || g.settings.optimistic);
return g.options && (!g.options.hasOwnProperty('optimistic') || g.options.optimistic);
});
if (entity instanceof Device) {
@@ -143,9 +143,10 @@ export default class Groups extends Extension {
const groupsToPublish: Set<Group> = new Set();
for (const member of entity.zh.members) {
const device = this.zigbee.resolveEntity(member.getDevice()) as Device;
const exposes = device.exposes();
const memberPayload: KeyValue = {};
Object.keys(payload).forEach((key) => {
if (stateProperties[key](payload[key], device.definition)) {
if (stateProperties[key](payload[key], exposes)) {
memberPayload[key] = payload[key];
}
});
@@ -180,7 +181,7 @@ export default class Groups extends Extension {
const device = this.zigbee.resolveEntity(member.getDevice());
if (this.state.exists(device)) {
const state = this.state.get(device);
if (state && state.state === 'ON') {
if (state.state === 'ON') {
return false;
}
}
+252 -152
View File
@@ -2,18 +2,19 @@ import * as settings from '../util/settings';
import logger from '../util/logger';
import utils from '../util/utils';
import stringify from 'json-stable-stringify-without-jsonify';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import assert from 'assert';
import Extension from './extension';
import bind from 'bind-decorator';
// eslint-disable-next-line camelcase
interface DiscoveryEntry {mockProperties: string[], type: string, object_id: string, discovery_payload: KeyValue}
interface MockProperty {property: string, value: KeyValue | string}
const sensorClick = {
// eslint-disable-next-line camelcase
interface DiscoveryEntry {mockProperties: MockProperty[], type: string, object_id: string, discovery_payload: KeyValue}
const sensorClick: DiscoveryEntry = {
type: 'sensor',
object_id: 'click',
mockProperties: ['click'],
mockProperties: [{property: 'click', value: null}],
discovery_payload: {
icon: 'mdi:toggle-switch',
value_template: '{{ value_json.click }}',
@@ -25,6 +26,30 @@ const ACCESS_SET = 0b010;
const groupSupportedTypes = ['light', 'switch', 'lock', 'cover'];
const defaultStatusTopic = 'homeassistant/status';
const legacyMapping = [
{
models: ['WXKG01LM', 'HS1EB/HS1EB-E', 'ICZB-KPD14S', 'TERNCY-SD01', 'TERNCY-PP01', 'ICZB-KPD18S',
'E1766', 'ZWallRemote0', 'ptvo.switch', '2AJZ4KPKEY', 'ZGRC-KEY-013', 'HGZB-02S', 'HGZB-045',
'HGZB-1S', 'AV2010/34', 'IM6001-BTP01', 'WXKG11LM', 'WXKG03LM', 'WXKG02LM_rev1', 'WXKG02LM_rev2',
'QBKG04LM', 'QBKG03LM', 'QBKG11LM', 'QBKG21LM', 'QBKG22LM', 'WXKG12LM', 'QBKG12LM',
'E1743'],
discovery: sensorClick,
},
{
models: ['ICTC-G-1'],
discovery: {
type: 'sensor',
mockProperties: [{property: 'brightness', value: null}],
object_id: 'brightness',
discovery_payload: {
unit_of_measurement: 'brightness',
icon: 'mdi:brightness-5',
value_template: '{{ value_json.brightness }}',
},
},
},
];
const featurePropertyWithoutEndpoint = (feature: zhc.DefinitionExposeFeature): string => {
if (feature.endpoint) {
return feature.property.slice(0, -1 + -1 * feature.endpoint.length);
@@ -37,19 +62,19 @@ const featurePropertyWithoutEndpoint = (feature: zhc.DefinitionExposeFeature): s
* This extensions handles integration with HomeAssistant
*/
export default class HomeAssistant extends Extension {
private discovered: {[s: string]: {topics: Set<string>, mockProperties: Set<string>}} = {};
private mapping: {[s: string]: DiscoveryEntry[]} = {};
private discovered: {[s: string]:
{topics: Set<string>, mockProperties: Set<MockProperty>, objectIDs: Set<string>}} = {};
private discoveredTriggers : {[s: string]: Set<string>}= {};
private discoveryTopic = settings.get().advanced.homeassistant_discovery_topic;
private statusTopic = settings.get().advanced.homeassistant_status_topic;
private entityAttributes = settings.get().advanced.homeassistant_legacy_entity_attributes;
private discoveryTopic = settings.get().homeassistant.discovery_topic;
private statusTopic = settings.get().homeassistant.status_topic;
private entityAttributes = settings.get().homeassistant.legacy_entity_attributes;
private zigbee2MQTTVersion: string;
constructor(zigbee: Zigbee, mqtt: MQTT, state: State, publishEntityState: PublishEntityState,
eventBus: EventBus, enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
restartCallback: () => void, addExtension: (extension: Extension) => Promise<void>) {
super(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension, restartCallback, addExtension);
if (settings.get().experimental.output === 'attribute') {
if (settings.get().advanced.output === 'attribute') {
throw new Error('Home Assistant integration is not possible with attribute output!');
}
}
@@ -60,17 +85,17 @@ export default class HomeAssistant extends Extension {
}
this.zigbee2MQTTVersion = (await utils.getZigbee2MQTTVersion(false)).version;
this.populateMapping();
this.eventBus.onDeviceRemoved(this, this.onDeviceRemoved);
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onDeviceRenamed(this, this.onDeviceRenamed);
this.eventBus.onEntityRenamed(this, this.onEntityRenamed);
this.eventBus.onPublishEntityState(this, this.onPublishEntityState);
this.eventBus.onGroupMembersChanged(this, this.onGroupMembersChanged);
this.eventBus.onDeviceAnnounce(this, this.onZigbeeEvent);
this.eventBus.onDeviceJoined(this, this.onZigbeeEvent);
this.eventBus.onDeviceInterview(this, this.onZigbeeEvent);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
this.eventBus.onEntityOptionsChanged(this, (data) => this.discover(data.entity, true));
this.mqtt.subscribe(this.statusTopic);
this.mqtt.subscribe(defaultStatusTopic);
@@ -80,10 +105,13 @@ export default class HomeAssistant extends Extension {
for (const entity of [...this.zigbee.devices(false), ...this.zigbee.groups()]) {
this.discover(entity, true);
}
// Send availability messages, this is required if the legacy_availability_payload option has been changed.
this.eventBus.emitPublishAvailability();
}
private exposeToConfig(exposes: zhc.DefinitionExpose[], entityType: 'device' | 'group',
definition?: zhc.Definition): DiscoveryEntry[] {
definition?: zhc.Definition, definitionExposes?: zhc.DefinitionExpose[]): 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');
@@ -103,11 +131,16 @@ export default class HomeAssistant extends Extension {
const hasBrightness = exposes.find((expose) => expose.features.find((e) => e.name === 'brightness'));
const hasColorTemp = exposes.find((expose) => expose.features.find((e) => e.name === 'color_temp'));
const state = firstExpose.features.find((f) => f.name === 'state');
// Prefer HS over XY when at least one of the lights in the group prefers HS over XY.
// A light prefers HS over XY when HS is earlier in the feature array than HS.
const preferHS = exposes.map((e) => [e.features.findIndex((ee) => ee.name === 'color_xy'),
e.features.findIndex((ee) => ee.name === 'color_hs')])
.filter((d) => d[0] !== -1 && d[1] !== -1 && d[1] < d[0]).length !== 0;
const discoveryEntry: DiscoveryEntry = {
type: 'light',
object_id: endpoint ? `light_${endpoint}` : 'light',
mockProperties: [state.property],
mockProperties: [{property: state.property, value: null}],
discovery_payload: {
brightness: !!hasBrightness,
schema: 'json',
@@ -119,8 +152,8 @@ export default class HomeAssistant extends Extension {
};
const colorModes = [
hasColorXY ? 'xy' : null,
!hasColorXY && hasColorHS ? 'hs' : null,
hasColorXY && !preferHS ? 'xy' : null,
(!hasColorXY || preferHS) && hasColorHS ? 'hs' : null,
hasColorTemp ? 'color_temp' : null,
].filter((c) => c);
@@ -138,7 +171,7 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.min_mireds = min;
}
const effect = definition && definition.exposes.find((e) => e.type === 'enum' && e.name === 'effect');
const effect = definitionExposes?.find((e) => e.type === 'enum' && e.name === 'effect');
if (effect) {
discoveryEntry.discovery_payload.effect = true;
discoveryEntry.discovery_payload.effect_list = effect.values;
@@ -151,7 +184,7 @@ export default class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: 'switch',
object_id: endpoint ? `switch_${endpoint}` : 'switch',
mockProperties: [property],
mockProperties: [{property: property, value: null}],
discovery_payload: {
payload_off: state.value_off,
payload_on: state.value_on,
@@ -216,7 +249,7 @@ export default class HomeAssistant extends Extension {
const state = firstExpose.features.find((f) => f.name === 'running_state');
if (state) {
discoveryEntry.mockProperties.push(state.property);
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'}` +
@@ -271,7 +304,7 @@ export default class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: 'number',
object_id: endpoint ? `${tempCalibration.name}_${endpoint}` : `${tempCalibration.name}`,
mockProperties: [tempCalibration.property],
mockProperties: [{property: tempCalibration.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${tempCalibration.property} }}`,
command_topic: true,
@@ -290,10 +323,10 @@ export default class HomeAssistant extends Extension {
const piHeatingDemand = firstExpose.features.find((f) => f.name === 'pi_heating_demand');
if (piHeatingDemand) {
const discoveryEntry = {
const discoveryEntry: DiscoveryEntry = {
type: 'sensor',
object_id: endpoint ? `${piHeatingDemand.name}_${endpoint}` : `${piHeatingDemand.name}`,
mockProperties: [piHeatingDemand.property],
mockProperties: [{property: piHeatingDemand.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${piHeatingDemand.property} }}`,
...(piHeatingDemand.unit && {unit_of_measurement: piHeatingDemand.unit}),
@@ -313,7 +346,7 @@ export default class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: 'lock',
object_id: 'lock',
mockProperties: [state.property],
mockProperties: [{property: state.property, value: null}],
discovery_payload: {
command_topic: true,
value_template: `{{ value_json.${state.property} }}`,
@@ -347,6 +380,8 @@ export default class HomeAssistant extends Extension {
} else if (firstExpose.type === 'cover') {
const position = exposes.find((expose) => expose.features.find((e) => e.name === 'position'));
const tilt = exposes.find((expose) => expose.features.find((e) => e.name === 'tilt'));
const motorState = definitionExposes?.find((e) => e.type === 'enum' && e.name === 'motor_state' &&
e.access === ACCESS_STATE);
const discoveryEntry: DiscoveryEntry = {
type: 'cover',
@@ -365,6 +400,37 @@ export default class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.command_topic = true;
discoveryEntry.discovery_payload.state_topic = !position;
discoveryEntry.discovery_payload.command_topic_prefix = endpoint;
// For curtains that have `motor_state` lookup a possible state names and make this
// available for discovery. If the curtains only support the `running` value,
// then we use it anyway. The movement direction is calculated (assumed) in this case.
if (motorState) {
const openingLookup = ['opening', 'open', 'forward', 'up', 'rising'];
const closingLookup = ['closing', 'close', 'backward', 'back', 'reverse', 'down', 'declining'];
const stoppedLookup = ['stopped', 'stop', 'pause', 'paused'];
const openingState = motorState.values.find((s) => openingLookup.includes(s.toLowerCase()));
const closingState = motorState.values.find((s) => closingLookup.includes(s.toLowerCase()));
const stoppedState = motorState.values.find((s) => stoppedLookup.includes(s.toLowerCase()));
if (openingState && closingState && stoppedState) {
discoveryEntry.discovery_payload.state_topic = true;
discoveryEntry.discovery_payload.state_opening = openingState;
discoveryEntry.discovery_payload.state_closing = closingState;
discoveryEntry.discovery_payload.state_stopped = stoppedState;
discoveryEntry.discovery_payload.value_template = `{% if not value_json.motor_state %} ` +
`${stoppedState} {% else %} {{ value_json.motor_state }} {% endif %}`;
}
} else {
const running = definitionExposes?.find((e) => e.type === 'binary' && e.name === 'running');
if (running) {
discoveryEntry.discovery_payload.state_topic = true;
discoveryEntry.discovery_payload.value_template = `{% if not value_json.running %} ` +
`stopped {% else %} {% if value_json.position > 0 %} closing {% else %} ` +
`opening {% endif %} {% endif %}`;
}
}
}
if (!position && !tilt) {
@@ -396,7 +462,7 @@ export default class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: 'fan',
object_id: 'fan',
mockProperties: ['fan_state'],
mockProperties: [{property: 'fan_state', value: null}],
discovery_payload: {
state_topic: true,
state_value_template: '{{ value_json.fan_state }}',
@@ -460,11 +526,14 @@ export default class HomeAssistant extends Extension {
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'},
carbon_monoxide: {device_class: 'safety'},
card: {entity_category: 'config', icon: 'mdi:clipboard-check'},
child_lock: {entity_category: 'config', icon: 'mdi:account-lock'},
color_sync: {entity_category: 'config', icon: 'mdi:sync-circle'},
consumer_connected: {entity_category: 'diagnostic', device_class: 'connectivity'},
contact: {device_class: 'door'},
garage_door_contact: {device_class: 'garage_door', payload_on: false, payload_off: true},
eco_mode: {entity_category: 'config', icon: 'mdi:leaf'},
expose_pin: {entity_category: 'config', icon: 'mdi:pin'},
gas: {device_class: 'gas'},
@@ -472,6 +541,7 @@ export default class HomeAssistant extends Extension {
led_disabled_night: {entity_category: 'config', icon: 'mdi:led-off'},
led_indication: {entity_category: 'config', icon: 'mdi:led-on'},
legacy: {entity_category: 'config', icon: 'mdi:cog'},
motor_reversal: {entity_category: 'config', icon: 'mdi:arrow-left-right'},
moving: {device_class: 'moving'},
no_position_support: {entity_category: 'config', icon: 'mdi:minus-circle-outline'},
occupancy: {device_class: 'motion'},
@@ -480,6 +550,7 @@ export default class HomeAssistant extends Extension {
smoke: {device_class: 'smoke'},
sos: {device_class: 'safety'},
tamper: {device_class: 'tamper'},
temperature_scale: {entity_category: 'config', icon: 'mdi:temperature-celsius'},
test: {entity_category: 'diagnostic', icon: 'mdi:test-tube'},
vibration: {device_class: 'vibration'},
water_leak: {device_class: 'moisture'},
@@ -494,7 +565,7 @@ export default class HomeAssistant extends Extension {
if (firstExpose.access & ACCESS_SET) {
const discoveryEntry: DiscoveryEntry = {
type: 'switch',
mockProperties: [firstExpose.property],
mockProperties: [{property: firstExpose.property, value: null}],
object_id: endpoint ?
`switch_${firstExpose.name}_${endpoint}` :
`switch_${firstExpose.name}`,
@@ -512,10 +583,10 @@ export default class HomeAssistant extends Extension {
};
discoveryEntries.push(discoveryEntry);
} else {
const discoveryEntry = {
const discoveryEntry: DiscoveryEntry = {
type: 'binary_sensor',
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
mockProperties: [firstExpose.property],
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${firstExpose.property} }}`,
payload_on: firstExpose.value_on,
@@ -527,6 +598,12 @@ export default class HomeAssistant extends Extension {
}
} else if (firstExpose.type === 'numeric') {
const lookup: {[s: string]: KeyValue} = {
ac_frequency: {device_class: 'frequency', enabled_by_default: false, entity_category: 'diagnostic',
state_class: 'measurement'},
alarm_humidity_max: {device_class: 'humidity', icon: 'mdi:water-plus'},
alarm_humidity_min: {device_class: 'humidity', icon: 'mdi:water-minus'},
alarm_temperature_max: {device_class: 'temperature', icon: 'mdi:thermometer-high'},
alarm_temperature_min: {device_class: 'temperature', icon: 'mdi:thermometer-low'},
angle: {icon: 'angle-acute'},
angle_axis: {icon: 'angle-acute'},
aqi: {device_class: 'aqi', state_class: 'measurement'},
@@ -534,9 +611,11 @@ export default class HomeAssistant extends Extension {
away_preset_days: {entity_category: 'config', icon: 'mdi:timer'},
away_preset_temperature: {entity_category: 'config', icon: 'mdi:thermometer'},
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'},
boost_time: {entity_category: 'config', icon: 'mdi:timer'},
calibration: {entity_category: 'config'},
calibration: {entity_category: 'config', icon: 'mdi:wrench-clock'},
calibration_time: {entity_category: 'config', icon: 'mdi:wrench-clock'},
co2: {device_class: 'carbon_dioxide', state_class: 'measurement'},
comfort_temperature: {entity_category: 'config', icon: 'mdi:thermometer'},
cpu_temperature: {
@@ -565,6 +644,7 @@ export default class HomeAssistant extends Extension {
device_temperature: {
device_class: 'temperature', entity_category: 'diagnostic', state_class: 'measurement',
},
duration: {entity_category: 'config', icon: 'mdi:timer'},
eco2: {device_class: 'carbon_dioxide', state_class: 'measurement'},
eco_temperature: {entity_category: 'config', icon: 'mdi:thermometer'},
energy: {device_class: 'energy', state_class: 'total_increasing'},
@@ -572,6 +652,10 @@ export default class HomeAssistant extends Extension {
gas_density: {icon: 'mdi:google-circles-communities', state_class: 'measurement'},
hcho: {icon: 'mdi:air-filter', state_class: 'measurement'},
humidity: {device_class: 'humidity', state_class: 'measurement'},
humidity_calibration: {entity_category: 'config', icon: 'mdi:wrench-clock'},
humidity_max: {entity_category: 'config', icon: 'mdi:water-percent'},
humidity_min: {entity_category: 'config', icon: 'mdi:water-percent'},
illuminance_calibration: {entity_category: 'config', icon: 'mdi:wrench-clock'},
illuminance_lux: {device_class: 'illuminance', state_class: 'measurement'},
illuminance: {device_class: 'illuminance', enabled_by_default: false, state_class: 'measurement'},
linkquality: {
@@ -588,11 +672,15 @@ export default class HomeAssistant extends Extension {
occupancy_timeout: {entity_category: 'config', icon: 'mdi:timer'},
pm10: {device_class: 'pm10', state_class: 'measurement'},
pm25: {device_class: 'pm25', state_class: 'measurement'},
people: {state_class: 'measurement', icon: 'mdi:account-multiple'},
position: {icon: 'mdi:valve', state_class: 'measurement'},
power: {device_class: 'power', entity_category: 'diagnostic', state_class: 'measurement'},
power_factor: {device_class: 'power_factor', enabled_by_default: false,
entity_category: 'diagnostic', state_class: 'measurement'},
precision: {entity_category: 'config', icon: 'mdi:decimal-comma-increase'},
pressure: {device_class: 'pressure', state_class: 'measurement'},
presence_timeout: {entity_category: 'config', icon: 'mdi:timer'},
reporting_time: {entity_category: 'config', icon: 'mdi:clock-time-one-outline'},
requested_brightness_level: {
enabled_by_default: false, entity_category: 'diagnostic', icon: 'mdi:brightness-5',
},
@@ -602,6 +690,9 @@ export default class HomeAssistant extends Extension {
smoke_density: {icon: 'mdi:google-circles-communities', state_class: 'measurement'},
soil_moisture: {icon: 'mdi:water-percent', state_class: 'measurement'},
temperature: {device_class: 'temperature', state_class: 'measurement'},
temperature_calibration: {entity_category: 'config', icon: 'mdi:wrench-clock'},
temperature_max: {entity_category: 'config', icon: 'mdi:thermometer-plus'},
temperature_min: {entity_category: 'config', icon: 'mdi:thermometer-minus'},
transition: {entity_category: 'config', icon: 'mdi:transition'},
voc: {device_class: 'volatile_organic_compounds', state_class: 'measurement'},
vibration_timeout: {entity_category: 'config', icon: 'mdi:timer'},
@@ -628,31 +719,39 @@ export default class HomeAssistant extends Extension {
z_axis: {icon: 'mdi:axis-z-arrow'},
};
const extraAttrs = {};
// If a variable includes Wh, mark it as energy
if (firstExpose.unit && ['Wh', 'kWh'].includes(firstExpose.unit)) {
Object.assign(extraAttrs, {device_class: 'energy', state_class: 'total_increasing'});
}
const allowsSet = firstExpose.access & ACCESS_SET;
const discoveryEntry = {
const discoveryEntry: DiscoveryEntry = {
type: 'sensor',
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
mockProperties: [firstExpose.property],
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${firstExpose.property} }}`,
enabled_by_default: !allowsSet,
...(firstExpose.unit && {unit_of_measurement: firstExpose.unit}),
...lookup[firstExpose.name],
...extraAttrs,
},
};
discoveryEntries.push(discoveryEntry);
/**
* If numeric attribute has SET access then expose as SELECT entity too.
* Note: currently both sensor and number are discoverd, this is to avoid
* Note: currently both sensor and number are discovered, this is to avoid
* breaking changes for sensors already existing in HA (legacy).
*/
if (allowsSet) {
const discoveryEntry: DiscoveryEntry = {
type: 'number',
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
mockProperties: [firstExpose.property],
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${firstExpose.property} }}`,
command_topic: true,
@@ -671,14 +770,20 @@ export default class HomeAssistant extends Extension {
} else if (firstExpose.type === 'enum') {
const lookup: {[s: string]: KeyValue} = {
action: {icon: 'mdi:gesture-double-tap'},
alarm_humidity: {icon: 'mdi:water-percent-alert'},
alarm_temperature: {icon: 'mdi:thermometer-alert'},
backlight_auto_dim: {entity_category: 'config', icon: 'mdi:brightness-auto'},
backlight_mode: {entity_category: 'config', icon: 'mdi:lightbulb'},
color_power_on_behavior: {entity_category: 'config', icon: 'mdi:palette'},
control_mode: {entity_category: 'config', icon: 'mdi:tune'},
device_mode: {entity_category: 'config', icon: 'mdi:tune'},
effect: {enabled_by_default: false, icon: 'mdi:palette'},
force: {enabled_by_default: false, icon: 'mdi:valve'},
keep_time: {entity_category: 'config', icon: 'mdi:av-timer'},
keypad_lockout: {entity_category: 'config', icon: 'mdi:lock'},
load_detection_mode: {entity_category: 'config', icon: 'mdi:tune'},
load_dimmable: {entity_category: 'diagnostic', icon: 'mdi:chart-bell-curve'},
load_type: {entity_category: 'diagnostic', icon: 'mdi:led-on'},
melody: {entity_category: 'config', icon: 'mdi:music-note'},
mode_phase_control: {entity_category: 'config', icon: 'mdi:tune'},
mode: {entity_category: 'config', icon: 'mdi:tune'},
@@ -686,9 +791,12 @@ export default class HomeAssistant extends Extension {
operation_mode: {entity_category: 'config', icon: 'mdi:tune'},
power_on_behavior: {entity_category: 'config', icon: 'mdi:power-settings'},
power_outage_memory: {entity_category: 'config', icon: 'mdi:power-settings'},
power_supply_mode: {entity_category: 'diagnostic', icon: 'mdi:power-settings'},
power_type: {entity_category: 'diagnostic', icon: 'mdi:lightning-bolt-circle'},
sensitivity: {entity_category: 'config', icon: 'mdi:tune'},
sensors_type: {entity_category: 'config', icon: 'mdi:tune'},
sound_volume: {entity_category: 'config', icon: 'mdi:volume-high'},
status: {icon: 'mdi:state-machine'},
switch_type: {entity_category: 'config', icon: 'mdi:tune'},
thermostat_unit: {entity_category: 'config', icon: 'mdi:thermometer'},
volume: {entity_category: 'config', icon: 'mdi: volume-high'},
@@ -699,7 +807,7 @@ export default class HomeAssistant extends Extension {
discoveryEntries.push({
type: 'sensor',
object_id: firstExpose.property,
mockProperties: [firstExpose.property],
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${firstExpose.property} }}`,
enabled_by_default: !(firstExpose.access & ACCESS_SET),
@@ -709,14 +817,14 @@ export default class HomeAssistant extends Extension {
/**
* If enum attribute has SET access then expose as SELECT entity too.
* Note: currently both sensor and select are discoverd, this is to avoid
* Note: currently both sensor and select are discovered, this is to avoid
* breaking changes for sensors already existing in HA (legacy).
*/
if ((firstExpose.access & ACCESS_SET)) {
discoveryEntries.push({
type: 'select',
object_id: firstExpose.property,
mockProperties: [firstExpose.property],
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${firstExpose.property} }}`,
state_topic: true,
@@ -735,10 +843,10 @@ export default class HomeAssistant extends Extension {
action: {icon: 'mdi:gesture-double-tap'},
};
const discoveryEntry = {
const discoveryEntry: DiscoveryEntry = {
type: 'sensor',
object_id: firstExpose.property,
mockProperties: [firstExpose.property],
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
value_template: `{{ value_json.${firstExpose.property} }}`,
...lookup[firstExpose.name],
@@ -753,46 +861,6 @@ export default class HomeAssistant extends Extension {
return discoveryEntries;
}
private populateMapping(): void {
for (const def of zigbeeHerdsmanConverters.definitions) {
this.mapping[def.model] = [];
if (['WXKG01LM', 'HS1EB/HS1EB-E', 'ICZB-KPD14S', 'TERNCY-SD01', 'TERNCY-PP01', 'ICZB-KPD18S',
'E1766', 'ZWallRemote0', 'ptvo.switch', '2AJZ4KPKEY', 'ZGRC-KEY-013', 'HGZB-02S', 'HGZB-045',
'HGZB-1S', 'AV2010/34', 'IM6001-BTP01', 'WXKG11LM', 'WXKG03LM', 'WXKG02LM_rev1', 'WXKG02LM_rev2',
'QBKG04LM', 'QBKG03LM', 'QBKG11LM', 'QBKG21LM', 'QBKG22LM', 'WXKG12LM', 'QBKG12LM',
'E1743'].includes(def.model)) {
// deprecated
this.mapping[def.model].push(sensorClick);
}
if (['ICTC-G-1'].includes(def.model)) {
// deprecated
this.mapping[def.model].push({
type: 'sensor',
mockProperties: ['brightness'],
object_id: 'brightness',
discovery_payload: {
unit_of_measurement: 'brightness',
icon: 'mdi:brightness-5',
value_template: '{{ value_json.brightness }}',
},
});
}
for (const expose of def.exposes) {
this.mapping[def.model].push(...this.exposeToConfig([expose], 'device', def));
}
}
// Deprecated in favour of exposes
for (const definition of utils.getExternalConvertersDefinitions(settings.get())) {
if (definition.hasOwnProperty('homeassistant')) {
this.mapping[definition.model] = definition.homeassistant;
}
}
}
@bind onDeviceRemoved(data: eventdata.DeviceRemoved): void {
logger.debug(`Clearing Home Assistant discovery topic for '${data.name}'`);
this.discovered[data.ieeeAddr]?.topics.forEach((topic) => {
@@ -816,9 +884,9 @@ export default class HomeAssistant extends Extension {
* zigbee2mqtt/mydevice/l1.
*/
const entity = this.zigbee.resolveEntity(data.entity.name);
if (entity.isDevice() && this.mapping[entity.definition?.model]) {
for (const config of this.mapping[entity.definition.model]) {
const match = /light_(.*)/.exec(config['object_id']);
if (entity.isDevice() && this.discovered[entity.ieeeAddr]) {
for (const objectID of this.discovered[entity.ieeeAddr].objectIDs) {
const match = /light_(.*)/.exec(objectID);
if (match) {
const endpoint = match[1];
const endpointRegExp = new RegExp(`(.*)_${endpoint}`);
@@ -842,7 +910,7 @@ export default class HomeAssistant extends Extension {
* can use Home Assistant entities in automations.
* https://github.com/Koenkk/zigbee2mqtt/issues/959#issuecomment-480341347
*/
if (settings.get().advanced.homeassistant_legacy_triggers) {
if (settings.get().homeassistant.legacy_triggers) {
const keys = ['action', 'click'].filter((k) => data.message[k]);
for (const key of keys) {
this.publishEntityState(data.entity, {[key]: ''});
@@ -865,25 +933,25 @@ export default class HomeAssistant extends Extension {
}
}
@bind onDeviceRenamed(data: eventdata.DeviceRenamed): void {
logger.debug(`Refreshing Home Assistant discovery topic for '${data.device.ieeeAddr}'`);
@bind onEntityRenamed(data: eventdata.EntityRenamed): void {
logger.debug(`Refreshing Home Assistant discovery topic for '${data.entity.name}'`);
// Clear before rename so Home Assistant uses new friendly_name
// https://github.com/Koenkk/zigbee2mqtt/issues/4096#issuecomment-674044916
if (data.homeAssisantRename) {
for (const config of this.getConfigs(data.device)) {
const topic = this.getDiscoveryTopic(config, data.device);
for (const config of this.getConfigs(data.entity)) {
const topic = this.getDiscoveryTopic(config, data.entity);
this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic, false, false);
}
}
this.discover(data.device, true);
this.discover(data.entity, true);
if (this.discoveredTriggers[data.device.ieeeAddr]) {
for (const config of this.discoveredTriggers[data.device.ieeeAddr]) {
if (data.entity.isDevice() && this.discoveredTriggers[data.entity.ieeeAddr]) {
for (const config of this.discoveredTriggers[data.entity.ieeeAddr]) {
const key = config.substring(0, config.indexOf('_'));
const value = config.substring(config.indexOf('_') + 1);
this.publishDeviceTriggerDiscover(data.device, key, value, true);
this.publishDeviceTriggerDiscover(data.entity, key, value, true);
}
}
}
@@ -891,54 +959,76 @@ export default class HomeAssistant extends Extension {
private getConfigs(entity: Device | Group): DiscoveryEntry[] {
const isDevice = entity.isDevice();
/* istanbul ignore next */
if (!entity || (isDevice && !entity.definition) ||
(isDevice && !this.mapping[entity.definition.model])) return [];
if (!entity || (isDevice && !entity.definition)) return [];
let configs: DiscoveryEntry[];
let configs: DiscoveryEntry[] = [];
if (isDevice) {
configs = this.mapping[entity.definition.model].slice();
const exposes = entity.exposes(); // avoid calling it hundred of times/s
for (const expose of exposes) {
configs.push(...this.exposeToConfig([expose], 'device', entity.definition, exposes));
}
for (const mapping of legacyMapping) {
if (mapping.models.includes(entity.definition.model)) {
configs.push(mapping.discovery);
}
}
// Deprecated in favour of exposes
/* istanbul ignore if */
if (entity.definition.hasOwnProperty('homeassistant')) {
// @ts-ignore
configs.push(entity.definition.homeassistant);
}
} else { // group
const exposesByType: {[s: string]: zhc.DefinitionExpose[]} = {};
entity.membersDefinitions().forEach((definition) => {
for (const expose of definition.exposes.filter((e) => groupSupportedTypes.includes(e.type))) {
let key = expose.type;
if (['switch', 'lock', 'cover'].includes(expose.type) && expose.endpoint) {
// A device can have multiple of these types which have to discovered seperately.
// e.g. switch with property state and valve_detection.
const state = expose.features.find((f) => f.name === 'state');
key += featurePropertyWithoutEndpoint(state);
}
entity.zh.members.map((e) => this.zigbee.resolveEntity(e.getDevice()) as Device)
.filter((d) => d.definition).forEach((device) => {
for (const expose of device.exposes().filter((e) => groupSupportedTypes.includes(e.type))) {
let key = expose.type;
if (['switch', 'lock', 'cover'].includes(expose.type) && expose.endpoint) {
// A device can have multiple of these types which have to discovered seperately.
// e.g. switch with property state and valve_detection.
const state = expose.features.find((f) => f.name === 'state');
key += featurePropertyWithoutEndpoint(state);
}
if (!exposesByType[key]) exposesByType[key] = [];
exposesByType[key].push(expose);
}
});
if (!exposesByType[key]) exposesByType[key] = [];
exposesByType[key].push(expose);
}
});
configs = [].concat(...Object.values(exposesByType)
.map((exposes) => this.exposeToConfig(exposes, 'group')));
}
if (isDevice && settings.get().advanced.last_seen !== 'disable') {
configs.push({
const config: DiscoveryEntry = {
type: 'sensor',
object_id: 'last_seen',
mockProperties: ['last_seen'],
mockProperties: [{property: 'last_seen', value: null}],
discovery_payload: {
value_template: '{{ value_json.last_seen }}',
icon: 'mdi:clock',
enabled_by_default: false,
entity_category: 'diagnostic',
device_class: 'timestamp',
},
});
};
/* istanbul ignore else */
if (settings.get().advanced.last_seen.startsWith('ISO_8601')) {
config.discovery_payload.device_class = 'timestamp';
}
configs.push(config);
}
if (isDevice && entity.definition.hasOwnProperty('ota')) {
const updateStateSensor: DiscoveryEntry = {
type: 'sensor',
object_id: 'update_state',
mockProperties: [],
mockProperties: [{property: 'update', value: {state: null}}],
discovery_payload: {
icon: 'mdi:update',
value_template: `{{ value_json['update']['state'] }}`,
@@ -948,10 +1038,10 @@ export default class HomeAssistant extends Extension {
};
configs.push(updateStateSensor);
const updateAvailableSensor = {
const updateAvailableSensor: DiscoveryEntry = {
type: 'binary_sensor',
object_id: 'update_available',
mockProperties: ['update_available'],
mockProperties: [{property: 'update_available', value: null}],
discovery_payload: {
payload_on: true,
payload_off: false,
@@ -964,19 +1054,19 @@ export default class HomeAssistant extends Extension {
configs.push(updateAvailableSensor);
}
if (isDevice && entity.settings.hasOwnProperty('legacy') && !entity.settings.legacy) {
if (isDevice && entity.options.hasOwnProperty('legacy') && !entity.options.legacy) {
configs = configs.filter((c) => c !== sensorClick);
}
if (!settings.get().advanced.homeassistant_legacy_triggers) {
if (!settings.get().homeassistant.legacy_triggers) {
configs = configs.filter((c) => c.object_id !== 'action' && c.object_id !== 'click');
}
// deep clone of the config objects
configs = JSON.parse(JSON.stringify(configs));
if (entity.settings.homeassistant) {
const s = entity.settings.homeassistant;
if (entity.options.homeassistant) {
const s = entity.options.homeassistant;
configs = configs.filter((config) => !s.hasOwnProperty(config.object_id) || s[config.object_id] != null);
configs.forEach((config) => {
const configOverride = s[config.object_id];
@@ -995,19 +1085,18 @@ export default class HomeAssistant extends Extension {
}
private discover(entity: Device | Group, force=false): void {
// Check if already discoverd and check if there are configs.
// Check if already discovered and check if there are configs.
const discoverKey = this.getDiscoverKey(entity);
const discover = force || !this.discovered[discoverKey];
if (entity.isGroup()) {
if (!discover || entity.zh.members.length === 0) return;
} else if (!discover || !entity.definition || !this.mapping[entity.definition.model] ||
entity.zh.interviewing ||
(entity.settings.hasOwnProperty('homeassistant') && !entity.settings.homeassistant)) {
} else if (!discover || !entity.definition || entity.zh.interviewing ||
(entity.options.hasOwnProperty('homeassistant') && !entity.options.homeassistant)) {
return;
}
this.discovered[discoverKey] = {topics: new Set(), mockProperties: new Set()};
this.discovered[discoverKey] = {topics: new Set(), mockProperties: new Set(), objectIDs: new Set()};
this.getConfigs(entity).forEach((config) => {
const payload = {...config.discovery_payload};
const baseTopic = `${settings.get().mqtt.base_topic}/${entity.name}`;
@@ -1038,9 +1127,11 @@ export default class HomeAssistant extends Extension {
payload.json_attributes_topic = stateTopic;
}
// Set (unique) name, separate by space if friendlyName contains space.
const nameSeparator = entity.name.includes('_') ? '_' : ' ';
payload.name = entity.name;
const devicePayload = this.getDevicePayload(entity);
// Set (unique) name, separate by space if device name contains space.
const nameSeparator = devicePayload.name.includes('_') ? '_' : ' ';
payload.name = devicePayload.name;
if (config.object_id.startsWith(config.type) && config.object_id.includes('_')) {
payload.name += `${nameSeparator}${config.object_id.split(/_(.+)/)[1]}`;
} else if (!config.object_id.startsWith(config.type)) {
@@ -1048,22 +1139,24 @@ export default class HomeAssistant extends Extension {
}
// Set unique_id
payload.unique_id = `${entity.settings.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
payload.unique_id = `${entity.options.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
// Attributes for device registry
payload.device = this.getDevicePayload(entity);
payload.device = devicePayload;
// Availability payload
payload.availability = [{topic: `${settings.get().mqtt.base_topic}/bridge/state`}];
const availabilityEnabled =
entity.isDevice() && utils.isAvailabilityEnabledForDevice(entity, settings.get());
/* istanbul ignore next */
if (availabilityEnabled) {
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
payload.availability_mode = 'all';
payload.availability.push({topic: `${baseTopic}/availability`});
}
if (!settings.get().advanced.legacy_availability_payload) {
payload.availability.forEach((a: KeyValue) => a.value_template = '{{ value_json.state }}');
}
const commandTopicPrefix = payload.command_topic_prefix ? `${payload.command_topic_prefix}/` : '';
delete payload.command_topic_prefix;
const commandTopicPostfix = payload.command_topic_postfix ? `/${payload.command_topic_postfix}` : '';
@@ -1166,12 +1259,15 @@ export default class HomeAssistant extends Extension {
}
// Override configuration with user settings.
if (entity.settings.hasOwnProperty('homeassistant')) {
const add = (obj: KeyValue): void => {
if (entity.options.hasOwnProperty('homeassistant')) {
const add = (obj: KeyValue, ignoreName: boolean): void => {
Object.keys(obj).forEach((key) => {
if (['type', 'object_id'].includes(key)) {
return;
} else if (['number', 'string', 'boolean'].includes(typeof obj[key])) {
} else if (ignoreName && key === 'name') {
return;
} else if (['number', 'string', 'boolean'].includes(typeof obj[key]) ||
Array.isArray(obj[key])) {
payload[key] = obj[key];
} else if (obj[key] === null) {
delete payload[key];
@@ -1183,17 +1279,19 @@ export default class HomeAssistant extends Extension {
});
};
add(entity.settings.homeassistant);
add(entity.options.homeassistant, true);
if (entity.settings.homeassistant.hasOwnProperty(config.object_id)) {
add(entity.settings.homeassistant[config.object_id]);
if (entity.options.homeassistant.hasOwnProperty(config.object_id)) {
add(entity.options.homeassistant[config.object_id], false);
}
}
const topic = this.getDiscoveryTopic(config, entity);
this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0}, this.discoveryTopic, false, false);
this.discovered[discoverKey].topics.add(topic);
config.mockProperties?.forEach((property) => this.discovered[discoverKey].mockProperties.add(property));
this.discovered[discoverKey].objectIDs.add(config.object_id);
config.mockProperties?.forEach((mockProperty) =>
this.discovered[discoverKey].mockProperties.add(mockProperty));
});
}
@@ -1202,7 +1300,7 @@ export default class HomeAssistant extends Extension {
const discoveryMatch = data.topic.match(discoveryRegex);
const isDeviceAutomation = discoveryMatch && discoveryMatch[1] === 'device_automation';
if (discoveryMatch) {
// Clear outdated discovery configs and remember already discoverd device_automations
// Clear outdated discovery configs and remember already discovered device_automations
let message: KeyValue = null;
try {
message = JSON.parse(data.message);
@@ -1244,7 +1342,7 @@ export default class HomeAssistant extends Extension {
`${this.discoveryTopic}/${this.getDiscoveryTopic(c, entity)}` === data.topic);
}
// Device was flagged to be excluded from homeassistant discovery
clear = clear || (entity.settings.hasOwnProperty('homeassistant') && !entity.settings.homeassistant);
clear = clear || (entity.options.hasOwnProperty('homeassistant') && !entity.options.homeassistant);
if (clear) {
logger.debug(`Clearing Home Assistant config '${data.topic}'`);
@@ -1273,9 +1371,16 @@ export default class HomeAssistant extends Extension {
private getDevicePayload(entity: Device | Group): KeyValue {
const identifierPostfix = entity.isGroup() ?
`zigbee2mqtt_${this.getEncodedBaseTopic()}` : 'zigbee2mqtt';
// Allow device name to be overriden by homeassistant config
let deviceName = entity.name;
if (typeof entity.options.homeassistant?.name === 'string') {
deviceName = entity.options.homeassistant.name;
}
const payload: KeyValue = {
identifiers: [`${identifierPostfix}_${entity.settings.ID}`],
name: entity.name,
identifiers: [`${identifierPostfix}_${entity.options.ID}`],
name: deviceName,
sw_version: `Zigbee2MQTT ${this.zigbee2MQTTVersion}`,
};
@@ -1296,9 +1401,9 @@ export default class HomeAssistant extends Extension {
override adjustMessageBeforePublish(entity: Device | Group, message: KeyValue): void {
const discoverKey = this.getDiscoverKey(entity);
this.discovered[discoverKey]?.mockProperties?.forEach((property) => {
if (!message.hasOwnProperty(property)) {
message[property] = null;
this.discovered[discoverKey]?.mockProperties?.forEach((mockProperty) => {
if (!message.hasOwnProperty(mockProperty.property)) {
message[mockProperty.property] = mockProperty.value;
}
});
@@ -1323,8 +1428,8 @@ export default class HomeAssistant extends Extension {
}
private async publishDeviceTriggerDiscover(device: Device, key: string, value: string, force=false): Promise<void> {
const haConfig = device.settings.homeassistant;
if (device.settings.hasOwnProperty('homeassistant') && (haConfig == null ||
const haConfig = device.options.homeassistant;
if (device.options.hasOwnProperty('homeassistant') && (haConfig == null ||
(haConfig.hasOwnProperty('device_automation') && typeof haConfig === 'object' &&
haConfig.device_automation == null))) {
return;
@@ -1362,11 +1467,6 @@ export default class HomeAssistant extends Extension {
this.discoveredTriggers[device.ieeeAddr].add(discoveredKey);
}
// Only for homeassistant.test.js
_getMapping(): {[s: string]: DiscoveryEntry[]} {
return this.mapping;
}
_clearDiscoveredTrigger(): void {
this.discoveredTriggers = {};
}
+3 -3
View File
@@ -51,7 +51,7 @@ export default class BridgeLegacy extends Extension {
try {
const entity = settings.getDevice(message);
assert(entity, `Entity '${message}' does not exist`);
settings.whitelistDevice(entity.ID.toString());
settings.addDeviceToPasslist(entity.ID.toString());
logger.info(`Whitelisted '${entity.friendly_name}'`);
this.mqtt.publish(
'bridge/log',
@@ -217,7 +217,7 @@ export default class BridgeLegacy extends Extension {
logger.info(`Successfully renamed - ${from} to ${to} `);
const entity = this.zigbee.resolveEntity(to);
if (entity.isDevice()) {
this.eventBus.emitDeviceRenamed({homeAssisantRename: false, from, to, device: entity});
this.eventBus.emitEntityRenamed({homeAssisantRename: false, from, to, entity});
}
this.mqtt.publish(
@@ -336,7 +336,7 @@ export default class BridgeLegacy extends Extension {
}
if (action === 'ban') {
settings.banDevice(ieeeAddr);
settings.blockDevice(ieeeAddr);
}
}
+1 -1
View File
@@ -116,7 +116,7 @@ export default class Report extends Extension {
device.zh.meta.reporting = reportKey;
} else {
delete device.zh.meta.reporting;
this.eventBus.emitReportingDisabled({device});
this.eventBus.emitReconfigure({device});
}
this.eventBus.emitDevicesChanged();
+2 -1
View File
@@ -244,6 +244,7 @@ export default class NetworkMap extends Extension {
} catch (error) {
failed.get(device).push('lqi');
logger.error(`Failed to execute LQI for '${device.name}'`);
logger.debug(error.stack);
}
if (includeRoutes) {
@@ -267,7 +268,7 @@ export default class NetworkMap extends Extension {
model: device.definition.model,
vendor: device.definition.vendor,
description: device.definition.description,
supports: Array.from(new Set((device.definition.exposes).map((e) => {
supports: Array.from(new Set((device.exposes()).map((e) => {
return e.hasOwnProperty('name') ? e.name :
`${e.type} (${e.features.map((f) => f.name).join(', ')})`;
}))).join(', '),
+10 -2
View File
@@ -19,6 +19,13 @@ export default class OnEvent extends Extension {
(data) => this.callOnEvent(data.device, 'deviceAnnounce', this.convertData(data)));
this.eventBus.onDeviceNetworkAddressChanged(this,
(data) => this.callOnEvent(data.device, 'deviceNetworkAddressChanged', this.convertData(data)));
this.eventBus.onEntityOptionsChanged(this,
(data) => {
if (data.entity.isDevice()) {
this.callOnEvent(data.entity, 'deviceOptionsChanged', data)
.then(() => this.eventBus.emitDevicesChanged());
}
});
}
private convertData(data: KeyValue): KeyValue {
@@ -33,10 +40,11 @@ export default class OnEvent extends Extension {
}
private async callOnEvent(device: Device, type: string, data: KeyValue): Promise<void> {
zhc.onEvent(type, data, device.zh);
const state = this.state.get(device);
zhc.onEvent(type, data, device.zh, device.options, state);
if (device.definition?.onEvent) {
await device.definition.onEvent(type, data, device.zh, device.settings);
await device.definition.onEvent(type, data, device.zh, device.options, state);
}
}
}
+47 -24
View File
@@ -3,9 +3,24 @@ import logger from '../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import utils from '../util/utils';
import tradfriOTA from 'zigbee-herdsman-converters/lib/ota/tradfri';
import zigbeeOTA from 'zigbee-herdsman-converters/lib/ota/zigbeeOTA';
import Extension from './extension';
import bind from 'bind-decorator';
import Device from '../model/device';
import dataDir from '../util/data';
import * as URI from 'uri-js';
import path from 'path';
function isValidUrl(url: string): boolean {
let parsed;
try {
parsed = URI.parse(url);
} catch (_) {
// istanbul ignore next
return false;
}
return parsed.scheme === 'http' || parsed.scheme === 'https';
}
type UpdateState = 'updating' | 'idle' | 'available';
interface UpdatePayload {
@@ -24,36 +39,51 @@ export default class OTAUpdate extends Extension {
override async start(): Promise<void> {
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
if (settings.get().advanced.ikea_ota_use_test_url) {
if (settings.get().ota.ikea_ota_use_test_url) {
tradfriOTA.useTestURL();
}
// Let zigbeeOTA module know if the override index file is provided
let overrideOTAIndex = settings.get().ota.zigbee_ota_override_index_location;
if (overrideOTAIndex) {
// If the file name is not a full path, then treat it as a relative to the data directory
if (!isValidUrl(overrideOTAIndex) && !path.isAbsolute(overrideOTAIndex)) {
overrideOTAIndex = dataDir.joinPath(overrideOTAIndex);
}
zigbeeOTA.useIndexOverride(overrideOTAIndex);
}
// In order to support local firmware files we need to let zigbeeOTA know where the data directory is
zigbeeOTA.setDataDir(dataDir.getPath());
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state.
// remove them.
for (const device of this.zigbee.devices(false)) {
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state.
// remove them.
this.removeProgressAndRemainingFromState(device);
}
}
private removeProgressAndRemainingFromState(device: Device): void {
delete this.state.get(device)?.update?.progress;
delete this.state.get(device)?.update?.remaining;
delete this.state.get(device).update?.progress;
delete this.state.get(device).update?.remaining;
}
@bind private async onZigbeeEvent(data: eventdata.DeviceMessage): Promise<void> {
if (settings.get().ota.disable_automatic_update_check) return;
if (data.type !== 'commandQueryNextImageRequest' || !data.device.definition) return;
if (data.type !== 'commandQueryNextImageRequest' || !data.device.definition ||
this.inProgress.has(data.device.ieeeAddr)) return;
logger.debug(`Device '${data.device.name}' requested OTA`);
const automaticOTACheckDisabled = settings.get().ota.disable_automatic_update_check;
let supportsOTA = data.device.definition.hasOwnProperty('ota');
if (supportsOTA) {
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
// each time, so this interval can be set by the user. The default is 1,440 minutes (one day).
const updateCheckInterval = settings.get().ota.update_check_interval * 1000 * 60;
const check = this.lastChecked.hasOwnProperty(data.device.ieeeAddr) ?
(Date.now() - this.lastChecked[data.device.ieeeAddr]) > updateCheckInterval : true;
if (!check || this.inProgress.has(data.device.ieeeAddr)) return;
if (!check) return;
this.lastChecked[data.device.ieeeAddr] = Date.now();
let available = false;
@@ -92,18 +122,11 @@ export default class OTAUpdate extends Extension {
}
}
private async readSoftwareBuildIDAndDateCode(device: Device, update: boolean):
private async readSoftwareBuildIDAndDateCode(device: Device, sendWhen: 'active' | 'immediate'):
Promise<{softwareBuildID: string, dateCode: string}> {
try {
const endpoint = device.zh.endpoints.find((e) => e.supportsInputCluster('genBasic'));
const result = await endpoint.read('genBasic', ['dateCode', 'swBuildId']);
if (update) {
device.zh.softwareBuildID = result.swBuildId;
device.zh.dateCode = result.dateCode;
device.zh.save();
}
const result = await endpoint.read('genBasic', ['dateCode', 'swBuildId'], {sendWhen});
return {softwareBuildID: result.swBuildId, dateCode: result.dateCode};
} catch (e) {
return null;
@@ -230,16 +253,16 @@ export default class OTAUpdate extends Extension {
}
};
const from_ = await this.readSoftwareBuildIDAndDateCode(device, false);
const from_ = await this.readSoftwareBuildIDAndDateCode(device, 'immediate');
await device.definition.ota.updateToLatest(device.zh, logger, onProgress);
const to = await this.readSoftwareBuildIDAndDateCode(device, true);
const [fromS, toS] = [stringify(from_), stringify(to)];
const msg = `Finished update of '${device.name}'` +
(to ? `, from '${fromS}' to '${toS}'` : ``);
logger.info(msg);
logger.info(`Finished update of '${device.name}'`);
this.eventBus.emitReconfigure({device});
this.removeProgressAndRemainingFromState(device);
const payload = this.getEntityPublishPayload('idle');
this.publishEntityState(device, payload);
const to = await this.readSoftwareBuildIDAndDateCode(device, 'active');
const [fromS, toS] = [stringify(from_), stringify(to)];
logger.info(`Device '${device.name}' was updated from '${fromS}' to '${toS}'`);
responseData.from = from_ ? utils.toSnakeCase(from_) : null;
responseData.to = to ? utils.toSnakeCase(to) : null;
this.eventBus.emitDevicesChanged();
+9 -10
View File
@@ -10,7 +10,7 @@ import Device from '../model/device';
import bind from 'bind-decorator';
const topicRegex = new RegExp(`^(.+?)(?:/(${utils.endpointNames.join('|')}|\\d+))?/(get|set)(?:/(.+))?`);
const propertyEndpointRegex = new RegExp(`^(.*)_(${utils.endpointNames.join('|')}|\\d+)$`);
const propertyEndpointRegex = new RegExp(`^(.*?)_(${utils.endpointNames.join('|')})$`);
const stateValues = ['on', 'off', 'toggle', 'open', 'close', 'stop', 'lock', 'unlock'];
const sceneConverterKeys = ['scene_store', 'scene_add', 'scene_remove', 'scene_remove_all'];
@@ -91,7 +91,7 @@ export default class Publish extends Extension {
// Only do this when the retrieve_state option is enabled for this device.
// retrieve_state == decprecated
if (re instanceof Device && result && result.hasOwnProperty('readAfterWriteTime') &&
re.settings.retrieve_state
re.options.retrieve_state
) {
setTimeout(() => converter.convertGet(target, key, meta), result.readAfterWriteTime);
}
@@ -128,18 +128,14 @@ export default class Publish extends Extension {
// Get entity details
const definition = re instanceof Device ? re.definition : re.membersDefinitions();
if (definition == null) {
logger.error(`Cannot publish to unsupported device '${re.name}'`);
return;
}
const target = re instanceof Group ? re.zh : re.endpoint(parsedTopic.endpoint);
if (target == null) {
logger.error(`Device '${re.name}' has no endpoint '${parsedTopic.endpoint}'`);
return;
}
const device = re instanceof Device ? re.zh : null;
const entitySettings = re.settings;
const entityState = this.state.get(re) || {};
const entitySettings = re.options;
const entityState = this.state.get(re);
const membersState = re instanceof Group ?
Object.fromEntries(re.zh.members.map((e) => [e.getDevice().ieeeAddr,
this.state.get(this.zigbee.resolveEntity(e.getDevice().ieeeAddr))])) : null;
@@ -149,8 +145,11 @@ export default class Publish extends Extension {
const c = new Set(definition.map((d) => d.toZigbee).flat());
if (c.size == 0) converters = defaultGroupConverters;
else converters = Array.from(c);
} else {
} else if (definition) {
converters = definition.toZigbee;
} else {
converters = [zigbeeHerdsmanConverters.toZigbeeConverters.read,
zigbeeHerdsmanConverters.toZigbeeConverters.write];
}
}
@@ -216,7 +215,7 @@ export default class Publish extends Extension {
}
if (!converter) {
logger.error(`No converter available for '${key}' (${message[key]})`);
logger.error(`No converter available for '${key}' (${stringify(message[key])})`);
continue;
}
+6 -21
View File
@@ -91,23 +91,6 @@ export default class Receive extends Extension {
/* istanbul ignore next */
if (!data.device) return;
/**
* Handling of re-transmitted Xiaomi messages.
* https://github.com/Koenkk/zigbee2mqtt/issues/1238
* https://github.com/Koenkk/zigbee2mqtt/issues/3592
*
* Some Xiaomi router devices re-transmit messages from Xiaomi end devices.
* The network address of these message is set to the one of the Xiaomi router.
* Therefore it looks like if the message came from the Xiaomi router, while in
* fact it came from the end device.
* Handling these message would result in false state updates.
* The group ID attribute of these message defines the network address of the end device.
*/
if (data.device.isXiaomi() && data.device.zh.type === 'Router' && data.groupID) {
logger.debug('Handling re-transmitted Xiaomi message');
data = {...data, device: this.zigbee.deviceByNetworkAddress(data.groupID)};
}
if (!this.shouldProcess(data)) {
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
settings.get(), true, this.publishEntityState);
@@ -124,6 +107,8 @@ export default class Receive extends Extension {
if (converters.length == 0 && !ignoreClusters.includes(data.cluster)) {
logger.debug(`No converter available for '${data.device.definition.model}' with ` +
`cluster '${data.cluster}' and type '${data.type}' and data '${stringify(data.data)}'`);
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
settings.get(), true, this.publishEntityState);
return;
}
@@ -143,9 +128,9 @@ export default class Receive extends Extension {
}
// Check if we have to debounce
if (data.device.settings.debounce) {
this.publishDebounce(data.device, payload, data.device.settings.debounce,
data.device.settings.debounce_ignore);
if (data.device.options.debounce) {
this.publishDebounce(data.device, payload, data.device.options.debounce,
data.device.options.debounce_ignore);
} else {
this.publishEntityState(data.device, payload);
}
@@ -156,7 +141,7 @@ export default class Receive extends Extension {
for (const converter of converters) {
try {
const converted = await converter.convert(
data.device.definition, data, publish, data.device.settings, meta);
data.device.definition, data, publish, data.device.options, meta);
if (converted) {
payload = {...payload, ...converted};
}
+16 -10
View File
@@ -5,16 +5,20 @@ import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
export default class Device {
public zh: zh.Device;
private _definition: zhc.Definition;
private _definitionModelID: string;
get ieeeAddr(): string {return this.zh.ieeeAddr;}
get ID(): string {return this.zh.ieeeAddr;}
get settings(): DeviceSettings {return {...settings.get().device_options, ...settings.getDevice(this.ieeeAddr)};}
get options(): DeviceOptions {return {...settings.get().device_options, ...settings.getDevice(this.ieeeAddr)};}
get name(): string {
return this.zh.type === 'Coordinator' ? 'Coordinator' : this.settings?.friendly_name || this.ieeeAddr;
return this.zh.type === 'Coordinator' ? 'Coordinator' : this.options?.friendly_name || this.ieeeAddr;
}
get definition(): zhc.Definition {
if (!this._definition && !this.zh.interviewing) {
// Some devices can change modelID, reconsider the definition in that case.
// https://github.com/Koenkk/zigbee-herdsman-converters/issues/3016
if (!this.zh.interviewing && (!this._definition || this._definitionModelID !== this.zh.modelID)) {
this._definition = zigbeeHerdsmanConverters.findByDevice(this.zh);
this._definitionModelID = this.zh.modelID;
}
return this._definition;
}
@@ -23,6 +27,15 @@ export default class Device {
this.zh = device;
}
exposes(): zhc.DefinitionExpose[] {
/* istanbul ignore if */
if (typeof this.definition.exposes == 'function') {
return this.definition.exposes(this.zh, this.options);
} else {
return this.definition.exposes;
}
}
ensureInSettings(): void {
if (this.zh.type !== 'Coordinator' && !settings.getDevice(this.zh.ieeeAddr)) {
settings.addDevice(this.zh.ieeeAddr);
@@ -58,13 +71,6 @@ export default class Device {
return name === 'default' ? null : name;
}
isXiaomi(): boolean {
const xiaomiManufacturerID = [4151, 4447];
/* istanbul ignore next */
return this.zh.modelID !== 'lumi.router' && xiaomiManufacturerID.includes(this.zh.manufacturerID) &&
(!this.zh.manufacturerName || !this.zh.manufacturerName.startsWith('Trust'));
}
isIkeaTradfri(): boolean {return this.zh.manufacturerID === 4476;}
isDevice(): this is Device {return true;}
+13 -3
View File
@@ -4,13 +4,23 @@ import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
export default class Group {
public zh: zh.Group;
private resolveDevice: (ieeeAddr: string) => Device;
get ID(): number {return this.zh.groupID;}
get settings(): GroupSettings {return settings.getGroup(this.ID);}
get name(): string {return this.settings?.friendly_name || this.ID.toString();}
get options(): GroupOptions {return {...settings.getGroup(this.ID)};}
get name(): string {return this.options?.friendly_name || this.ID.toString();}
constructor(group: zh.Group) {
constructor(group: zh.Group, resolveDevice: (ieeeAddr: string) => Device) {
this.zh = group;
this.resolveDevice = resolveDevice;
}
hasMember(device: Device): boolean {
return !!device.zh.endpoints.find((e) => this.zh.members.includes(e));
}
membersDevices(): Device[] {
return this.zh.members.map((e) => this.resolveDevice(e.getDevice().ieeeAddr)).filter((d) => d);
}
membersDefinitions(): zhc.Definition[] {
+10 -3
View File
@@ -22,7 +22,7 @@ export default class MQTT {
const options: mqtt.IClientOptions = {
will: {
topic: `${settings.get().mqtt.base_topic}/bridge/state`,
payload: 'offline',
payload: utils.availabilityPayload('offline', settings.get()),
retain: settings.get().mqtt.force_disable_retain ? false : true,
qos: 1,
},
@@ -71,6 +71,7 @@ export default class MQTT {
this.client = mqtt.connect(mqttSettings.server, options);
// @ts-ignore https://github.com/Koenkk/zigbee2mqtt/issues/9822
this.client.stream.setMaxListeners(0);
this.eventBus.onPublishAvailability(this, () => this.publishStateOnline());
const onConnect = this.onConnect;
this.client.on('connect', async () => {
@@ -94,12 +95,18 @@ export default class MQTT {
logger.info('Connected to MQTT server');
this.subscribe(`${settings.get().mqtt.base_topic}/#`);
await this.publish('bridge/state', 'online', {retain: true, qos: 0});
await this.publishStateOnline();
}
async publishStateOnline(): Promise<void> {
await this.publish('bridge/state', utils.availabilityPayload('online', settings.get()), {retain: true, qos: 0});
}
async disconnect(): Promise<void> {
clearTimeout(this.connectionTimer);
await this.publish('bridge/state', 'offline', {retain: true, qos: 0});
await this.publish('bridge/state', utils.availabilityPayload('offline', settings.get()),
{retain: true, qos: 0});
this.eventBus.removeListeners(this);
logger.info('Disconnecting from MQTT server');
this.client.end();
}
+1 -1
View File
@@ -68,7 +68,7 @@ class State {
}
get(entity: Group | Device): KeyValue {
return this.state[entity.ID];
return this.state[entity.ID] || {};
}
set(entity: Group | Device, update: KeyValue, reason: string=null): KeyValue {
+50 -44
View File
@@ -114,9 +114,11 @@ declare global {
description: string
options: zhc.DefinitionExpose[],
vendor: string
exposes: DefinitionExpose[]
configure?: (device: zh.Device, coordinatorEndpoint: zh.Endpoint, logger: Logger) => Promise<void>;
onEvent?: (type: string, data: KeyValue, device: zh.Device, settings: KeyValue) => Promise<void>;
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<boolean>;
updateToLatest: (device: zh.Device, logger: Logger,
@@ -129,7 +131,7 @@ declare global {
}
namespace eventdata {
type DeviceRenamed = { device: Device, homeAssisantRename: boolean, from: string, to: string };
type EntityRenamed = { entity: Device | Group, homeAssisantRename: boolean, from: string, to: string };
type DeviceRemoved = { ieeeAddr: string, name: string };
type MQTTMessage = { topic: string, message: string };
type MQTTMessagePublished = { topic: string, payload: string, options: {retain: boolean, qos: number} };
@@ -142,7 +144,8 @@ declare global {
type DeviceAnnounce = { device: Device };
type DeviceInterview = { device: Device, status: 'started' | 'successful' | 'failed' };
type DeviceJoined = { device: Device };
type ReportingDisabled = { device: Device };
type EntityOptionsChanged = { entity: Device | Group, from: KeyValue, to: KeyValue };
type Reconfigure = { device: Device };
type DeviceLeave = { ieeeAddr: string, name: string };
type GroupMembersChanged = {group: Group, action: 'remove' | 'add' | 'remove_all',
endpoint: zh.Endpoint, skipDisableReporting: boolean };
@@ -163,24 +166,18 @@ declare global {
// Settings
// eslint-disable camelcase
interface Settings {
homeassistant?: boolean,
devices?: {[s: string]: DeviceSettings},
groups?: {[s: string]: GroupSettings},
passlist: string[],
blocklist: string[],
whitelist: string[],
ban: string[],
availability?: boolean | {
active?: {timeout?: number},
passive?: {timeout?: number}
homeassistant?: {
discovery_topic: string,
status_topic: string,
legacy_entity_attributes: boolean,
legacy_triggers: boolean,
},
permit_join: boolean,
frontend?: {
auth_token?: string,
host?: string,
port?: number,
url?: string,
permit_join?: boolean,
availability?: {
active: {timeout: number},
passive: {timeout: number}
},
external_converters: string[],
mqtt: {
base_topic: string,
include_device_information: boolean,
@@ -197,11 +194,14 @@ declare global {
reject_unauthorized?: boolean,
},
serial: {
disable_led?: boolean,
disable_led: boolean,
port?: string,
adapter?: 'deconz' | 'zstack' | 'ezsp' | 'zigate'
adapter?: 'deconz' | 'zstack' | 'ezsp' | 'zigate',
baudrate?: number,
rtscts?: boolean,
},
device_options: KeyValue,
passlist: string[],
blocklist: string[],
map_options: {
graphviz: {
colors: {
@@ -222,12 +222,24 @@ declare global {
},
},
},
experimental: {
output: 'json' | 'attribute' | 'attribute_and_json',
transmit_power?: number,
ota: {
update_check_interval: number,
disable_automatic_update_check: boolean,
zigbee_ota_override_index_location?: string,
ikea_ota_use_test_url?: boolean,
},
frontend?: {
auth_token?: string,
host?: string,
port?: number,
url?: string,
},
devices?: {[s: string]: DeviceOptions},
groups?: {[s: string]: GroupOptions},
device_options: KeyValue,
advanced: {
legacy_api: boolean,
legacy_availability_payload: boolean,
log_rotation: boolean,
log_symlink_current: boolean,
log_output: ('console' | 'file' | 'syslog')[],
@@ -235,7 +247,6 @@ declare global {
log_file: string,
log_level: 'debug' | 'info' | 'error' | 'warn',
log_syslog: KeyValue,
soft_reset_timeout: number,
pan_id: number | 'GENERATE',
ext_pan_id: number[],
channel: number,
@@ -247,30 +258,21 @@ declare global {
last_seen: 'disable' | 'ISO_8601' | 'ISO_8601_local' | 'epoch',
elapsed: boolean,
network_key: number[] | 'GENERATE',
report: boolean,
homeassistant_discovery_topic: string,
homeassistant_status_topic: string,
homeassistant_legacy_entity_attributes: boolean,
homeassistant_legacy_triggers: boolean,
timestamp_format: string,
baudrate?: number,
rtscts?: boolean,
ikea_ota_use_test_url?: boolean,
// below are deprecated
output: 'json' | 'attribute' | 'attribute_and_json',
transmit_power?: number,
// Everything below is deprecated
availability_timeout?: number,
availability_blocklist?: string[],
availability_passlist?: string[],
availability_blacklist?: string[],
availability_whitelist?: string[],
soft_reset_timeout: number,
report: boolean,
},
ota: {
update_check_interval: number,
disable_automatic_update_check: boolean
},
external_converters: string[],
}
interface DeviceSettings {
interface DeviceOptions {
ID?: string,
retention?: number,
availability?: boolean | {timeout: number},
@@ -284,9 +286,11 @@ declare global {
legacy?: boolean,
filtered_attributes?: string[],
friendly_name: string,
description?: string,
qos?: 0 | 1 | 2,
}
interface GroupSettings {
interface GroupOptions {
devices?: string[],
ID?: number,
optimistic?: boolean,
@@ -295,6 +299,8 @@ declare global {
homeassistant?: KeyValue,
filtered_attributes?: string[],
friendly_name: string,
description?: string,
qos?: 0 | 1 | 2,
}
}
+6 -1
View File
@@ -4,10 +4,15 @@ declare module 'zigbee-herdsman-converters' {
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): Promise<void>;
export function onEvent(type: string, data: KeyValue, device: zh.Device, settings: KeyValue,
state: KeyValue): Promise<void>;
}
declare module 'zigbee-herdsman-converters/lib/ota/tradfri' {
export function useTestURL(): void;
}
declare module 'zigbee-herdsman-converters/lib/ota/zigbeeOTA' {
export function useIndexOverride(indexFileName: string): void;
export function setDataDir(dataDir: string): void;
}
+120 -81
View File
@@ -9,40 +9,12 @@ import assert from 'assert';
const colorizer = winston.format.colorize();
// What transports to enable
const output = settings.get().advanced.log_output;
// Directory to log to
const timestamp = moment(Date.now()).format('YYYY-MM-DD.HH-mm-ss');
const directory = settings.get().advanced.log_directory.replace('%TIMESTAMP%', timestamp);
const logFilename = settings.get().advanced.log_file.replace('%TIMESTAMP%', timestamp);
// Make sure that log directoy exsists when not logging to stdout only
if (output.includes('file')) {
fx.mkdirSync(directory);
if (settings.get().advanced.log_symlink_current) {
const current = settings.get().advanced.log_directory.replace('%TIMESTAMP%', 'current');
const actual = './' + timestamp;
if (fs.existsSync(current)) {
fs.unlinkSync(current);
}
fs.symlinkSync(actual, current);
}
}
type Z2MLogLevel = 'warn' | 'debug' | 'info' | 'error';
type WinstonLogLevel = 'warning' | 'debug' | 'info' | 'error';
const z2mToWinstonLevel = (level: Z2MLogLevel): WinstonLogLevel => level === 'warn' ? 'warning' : level;
const winstonToZ2mLevel = (level: WinstonLogLevel): Z2MLogLevel => level === 'warning' ? 'warn' : level;
// Determine the log level.
const z2mLevel = settings.get().advanced.log_level;
const validLevels = ['info', 'error', 'warn', 'debug'];
assert(validLevels.includes(z2mLevel), `'${z2mLevel}' is not valid log_level, use one of '${validLevels.join(', ')}'`);
const level = z2mToWinstonLevel(z2mLevel);
const levelWithCompensatedLength: {[s: string]: string} = {
'info': 'info ',
'error': 'error',
@@ -50,69 +22,113 @@ const levelWithCompensatedLength: {[s: string]: string} = {
'debug': 'debug',
};
const timestampFormat = (): string => moment().format(settings.get().advanced.timestamp_format);
let logger: winston.Logger;
let fileTransport : winston.transport;
let output: string[];
let directory: string;
let logFilename: string;
let transportsToUse: winston.transport[];
// Setup default console logger
const transportsToUse: winston.transport[] = [
new winston.transports.Console({
function init(): void {
// What transports to enable
output = settings.get().advanced.log_output;
// Directory to log to
const timestamp = moment(Date.now()).format('YYYY-MM-DD.HH-mm-ss');
directory = settings.get().advanced.log_directory.replace('%TIMESTAMP%', timestamp);
logFilename = settings.get().advanced.log_file.replace('%TIMESTAMP%', timestamp);
// Make sure that log directoy exsists when not logging to stdout only
if (output.includes('file')) {
fx.mkdirSync(directory);
if (settings.get().advanced.log_symlink_current) {
const current = settings.get().advanced.log_directory.replace('%TIMESTAMP%', 'current');
const actual = './' + timestamp;
/* istanbul ignore next */
if (fs.existsSync(current)) {
fs.unlinkSync(current);
}
fs.symlinkSync(actual, current);
}
}
// Determine the log level.
const z2mLevel = settings.get().advanced.log_level;
const validLevels = ['info', 'error', 'warn', 'debug'];
assert(validLevels.includes(z2mLevel),
`'${z2mLevel}' is not valid log_level, use one of '${validLevels.join(', ')}'`);
const level = z2mToWinstonLevel(z2mLevel);
const timestampFormat = (): string => moment().format(settings.get().advanced.timestamp_format);
// Setup default console logger
transportsToUse = [
new winston.transports.Console({
level,
silent: !output.includes('console'),
format: winston.format.combine(
winston.format.timestamp({format: timestampFormat}),
winston.format.printf(/* istanbul ignore next */(info) => {
const {timestamp, level, message} = info;
const l = winstonToZ2mLevel(level as WinstonLogLevel);
const plainPrefix = `Zigbee2MQTT:${levelWithCompensatedLength[l]}`;
let prefix = plainPrefix;
if (process.stdout.isTTY) {
prefix = colorizer.colorize(l, plainPrefix);
}
return `${prefix} ${timestamp.split('.')[0]}: ${message}`;
}),
),
}),
];
// Add file logger when enabled
// NOTE: the initiation of the logger, even when not added as transport tries to create the logging directory
const transportFileOptions: KeyValue = {
filename: path.join(directory, logFilename),
json: false,
level,
silent: !output.includes('console'),
format: winston.format.combine(
winston.format.timestamp({format: timestampFormat}),
winston.format.printf(/* istanbul ignore next */(info) => {
const {timestamp, level, message} = info;
const l = winstonToZ2mLevel(level as WinstonLogLevel);
const prefix = colorizer.colorize(l, `Zigbee2MQTT:${levelWithCompensatedLength[l]}`);
return `${prefix} ${timestamp.split('.')[0]}: ${message}`;
return `${levelWithCompensatedLength[l]} ${timestamp.split('.')[0]}: ${message}`;
}),
),
}),
];
// Add file logger when enabled
// NOTE: the initiation of the logger, even when not added as transport tries to create the logging directory
const transportFileOptions: KeyValue = {
filename: path.join(directory, logFilename),
json: false,
level,
format: winston.format.combine(
winston.format.timestamp({format: timestampFormat}),
winston.format.printf(/* istanbul ignore next */(info) => {
const {timestamp, level, message} = info;
const l = winstonToZ2mLevel(level as WinstonLogLevel);
return `${levelWithCompensatedLength[l]} ${timestamp.split('.')[0]}: ${message}`;
}),
),
};
if (settings.get().advanced.log_rotation) {
transportFileOptions.tailable = true;
transportFileOptions.maxFiles = 3; // Keep last 3 files
transportFileOptions.maxsize = 10000000; // 10MB
}
if (output.includes('file')) {
transportsToUse.push(new winston.transports.File(transportFileOptions));
}
/* istanbul ignore next */
if (output.includes('syslog')) {
// eslint-disable-next-line
require('winston-syslog').Syslog;
const options: KeyValue = {
app_name: 'Zigbee2MQTT',
format: winston.format.printf(/* istanbul ignore next */(info) => {
return `${info.message}`;
}),
...settings.get().advanced.log_syslog,
};
if (options.hasOwnProperty('type')) options.type = options.type.toString();
// @ts-ignore
transportsToUse.push(new winston.transports.Syslog(options));
}
// Create logger
const logger = winston.createLogger({transports: transportsToUse, levels: winston.config.syslog.levels});
if (settings.get().advanced.log_rotation) {
transportFileOptions.tailable = true;
transportFileOptions.maxFiles = 3; // Keep last 3 files
transportFileOptions.maxsize = 10000000; // 10MB
}
if (output.includes('file')) {
fileTransport = new winston.transports.File(transportFileOptions);
transportsToUse.push(fileTransport);
}
/* istanbul ignore next */
if (output.includes('syslog')) {
// eslint-disable-next-line
require('winston-syslog').Syslog;
const options: KeyValue = {
app_name: 'Zigbee2MQTT',
format: winston.format.printf(/* istanbul ignore next */(info) => {
return `${info.message}`;
}),
...settings.get().advanced.log_syslog,
};
if (options.hasOwnProperty('type')) options.type = options.type.toString();
// @ts-ignore
transportsToUse.push(new winston.transports.Syslog(options));
}
logger = winston.createLogger({transports: transportsToUse, levels: winston.config.syslog.levels});
}
// Cleanup any old log directory.
function cleanup(): void {
@@ -181,6 +197,29 @@ function error(message: string): void {
logger.error(message);
}
// Workaround for https://github.com/winstonjs/winston/issues/1629.
// https://github.com/Koenkk/zigbee2mqtt/pull/10905
/* istanbul ignore next */
async function end(): Promise<void> {
logger.end();
await new Promise<void>((resolve) => {
if (!fileTransport) {
process.nextTick(resolve);
} else {
// @ts-ignore
if (fileTransport._dest) {
// @ts-ignore
fileTransport._dest.on('finish', resolve);
} else {
// @ts-ignore
fileTransport.on('open', () => fileTransport._dest.on('finish', resolve));
}
}
});
}
export default {
logOutput, warn, warning, error, info, debug, setLevel, getLevel, cleanup, addTransport, winston: logger,
init, logOutput, warn, warning, error, info, debug, setLevel, getLevel, cleanup, addTransport, end,
winston: (): winston.Logger => logger,
};
+475 -362
View File
@@ -1,14 +1,49 @@
{
"type": "object",
"properties": {
"device_options": {
"type": "object"
},
"homeassistant": {
"title": "Home Assistant integration",
"type": "boolean",
"requiresRestart": true,
"description": "Home Assistant integration (MQTT discovery)",
"default": false
"default": false,
"oneOf": [
{
"type": "boolean",
"title": "Home Assistant (simple)"
},
{
"type": "object",
"title": "Home Assistant (advanced)",
"properties": {
"legacy_triggers": {
"type": "boolean",
"title": "Home Assistant legacy triggers",
"description": "Home Assistant legacy triggers, when enabled Zigbee2mqt will send an empty 'action' or 'click' after one has been send. A 'sensor_action' and 'sensor_click' will be discoverd",
"default": true
},
"discovery_topic": {
"type": "string",
"title": "Homeassistant discovery topic",
"description": "Home Assistant discovery topic",
"requiresRestart": true,
"examples": ["homeassistant"]
},
"legacy_entity_attributes": {
"type": "boolean",
"title": "Home Assistant legacy entity attributes",
"description": "Home Assistant legacy entity attributes, when enabled Zigbee2MQTT will add state attributes to each entity, additional to the separate entities and devices it already creates",
"default": true
},
"status_topic": {
"type": "string",
"title": "Home Assistant status topic",
"description": "Home Assistant status topic",
"requiresRestart": true,
"examples": ["homeassistant/status"]
}
}
}
]
},
"permit_join": {
"type": "boolean",
@@ -16,25 +51,15 @@
"title": "Permit join",
"description": "Allow new devices to join (re-applied at restart)"
},
"external_converters": {
"type": "array",
"title": "External converters",
"description": "You can define external converters to e.g. add support for a DiY device",
"requiresRestart": true,
"items": {
"type": "string"
},
"examples": ["DIYRuZ_FreePad.js"]
},
"availability": {
"oneOf": [
{
"type": "boolean",
"title": "Availability (boolean)"
"title": "Availability (simple)"
},
{
"type": "object",
"title": "Availability (object)",
"title": "Availability (advanced)",
"properties": {
"active": {
"type": "object",
@@ -53,7 +78,7 @@
},
"passive": {
"type": "object",
"title": "Active",
"title": "Passive",
"requiresRestart": true,
"description": "Options for passive devices (mostly battery powered)",
"properties": {
@@ -73,6 +98,16 @@
"requiresRestart": true,
"description": "Checks whether devices are online/offline"
},
"external_converters": {
"type": "array",
"title": "External converters",
"description": "You can define external converters to e.g. add support for a DiY device",
"requiresRestart": true,
"items": {
"type": "string"
},
"examples": ["DIYRuZ_FreePad.js"]
},
"mqtt": {
"type": "object",
"title": "MQTT",
@@ -80,6 +115,7 @@
"base_topic": {
"type": "string",
"title": "Base topic",
"default": "zigbee2mqtt",
"requiresRestart": true,
"description": "MQTT base topic for Zigbee2MQTT MQTT messages",
"examples": ["zigbee2mqtt"]
@@ -169,7 +205,7 @@
"default": false
}
},
"required": ["base_topic", "server"]
"required": ["server"]
},
"serial": {
"type": "object",
@@ -196,6 +232,19 @@
"default": "auto",
"requiresRestart": true,
"description": "Adapter type, not needed unless you are experiencing problems"
},
"baudrate": {
"type": "number",
"title": "Baudrate",
"requiresRestart": true,
"description": "Baud rate speed for serial port, this can be anything firmware support but default is 115200 for Z-Stack and EZSP, 38400 for Deconz, however note that some EZSP firmware need 57600",
"examples": [38400, 57600, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS",
"requiresRestart": true,
"description": "RTS / CTS Hardware Flow Control for serial port"
}
}
},
@@ -217,331 +266,6 @@
"type": "string"
}
},
"whitelist": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Whitelist (deprecated, use passlist)",
"items": {
"type": "string"
}
},
"ban": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Ban (deprecated, use blocklist)",
"items": {
"type": "string"
}
},
"experimental": {
"type": "object",
"title": "Experimental",
"properties": {
"transmit_power": {
"type": ["number", "null"],
"title": "Transmit power",
"requiresRestart": true,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
},
"output": {
"type": "string",
"enum": ["attribute_and_json", "attribute", "json"],
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)"
}
}
},
"advanced": {
"type": "object",
"title": "Advanced",
"properties": {
"legacy_api": {
"type": "boolean",
"title": "Legacy API",
"requiresRestart": true,
"description": "Disables the legacy api (false = disable)",
"default": true
},
"pan_id": {
"oneOf": [{
"type": "string",
"title": "Pan ID (string)"
},
{
"type": "number",
"title": "Pan ID (number)"
}
],
"title": "Pan ID",
"requiresRestart": true,
"description": "ZigBee pan ID, changing requires repairing all devices!"
},
"ext_pan_id": {
"type": "array",
"items": {
"type": "number"
},
"title": "Ext Pan ID",
"requiresRestart": true,
"description": "Zigbee extended pan ID, changing requires repairing all devices!"
},
"channel": {
"type": "number",
"minimum": 11,
"maximum": 26,
"default": 11,
"title": "ZigBee channel",
"requiresRestart": true,
"description": "Zigbee channel, changing requires repairing all devices! (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)",
"examples": [15, 20, 25]
},
"cache_state": {
"type": "boolean",
"title": "Cache state",
"description": "MQTT message payload will contain all attributes, not only changed ones. Has to be true when integrating via Home Assistant",
"default": true
},
"cache_state_persistent": {
"type": "boolean",
"title": "Persist cache state",
"description": "Persist cached state, only used when cache_state: true",
"default": true
},
"cache_state_send_on_startup": {
"type": "boolean",
"title": "Send cached state on startup",
"description": "Send cached state on startup, only used when cache_state: true",
"default": true
},
"log_rotation": {
"type": "boolean",
"title": "Log rotation",
"requiresRestart": true,
"description": "Log rotation",
"default": true
},
"log_symlink_current": {
"type": "boolean",
"title": "Log symlink current",
"requiresRestart": true,
"description": "Create symlink to current logs in the log directory",
"default": false
},
"log_level": {
"type": "string",
"enum": ["info", "warn", "error", "debug"],
"title": "Log level",
"description": "Logging level",
"default": "info"
},
"log_output": {
"type": "array",
"requiresRestart": true,
"items": {
"type": "string",
"enum": ["console", "file", "syslog"]
},
"title": "Log output",
"description": "Output location of the log, leave empty to supress logging"
},
"log_directory": {
"type": "string",
"title": "Log directory",
"requiresRestart": true,
"description": "Location of log directory",
"examples": ["data/log/%TIMESTAMP%"]
},
"log_file": {
"type": "string",
"title": "Log file",
"requiresRestart": true,
"description": "Log file name, can also contain timestamp",
"examples": ["zigbee2mqtt_%TIMESTAMP%.log"],
"default": "log.txt"
},
"baudrate": {
"type": "number",
"title": "Baudrate",
"requiresRestart": true,
"description": "Baud rate speed for serial port, this can be anything firmware support but default is 115200 for Z-Stack and EZSP, 38400 for Deconz, however note that some EZSP firmware need 57600",
"examples": [38400, 57600, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS",
"requiresRestart": true,
"description": "RTS / CTS Hardware Flow Control for serial port"
},
"soft_reset_timeout": {
"type": "number",
"minimum": 0,
"requiresRestart": true,
"title": "Soft reset timeout (deprecated)",
"description": "Soft reset ZNP after timeout",
"readOnly": true
},
"network_key": {
"oneOf": [{
"type": "string",
"title": "Network key(string)"
},
{
"type": "array",
"items": {
"type": "number"
},
"title": "Network key(array)"
}
],
"title": "Network key",
"requiresRestart": true,
"description": "Network encryption key, changing requires repairing all devices!"
},
"last_seen": {
"type": "string",
"enum": ["disable", "ISO_8601", "ISO_8601_local", "epoch"],
"title": "Last seen",
"description": "Add a last_seen attribute to MQTT messages, contains date/time of last Zigbee message",
"default": "disable"
},
"elapsed": {
"type": "boolean",
"title": "Elapsed",
"description": "Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg",
"default": false
},
"report": {
"type": "boolean",
"title": "Reporting",
"requiresRestart": true,
"readOnly": true,
"description": "Enables report feature (deprecated)"
},
"homeassistant_discovery_topic": {
"type": "string",
"title": "Homeassistant discovery topic",
"description": "Home Assistant discovery topic",
"requiresRestart": true,
"examples": ["homeassistant"]
},
"homeassistant_legacy_entity_attributes": {
"type": "boolean",
"title": "Home Assistant legacy entity attributes",
"description": "Home Assistant legacy entity attributes, when enabled Zigbee2MQTT will add state attributes to each entity, additional to the separate entities and devices it already creates",
"default": true
},
"homeassistant_status_topic": {
"type": "string",
"title": "Home Assistant status topic",
"description": "Home Assistant status topic",
"requiresRestart": true,
"examples": ["homeassistant/status"]
},
"timestamp_format": {
"type": "string",
"title": "Timestamp format",
"requiresRestart": true,
"description": "Log timestamp format",
"examples": ["YYYY-MM-DD HH:mm:ss"]
},
"adapter_concurrent": {
"title": "Adapter concurrency",
"requiresRestart": true,
"type": ["number", "null"],
"description": "Adapter concurrency (e.g. 2 for CC2531 or 16 for CC26X2R1) (default: null, uses recommended value)"
},
"adapter_delay": {
"type": ["number", "null"],
"requiresRestart": true,
"title": "Adapter delay",
"description": "Adapter delay"
},
"ikea_ota_use_test_url": {
"type": "boolean",
"title": "IKEA TRADFRI OTA use test url",
"requiresRestart": true,
"description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation",
"default": false
},
"homeassistant_legacy_triggers": {
"type": "boolean",
"title": "Home Assistant legacy triggers",
"description": "Home Assistant legacy triggers, when enabled Zigbee2mqt will send an empty 'action' or 'click' after one has been send. A 'sensor_action' and 'sensor_click' will be discoverd",
"default": true
},
"log_syslog": {
"type": "object",
"title": "syslog",
"properties": {
"host": {
"type": "string",
"title": "Host",
"description": "The host running syslogd, defaults to localhost.",
"default": "localhost"
},
"port": {
"type": "number",
"title": "Port",
"description": "The port on the host that syslog is running on, defaults to syslogd's default port.",
"default": 123
},
"protocol": {
"type": "string",
"title": "Protocol",
"description": "The network protocol to log over (e.g. tcp4, udp4, tls4, unix, unix-connect, etc).",
"default": "tcp4",
"examples": [
"udp4",
"tls4",
"unix",
"unix-connect"
]
},
"path": {
"type": "string",
"title": "Path",
"description": "The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).",
"default": "/dev/log",
"examples": [
"/var/run/syslog"
]
},
"pid": {
"type": "string",
"title": "PID",
"description": "PID of the process that log messages are coming from (Default process.pid).",
"default": "process.pid"
},
"localhost": {
"type": "string",
"title": "Localhost",
"description": "Host to indicate that log messages are coming from (Default: localhost).",
"default": "localhost"
},
"type": {
"type": "string",
"title": "Type",
"description": "The type of the syslog protocol to use (Default: BSD, also valid: 5424).",
"default": "5424"
},
"app_name": {
"type": "string",
"title": "Localhost",
"description": "The name of the application (Default: Zigbee2MQTT).",
"default": "Zigbee2MQTT"
},
"eol": {
"type": "string",
"title": "eol",
"description": "The end of line character to be added to the end of the message (Default: Message without modifications).",
"default": "/n"
}
}
}
}
},
"map_options": {
"type": "object",
"title": "Networkmap",
@@ -612,9 +336,65 @@
"title": "Disable automatic update check",
"description": "Zigbee devices may request a firmware update, and do so frequently, causing Zigbee2MQTT to reach out to third party servers. If you disable these device initiated checks, you can still initiate a firmware update check manually.",
"default": false
},
"ikea_ota_use_test_url": {
"type": "boolean",
"title": "IKEA TRADFRI OTA use test url",
"requiresRestart": true,
"description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation",
"default": false
},
"zigbee_ota_override_index_location": {
"type": "string",
"title": "OTA index override file name",
"requiresRestart": true,
"description": "Location of override OTA index file",
"examples": ["index.json"]
}
}
},
"frontend": {
"oneOf": [
{
"type": "boolean",
"title": "Frontend (simple)"
},
{
"type": "object",
"title": "Frontend (advanced)",
"properties": {
"port": {
"type": "number",
"title": "Port",
"description": "Frontend binding port",
"default": 8080,
"requiresRestart": true
},
"host": {
"type": "string",
"title": "Bind host",
"description": "Frontend binding host",
"default": "0.0.0.0",
"requiresRestart": true
},
"auth_token": {
"type": ["string", "null"],
"title": "Auth token",
"description": "Enables authentication, disabled by default",
"requiresRestart": true
},
"url": {
"type": ["string", "null"],
"title": "URL",
"description": "URL on which the frontend can be reached, currently only used for the Home Assistant device configuration page",
"requiresRestart": true
}
}
}
],
"title": "Frontend",
"requiresRestart": true
},
"devices": {
"type": "object",
"propertyNames": {
@@ -637,31 +417,353 @@
}
}
},
"frontend": {
"device_options": {
"type": "object",
"title": "Frontend",
"title": "Options that are applied to all devices"
},
"advanced": {
"type": "object",
"title": "Advanced",
"properties": {
"port": {
"type": "number",
"title": "Port",
"description": "Frontend binding port",
"default": 8080,
"requiresRestart": true
"legacy_api": {
"type": "boolean",
"title": "Legacy API",
"requiresRestart": true,
"description": "Disables the legacy api (false = disable)",
"default": true
},
"host": {
"legacy_availability_payload": {
"type": "boolean",
"title": "Legacy availability payload",
"requiresRestart": true,
"description": "Payload to be used for device availabilty and bridge/state topics. true = text, false = JSON",
"default": true
},
"log_rotation": {
"type": "boolean",
"title": "Log rotation",
"requiresRestart": true,
"description": "Log rotation",
"default": true
},
"log_symlink_current": {
"type": "boolean",
"title": "Log symlink current",
"requiresRestart": true,
"description": "Create symlink to current logs in the log directory",
"default": false
},
"log_output": {
"type": "array",
"requiresRestart": true,
"items": {
"type": "string",
"enum": ["console", "file", "syslog"]
},
"title": "Log output",
"description": "Output location of the log, leave empty to suppress logging"
},
"log_directory": {
"type": "string",
"title": "Bind host",
"description": "Frontend binding host",
"default": "0.0.0.0",
"requiresRestart": true
"title": "Log directory",
"requiresRestart": true,
"description": "Location of log directory",
"examples": ["data/log/%TIMESTAMP%"]
},
"auth_token": {
"type": ["string", "null"],
"title": "Auth token",
"description": "Enables authentication, disabled by default",
"requiresRestart": true
"log_file": {
"type": "string",
"title": "Log file",
"requiresRestart": true,
"description": "Log file name, can also contain timestamp",
"examples": ["zigbee2mqtt_%TIMESTAMP%.log"],
"default": "log.txt"
},
"log_level": {
"type": "string",
"enum": ["info", "warn", "error", "debug"],
"title": "Log level",
"description": "Logging level",
"default": "info"
},
"log_syslog": {
"type": "object",
"title": "syslog",
"properties": {
"host": {
"type": "string",
"title": "Host",
"description": "The host running syslogd, defaults to localhost.",
"default": "localhost"
},
"port": {
"type": "number",
"title": "Port",
"description": "The port on the host that syslog is running on, defaults to syslogd's default port.",
"default": 123
},
"protocol": {
"type": "string",
"title": "Protocol",
"description": "The network protocol to log over (e.g. tcp4, udp4, tls4, unix, unix-connect, etc).",
"default": "tcp4",
"examples": [
"udp4",
"tls4",
"unix",
"unix-connect"
]
},
"path": {
"type": "string",
"title": "Path",
"description": "The path to the syslog dgram socket (i.e. /dev/log or /var/run/syslog for OS X).",
"default": "/dev/log",
"examples": [
"/var/run/syslog"
]
},
"pid": {
"type": "string",
"title": "PID",
"description": "PID of the process that log messages are coming from (Default process.pid).",
"default": "process.pid"
},
"localhost": {
"type": "string",
"title": "Localhost",
"description": "Host to indicate that log messages are coming from (Default: localhost).",
"default": "localhost"
},
"type": {
"type": "string",
"title": "Type",
"description": "The type of the syslog protocol to use (Default: BSD, also valid: 5424).",
"default": "5424"
},
"app_name": {
"type": "string",
"title": "Localhost",
"description": "The name of the application (Default: Zigbee2MQTT).",
"default": "Zigbee2MQTT"
},
"eol": {
"type": "string",
"title": "eol",
"description": "The end of line character to be added to the end of the message (Default: Message without modifications).",
"default": "/n"
}
}
},
"pan_id": {
"oneOf": [{
"type": "string",
"title": "Pan ID (string)"
},
{
"type": "number",
"title": "Pan ID (number)"
}
],
"title": "Pan ID",
"requiresRestart": true,
"description": "ZigBee pan ID, changing requires repairing all devices!"
},
"ext_pan_id": {
"type": "array",
"items": {
"type": "number"
},
"title": "Ext Pan ID",
"requiresRestart": true,
"description": "Zigbee extended pan ID, changing requires repairing all devices!"
},
"channel": {
"type": "number",
"minimum": 11,
"maximum": 26,
"default": 11,
"title": "ZigBee channel",
"requiresRestart": true,
"description": "Zigbee channel, changing requires repairing all devices! (Note: use a ZLL channel: 11, 15, 20, or 25 to avoid Problems)",
"examples": [15, 20, 25]
},
"adapter_concurrent": {
"title": "Adapter concurrency",
"requiresRestart": true,
"type": ["number", "null"],
"description": "Adapter concurrency (e.g. 2 for CC2531 or 16 for CC26X2R1) (default: null, uses recommended value)"
},
"adapter_delay": {
"type": ["number", "null"],
"requiresRestart": true,
"title": "Adapter delay",
"description": "Adapter delay"
},
"cache_state": {
"type": "boolean",
"title": "Cache state",
"description": "MQTT message payload will contain all attributes, not only changed ones. Has to be true when integrating via Home Assistant",
"default": true
},
"cache_state_persistent": {
"type": "boolean",
"title": "Persist cache state",
"description": "Persist cached state, only used when cache_state: true",
"default": true
},
"cache_state_send_on_startup": {
"type": "boolean",
"title": "Send cached state on startup",
"description": "Send cached state on startup, only used when cache_state: true",
"default": true
},
"last_seen": {
"type": "string",
"enum": ["disable", "ISO_8601", "ISO_8601_local", "epoch"],
"title": "Last seen",
"description": "Add a last_seen attribute to MQTT messages, contains date/time of last Zigbee message",
"default": "disable"
},
"elapsed": {
"type": "boolean",
"title": "Elapsed",
"description": "Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg",
"default": false
},
"network_key": {
"oneOf": [{
"type": "string",
"title": "Network key(string)"
},
{
"type": "array",
"items": {
"type": "number"
},
"title": "Network key(array)"
}
],
"title": "Network key",
"requiresRestart": true,
"description": "Network encryption key, changing requires repairing all devices!"
},
"timestamp_format": {
"type": "string",
"title": "Timestamp format",
"requiresRestart": true,
"description": "Log timestamp format",
"examples": ["YYYY-MM-DD HH:mm:ss"]
},
"transmit_power": {
"type": ["number", "null"],
"title": "Transmit power",
"requiresRestart": true,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
},
"output": {
"type": "string",
"enum": ["attribute_and_json", "attribute", "json"],
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)"
},
"homeassistant_discovery_topic": {
"type": "string",
"title": "Homeassistant discovery topic",
"description": "Home Assistant discovery topic",
"requiresRestart": true,
"examples": ["homeassistant"]
},
"homeassistant_legacy_entity_attributes": {
"type": "boolean",
"title": "Home Assistant legacy entity attributes",
"description": "Home Assistant legacy entity attributes, when enabled Zigbee2MQTT will add state attributes to each entity, additional to the separate entities and devices it already creates",
"default": true
},
"homeassistant_status_topic": {
"type": "string",
"title": "Home Assistant status topic",
"description": "Home Assistant status topic",
"requiresRestart": true,
"examples": ["homeassistant/status"]
},
"homeassistant_legacy_triggers": {
"type": "boolean",
"title": "Home Assistant legacy triggers",
"description": "Home Assistant legacy triggers, when enabled Zigbee2mqt will send an empty 'action' or 'click' after one has been send. A 'sensor_action' and 'sensor_click' will be discovered",
"default": true
},
"soft_reset_timeout": {
"type": "number",
"minimum": 0,
"requiresRestart": true,
"title": "Soft reset timeout (deprecated)",
"description": "Soft reset ZNP after timeout",
"readOnly": true
},
"report": {
"type": "boolean",
"title": "Reporting",
"requiresRestart": true,
"readOnly": true,
"description": "Enables report feature (deprecated)"
},
"baudrate": {
"type": "number",
"title": "Baudrate (deprecated)",
"requiresRestart": true,
"description": "Baud rate speed for serial port, this can be anything firmware support but default is 115200 for Z-Stack and EZSP, 38400 for Deconz, however note that some EZSP firmware need 57600",
"examples": [38400, 57600, 115200]
},
"rtscts": {
"type": "boolean",
"title": "RTS / CTS (deprecated)",
"requiresRestart": true,
"description": "RTS / CTS Hardware Flow Control for serial port"
},
"ikea_ota_use_test_url": {
"type": "boolean",
"title": "IKEA TRADFRI OTA use test url (deprecated)",
"requiresRestart": true,
"description": "Use IKEA TRADFRI OTA test server, see OTA updates documentation",
"default": false
}
}
},
"experimental": {
"type": "object",
"title": "Experimental (deprecated)",
"properties": {
"transmit_power": {
"type": ["number", "null"],
"title": "Transmit power",
"requiresRestart": true,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
},
"output": {
"type": "string",
"enum": ["attribute_and_json", "attribute", "json"],
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)"
}
}
},
"whitelist": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Whitelist (deprecated, use passlist)",
"items": {
"type": "string"
}
},
"ban": {
"readOnly": true,
"type": "array",
"requiresRestart": true,
"title": "Ban (deprecated, use blocklist)",
"items": {
"type": "string"
}
}
},
"required": ["mqtt"],
@@ -732,6 +834,17 @@
"type": "string",
"title": "Icon",
"description": "The user-defined device icon for the frontend. It can be a link to an image (not a path to a file) or base64 encoded data URL like: image/svg+xml;base64,PHN2ZyB3aW....R0aD"
},
"homeassistant": {
"type": ["object", "null"],
"title": "Home Assistant",
"properties": {
"name": {
"type": "string",
"title": "Home Assistant name",
"description": "Name of the device in Home Assistant"
}
}
}
},
"required": ["friendly_name"]
+145 -138
View File
@@ -5,33 +5,46 @@ import path from 'path';
import yaml from './yaml';
import Ajv from 'ajv';
import schemaJson from './settings.schema.json';
export const schema = schemaJson;
export let schema = schemaJson;
// @ts-ignore
schema = {};
objectAssignDeep(schema, schemaJson);
// Remove legacy settings from schema
{
delete schema.properties.advanced.properties.homeassistant_discovery_topic;
delete schema.properties.advanced.properties.homeassistant_legacy_entity_attributes;
delete schema.properties.advanced.properties.homeassistant_legacy_triggers;
delete schema.properties.advanced.properties.homeassistant_status_topic;
delete schema.properties.advanced.properties.soft_reset_timeout;
delete schema.properties.advanced.properties.report;
delete schema.properties.advanced.properties.baudrate;
delete schema.properties.advanced.properties.rtscts;
delete schema.properties.advanced.properties.ikea_ota_use_test_url;
delete schema.properties.experimental;
delete schemaJson.properties.whitelist;
delete schemaJson.properties.ban;
}
// DEPRECATED ZIGBEE2MQTT_CONFIG: https://github.com/Koenkk/zigbee2mqtt/issues/4697
const file = process.env.ZIGBEE2MQTT_CONFIG ?? data.joinPath('configuration.yaml');
const ajvSetting = new Ajv({allErrors: true}).addKeyword('requiresRestart').compile(schema);
const ajvSetting = new Ajv({allErrors: true}).addKeyword('requiresRestart').compile(schemaJson);
const ajvRestartRequired = new Ajv({allErrors: true})
.addKeyword({keyword: 'requiresRestart', validate: (schema: unknown) => !schema}).compile(schema);
.addKeyword({keyword: 'requiresRestart', validate: (s: unknown) => !s}).compile(schemaJson);
const defaults: RecursivePartial<Settings> = {
passlist: [],
blocklist: [],
// Deprecated: use block/passlist
whitelist: [],
ban: [],
permit_join: false,
external_converters: [],
mqtt: {
base_topic: 'zigbee2mqtt',
include_device_information: false,
/**
* Configurable force disable retain flag on mqtt publish.
* https://github.com/Koenkk/zigbee2mqtt/pull/4948
*/
force_disable_retain: false,
},
serial: {
disable_led: false,
},
device_options: {},
passlist: [],
blocklist: [],
map_options: {
graphviz: {
colors: {
@@ -52,12 +65,14 @@ const defaults: RecursivePartial<Settings> = {
},
},
},
experimental: {
// json or attribute or attribute_and_json
output: 'json',
ota: {
update_check_interval: 24 * 60,
disable_automatic_update_check: false,
},
device_options: {},
advanced: {
legacy_api: true,
legacy_availability_payload: true,
log_rotation: true,
log_symlink_current: false,
log_output: ['console', 'file'],
@@ -65,106 +80,113 @@ const defaults: RecursivePartial<Settings> = {
log_file: 'log.txt',
log_level: /* istanbul ignore next */ process.env.DEBUG ? 'debug' : 'info',
log_syslog: {},
soft_reset_timeout: 0,
pan_id: 0x1a62,
ext_pan_id: [0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD],
channel: 11,
adapter_concurrent: null,
adapter_delay: null,
// Availability timeout in seconds, disabled by default.
availability_blocklist: [],
availability_passlist: [],
// Deprecated, use block/passlist
availability_blacklist: [],
availability_whitelist: [],
/**
* Home Assistant requires ALL attributes to be present in ALL MQTT messages send by the device.
* https://community.home-assistant.io/t/missing-value-with-mqtt-only-last-data-set-is-shown/47070/9
*
* Therefore Zigbee2MQTT BY DEFAULT caches all values and resend it with every message.
* advanced.cache_state in configuration.yaml allows to configure this.
* https://www.zigbee2mqtt.io/guide/configuration/
*/
cache_state: true,
cache_state_persistent: true,
cache_state_send_on_startup: true,
/**
* Add a last_seen attribute to mqtt messages, contains date/time of zigbee message arrival
* "ISO_8601": ISO 8601 format
* "ISO_8601_local": Local ISO 8601 format (instead of UTC-based)
* "epoch": milliseconds elapsed since the UNIX epoch
* "disable": no last_seen attribute (default)
*/
last_seen: 'disable',
// Optional: Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg
elapsed: false,
/**
* https://github.com/Koenkk/zigbee2mqtt/issues/685#issuecomment-449112250
*
* Network key will serve as the encryption key of your network.
* Changing this will require you to repair your devices.
*/
network_key: [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13],
/**
* Enables reporting feature
*/
report: false,
/**
* Home Assistant discovery topic
*/
homeassistant_discovery_topic: 'homeassistant',
/**
* Home Assistant status topic
*/
homeassistant_status_topic: 'hass/status',
/**
* Home Assistant legacy entity attributes, when enabled:
* Zigbee2MQTT will send additional states as attributes with each entity.
* For example, A temperature & humidity sensor will have 2 entities for
* the temperature and humidity, with this setting enabled both entities
* will also have an temperature and humidity attribute.
*/
homeassistant_legacy_entity_attributes: true,
/**
* Home Assistant legacy triggers, when enabled:
* - Zigbee2mqt will send an empty 'action' or 'click' after one has been send
* - A 'sensor_action' and 'sensor_click' will be discoverd
*/
homeassistant_legacy_triggers: true,
/**
* Configurable timestampFormat
* https://github.com/Koenkk/zigbee2mqtt/commit/44db557a0c83f419d66755d14e460cd78bd6204e
*/
timestamp_format: 'YYYY-MM-DD HH:mm:ss',
output: 'json',
// Everything below is deprecated
availability_blocklist: [],
availability_passlist: [],
availability_blacklist: [],
availability_whitelist: [],
soft_reset_timeout: 0,
report: false,
},
ota: {
/**
* Minimal time delta in minutes between polling third party server for potential firmware updates
*/
update_check_interval: 24 * 60,
/**
* Completely disallow Zigbee devices to initiate a search for a potential firmware update.
* If set to true, only a user-initiated update search will be possible.
*/
disable_automatic_update_check: false,
},
external_converters: [],
};
let _settings: Partial<Settings>;
let _settingsWithDefaults: Settings;
function loadSettingsWithDefaults(): void {
_settingsWithDefaults = objectAssignDeep({}, defaults, getInternalSettings()) as Settings;
if (!_settingsWithDefaults.devices) {
_settingsWithDefaults.devices = {};
}
if (!_settingsWithDefaults.groups) {
_settingsWithDefaults.groups = {};
}
if (_settingsWithDefaults.homeassistant) {
const defaults = {discovery_topic: 'homeassistant', status_topic: 'hass/status',
legacy_entity_attributes: true, legacy_triggers: true};
const sLegacy = {};
if (_settingsWithDefaults.advanced) {
for (const key of ['homeassistant_legacy_triggers', 'homeassistant_discovery_topic',
'homeassistant_legacy_entity_attributes', 'homeassistant_status_topic']) {
// @ts-ignore
if (_settingsWithDefaults.advanced[key] !== undefined) {
// @ts-ignore
sLegacy[key.replace('homeassistant_', '')] = _settingsWithDefaults.advanced[key];
}
}
}
const s = typeof _settingsWithDefaults.homeassistant === 'object' ? _settingsWithDefaults.homeassistant : {};
// @ts-ignore
_settingsWithDefaults.homeassistant = {};
objectAssignDeep(_settingsWithDefaults.homeassistant, defaults, sLegacy, s);
}
if (_settingsWithDefaults.availability || _settingsWithDefaults.advanced?.availability_timeout) {
const defaults = {};
const s = typeof _settingsWithDefaults.availability === 'object' ? _settingsWithDefaults.availability : {};
// @ts-ignore
_settingsWithDefaults.availability = {};
objectAssignDeep(_settingsWithDefaults.availability, defaults, s);
}
if (_settingsWithDefaults.frontend) {
const defaults = {port: 8080, auth_token: false, host: '0.0.0.0'};
const s = typeof _settingsWithDefaults.frontend === 'object' ? _settingsWithDefaults.frontend : {};
// @ts-ignore
_settingsWithDefaults.frontend = {};
objectAssignDeep(_settingsWithDefaults.frontend, defaults, s);
}
if (_settings.advanced?.hasOwnProperty('baudrate') && _settings.serial?.baudrate == null) {
// @ts-ignore
_settingsWithDefaults.serial.baudrate = _settings.advanced.baudrate;
}
if (_settings.advanced?.hasOwnProperty('rtscts') && _settings.serial?.rtscts == null) {
// @ts-ignore
_settingsWithDefaults.serial.rtscts = _settings.advanced.rtscts;
}
if (_settings.advanced?.hasOwnProperty('ikea_ota_use_test_url') && _settings.ota?.ikea_ota_use_test_url == null) {
// @ts-ignore
_settingsWithDefaults.ota.ikea_ota_use_test_url = _settings.advanced.ikea_ota_use_test_url;
}
// @ts-ignore
if (_settings.experimental?.hasOwnProperty('transmit_power') && _settings.advanced?.transmit_power == null) {
// @ts-ignore
_settingsWithDefaults.advanced.transmit_power = _settings.experimental.transmit_power;
}
// @ts-ignore
if (_settings.experimental?.hasOwnProperty('output') && _settings.advanced?.output == null) {
// @ts-ignore
_settingsWithDefaults.advanced.output = _settings.experimental.output;
}
// @ts-ignore
_settingsWithDefaults.ban && _settingsWithDefaults.blocklist.push(..._settingsWithDefaults.ban);
// @ts-ignore
_settingsWithDefaults.whitelist && _settingsWithDefaults.passlist.push(..._settingsWithDefaults.whitelist);
}
function write(): void {
const settings = getInternalSettings();
const toWrite: KeyValue = objectAssignDeep({}, settings);
@@ -213,7 +235,7 @@ function write(): void {
yaml.writeIfChanged(file, toWrite);
_settings = read();
_settingsWithDefaults = objectAssignDeep({}, defaults, getInternalSettings()) as Settings;
loadSettingsWithDefaults();
}
export function validate(): string[] {
@@ -247,15 +269,18 @@ export function validate(): string[] {
// Verify that all friendly names are unique
const names: string[] = [];
const check = (name: string): void => {
if (names.includes(name)) errors.push(`Duplicate friendly_name '${name}' found`);
errors.push(...utils.validateFriendlyName(name));
names.push(name);
const check = (e: DeviceOptions | GroupOptions): void => {
if (names.includes(e.friendly_name)) errors.push(`Duplicate friendly_name '${e.friendly_name}' found`);
errors.push(...utils.validateFriendlyName(e.friendly_name));
names.push(e.friendly_name);
if (e.qos != null && ![0, 1, 2].includes(e.qos)) {
errors.push(`QOS for '${e.friendly_name}' not valid, should be 0, 1 or 2 got ${e.qos}`);
}
};
const settingsWithDefaults = get();
Object.values(settingsWithDefaults.devices).forEach((d) => check(d.friendly_name));
Object.values(settingsWithDefaults.groups).forEach((g) => check(g.friendly_name));
Object.values(settingsWithDefaults.devices).forEach((d) => check(d));
Object.values(settingsWithDefaults.groups).forEach((g) => check(g));
if (settingsWithDefaults.mqtt.version !== 5) {
for (const device of Object.values(settingsWithDefaults.devices)) {
@@ -373,7 +398,7 @@ function applyEnvironmentVariables(settings: Partial<Settings>): void {
}
});
};
iterate(schema.properties, []);
iterate(schemaJson.properties, []);
}
function getInternalSettings(): Partial<Settings> {
@@ -387,15 +412,7 @@ function getInternalSettings(): Partial<Settings> {
export function get(): Settings {
if (!_settingsWithDefaults) {
_settingsWithDefaults = objectAssignDeep({}, defaults, getInternalSettings()) as Settings;
}
if (!_settingsWithDefaults.devices) {
_settingsWithDefaults.devices = {};
}
if (!_settingsWithDefaults.groups) {
_settingsWithDefaults.groups = {};
loadSettingsWithDefaults();
}
return _settingsWithDefaults;
@@ -440,7 +457,7 @@ export function apply(newSettings: Record<string, unknown>): boolean {
return restartRequired;
}
export function getGroup(IDorName: string | number): GroupSettings {
export function getGroup(IDorName: string | number): GroupOptions {
const settings = get();
const byID = settings.groups[IDorName];
if (byID) {
@@ -456,14 +473,14 @@ export function getGroup(IDorName: string | number): GroupSettings {
return null;
}
export function getGroups(): GroupSettings[] {
export function getGroups(): GroupOptions[] {
const settings = get();
return Object.entries(settings.groups).map(([ID, group]) => {
return {devices: [], ...group, ID: Number(ID)};
});
}
function getGroupThrowIfNotExists(IDorName: string): GroupSettings {
function getGroupThrowIfNotExists(IDorName: string): GroupOptions {
const group = getGroup(IDorName);
if (!group) {
throw new Error(`Group '${IDorName}' does not exist`);
@@ -472,7 +489,7 @@ function getGroupThrowIfNotExists(IDorName: string): GroupSettings {
return group;
}
export function getDevice(IDorName: string): DeviceSettings {
export function getDevice(IDorName: string): DeviceOptions {
const settings = get();
const byID = settings.devices[IDorName];
if (byID) {
@@ -488,7 +505,7 @@ export function getDevice(IDorName: string): DeviceSettings {
return null;
}
function getDeviceThrowIfNotExists(IDorName: string): DeviceSettings {
function getDeviceThrowIfNotExists(IDorName: string): DeviceOptions {
const device = getDevice(IDorName);
if (!device) {
throw new Error(`Device '${IDorName}' does not exist`);
@@ -497,7 +514,7 @@ function getDeviceThrowIfNotExists(IDorName: string): DeviceSettings {
return device;
}
export function addDevice(ID: string): DeviceSettings {
export function addDevice(ID: string): DeviceOptions {
if (getDevice(ID)) {
throw new Error(`Device '${ID}' already exists`);
}
@@ -513,17 +530,17 @@ export function addDevice(ID: string): DeviceSettings {
return getDevice(ID);
}
export function whitelistDevice(ID: string): void {
export function addDeviceToPasslist(ID: string): void {
const settings = getInternalSettings();
if (!settings.whitelist) {
settings.whitelist = [];
if (!settings.passlist) {
settings.passlist = [];
}
if (settings.whitelist.includes(ID)) {
throw new Error(`Device '${ID}' already whitelisted`);
if (settings.passlist.includes(ID)) {
throw new Error(`Device '${ID}' already in passlist`);
}
settings.whitelist.push(ID);
settings.passlist.push(ID);
write();
}
@@ -537,16 +554,6 @@ export function blockDevice(ID: string): void {
write();
}
export function banDevice(ID: string): void {
const settings = getInternalSettings();
if (!settings.ban) {
settings.ban = [];
}
settings.ban.push(ID);
write();
}
export function removeDevice(IDorName: string): void {
const device = getDeviceThrowIfNotExists(IDorName);
const settings = getInternalSettings();
@@ -564,7 +571,7 @@ export function removeDevice(IDorName: string): void {
write();
}
export function addGroup(name: string, ID?: string): GroupSettings {
export function addGroup(name: string, ID?: string): GroupOptions {
utils.validateFriendlyName(name, true);
if (getGroup(name) || getDevice(name)) {
throw new Error(`friendly_name '${name}' is already in use`);
+40 -8
View File
@@ -38,7 +38,7 @@ const endpointNames = [
'button_11', 'button_12', 'button_13', 'button_14', 'button_15',
'button_16', 'button_17', 'button_18', 'button_19', 'button_20',
'button_light', 'button_fan_high', 'button_fan_med', 'button_fan_low',
'heat', 'cool', 'water', 'meter', 'wifi',
'heat', 'cool', 'water', 'meter', 'wifi', 'no_occupancy_since',
];
function capitalize(s: string): string {
@@ -210,6 +210,30 @@ function toSnakeCase(value: string | KeyValue): any {
}
}
function charRange(start: string, stop: string): number[] {
const result = [];
for (let idx=start.charCodeAt(0), end=stop.charCodeAt(0); idx <=end; ++idx) {
result.push(idx);
}
return result;
}
const controlCharacters = [
...charRange('\u0000', '\u001F'),
...charRange('\u007f', '\u009F'),
...charRange('\ufdd0', '\ufdef'),
];
function containsControlCharacter(str: string): boolean {
for (let i = 0; i < str.length; i++) {
const ch = str.charCodeAt(i);
if (controlCharacters.includes(ch) || [0xFFFE, 0xFFFF].includes(ch & 0xFFFF)) {
return true;
}
}
return false;
}
function validateFriendlyName(name: string, throwFirstError=false): string[] {
const errors = [];
for (const endpointName of endpointNames) {
@@ -220,7 +244,7 @@ function validateFriendlyName(name: string, throwFirstError=false): string[] {
if (name.length === 0) errors.push(`friendly_name must be at least 1 char long`);
if (name.endsWith('/') || name.startsWith('/')) errors.push(`friendly_name is not allowed to end or start with /`);
if (name.endsWith(String.fromCharCode(0))) errors.push(`friendly_name is not allowed to contain null char`);
if (containsControlCharacter(name)) errors.push(`friendly_name is not allowed to contain control char`);
if (endpointNames.includes(name)) errors.push(`Following friendly_name are not allowed: '${endpointNames}'`);
if (name.match(/.*\/\d*$/)) errors.push(`Friendly name cannot end with a "/DIGIT" ('${name}')`);
if (name.includes('#') || name.includes('+')) {
@@ -245,9 +269,13 @@ function sanitizeImageParameter(parameter: string): string {
return sanitized;
}
function isAvailabilityEnabledForDevice(device: Device, settings: Settings): boolean {
if (device.settings.hasOwnProperty('availability')) {
return !!device.settings.availability;
function isAvailabilityEnabledForEntity(entity: Device | Group, settings: Settings): boolean {
if (entity.isGroup()) {
return !entity.membersDevices().map((d) => isAvailabilityEnabledForEntity(d, settings)).includes(false);
}
if (entity.options.hasOwnProperty('availability')) {
return !!entity.options.availability;
}
// availability_timeout = deprecated
@@ -256,11 +284,11 @@ function isAvailabilityEnabledForDevice(device: Device, settings: Settings): boo
const passlist = settings.advanced.availability_passlist.concat(settings.advanced.availability_whitelist);
if (passlist.length > 0) {
return passlist.includes(device.name) || passlist.includes(device.ieeeAddr);
return passlist.includes(entity.name) || passlist.includes(entity.ieeeAddr);
}
const blocklist = settings.advanced.availability_blacklist.concat(settings.advanced.availability_blocklist);
return !blocklist.includes(device.name) && !blocklist.includes(device.ieeeAddr);
return !blocklist.includes(entity.name) && !blocklist.includes(entity.ieeeAddr);
}
const entityIDRegex = new RegExp(`^(.+?)(?:/(${endpointNames.join('|')}|\\d+))?$`);
@@ -277,6 +305,10 @@ function isZHGroup(obj: unknown): obj is zh.Group {
return obj.constructor.name.toLowerCase() === 'group';
}
function availabilityPayload(state: 'online' | 'offline', settings: Settings): string {
return settings.advanced.legacy_availability_payload ? state : JSON.stringify({state});
}
const hours = (hours: number): number => 1000 * 60 * 60 * hours;
const minutes = (minutes: number): number => 1000 * 60 * minutes;
const seconds = (seconds: number): number => 1000 * seconds;
@@ -302,5 +334,5 @@ export default {
equalsPartial, getObjectProperty, getResponse, parseJSON, loadModuleFromText, loadModuleFromFile,
getExternalConvertersDefinitions, removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase,
parseEntityID, isEndpoint, isZHGroup, hours, minutes, seconds, validateFriendlyName, sleep,
sanitizeImageParameter, isAvailabilityEnabledForDevice, publishLastSeen,
sanitizeImageParameter, isAvailabilityEnabledForEntity, publishLastSeen, availabilityPayload,
};
+12 -10
View File
@@ -36,8 +36,8 @@ export default class Zigbee {
databaseBackupPath: data.joinPath('database.db.backup'),
backupPath: data.joinPath('coordinator_backup.json'),
serialPort: {
baudRate: settings.get().advanced.baudrate,
rtscts: settings.get().advanced.rtscts,
baudRate: settings.get().serial.baudrate,
rtscts: settings.get().serial.rtscts,
path: settings.get().serial.port,
adapter: settings.get().serial.adapter,
},
@@ -80,12 +80,14 @@ export default class Zigbee {
});
this.herdsman.on('deviceInterview', (data: ZHEvents.DeviceInterviewPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
/* 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) => {
const device = this.resolveDevice(data.device.ieeeAddr);
/* istanbul ignore if */ if (!device) return; // Prevent potential race
logger.info(`Device '${device.name}' joined`);
this.eventBus.emitDeviceJoined({device});
});
@@ -110,8 +112,8 @@ export default class Zigbee {
for (const device of this.devices(false)) {
// If a passlist is used, all other device will be removed from the network.
const passlist = settings.get().passlist.concat(settings.get().whitelist);
const blocklist = settings.get().blocklist.concat(settings.get().ban);
const passlist = settings.get().passlist;
const blocklist = settings.get().blocklist;
const remove = async (device: Device): Promise<void> => {
try {
await device.zh.removeFromNetwork();
@@ -131,8 +133,8 @@ export default class Zigbee {
}
// Check if we have to set a transmit power
if (settings.get().experimental.hasOwnProperty('transmit_power')) {
const transmitPower = settings.get().experimental.transmit_power;
if (settings.get().advanced.hasOwnProperty('transmit_power')) {
const transmitPower = settings.get().advanced.transmit_power;
await this.herdsman.setTransmitPower(transmitPower);
logger.info(`Set transmit power to '${transmitPower}'`);
}
@@ -217,7 +219,7 @@ export default class Zigbee {
}
}
private resolveDevice(ieeeAddr: string): Device {
@bind private resolveDevice(ieeeAddr: string): Device {
if (!this.deviceLookup[ieeeAddr]) {
const device = this.herdsman.getDeviceByIeeeAddr(ieeeAddr);
device && (this.deviceLookup[ieeeAddr] = new Device(device));
@@ -233,7 +235,7 @@ export default class Zigbee {
private resolveGroup(groupID: number): Group {
const group = this.herdsman.getGroupByID(Number(groupID));
if (group && !this.groupLookup[groupID]) {
this.groupLookup[groupID] = new Group(group);
this.groupLookup[groupID] = new Group(group, this.resolveDevice);
}
return this.groupLookup[groupID];
@@ -273,8 +275,8 @@ export default class Zigbee {
@bind private async acceptJoiningDeviceHandler(ieeeAddr: string): Promise<boolean> {
// If passlist is set, all devices not on passlist will be rejected to join the network
const passlist = settings.get().passlist.concat(settings.get().whitelist);
const blocklist = settings.get().blocklist.concat(settings.get().ban);
const passlist = settings.get().passlist;
const blocklist = settings.get().blocklist;
if (passlist.length > 0) {
if (passlist.includes(ieeeAddr)) {
logger.info(`Accepting joining device which is on passlist '${ieeeAddr}'`);
+12231 -18185
View File
File diff suppressed because it is too large Load Diff
+33 -31
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "1.22.1",
"version": "1.25.2",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"repository": {
@@ -8,7 +8,7 @@
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
},
"engines": {
"node": "^10 || ^12 || ^14 || ^15 || ^16"
"node": "^14 || ^16 || ^17 || ^18"
},
"keywords": [
"xiaomi",
@@ -35,54 +35,56 @@
},
"homepage": "https://koenkk.github.io/zigbee2mqtt",
"dependencies": {
"ajv": "^8.8.2",
"ajv": "^8.11.0",
"bind-decorator": "^1.0.11",
"connect-gzip-static": "^2.1.1",
"core-js": "^3.19.1",
"connect-gzip-static": "2.1.1",
"core-js": "^3.22.7",
"debounce": "^1.2.1",
"deep-object-diff": "^1.1.0",
"deep-object-diff": "^1.1.7",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.1.2",
"finalhandler": "^1.2.0",
"git-last-commit": "^1.0.1",
"humanize-duration": "^3.27.0",
"humanize-duration": "^3.27.2",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"mkdir-recursive": "^0.4.0",
"moment": "^2.29.1",
"mqtt": "4.2.8",
"moment": "^2.29.3",
"mqtt": "4.3.7",
"object-assign-deep": "^0.4.0",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"semver": "^7.3.7",
"source-map-support": "^0.5.21",
"winston": "^3.3.3",
"winston-syslog": "^2.4.4",
"ws": "^8.3.0",
"zigbee-herdsman": "0.13.176",
"zigbee-herdsman-converters": "14.0.336",
"zigbee2mqtt-frontend": "0.6.46"
"uri-js": "^4.4.1",
"winston": "^3.7.2",
"winston-syslog": "^2.5.0",
"winston-transport": "^4.5.0",
"ws": "^8.7.0",
"zigbee-herdsman": "0.14.34",
"zigbee-herdsman-converters": "14.0.533",
"zigbee2mqtt-frontend": "0.6.97"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/plugin-proposal-decorators": "^7.16.4",
"@babel/preset-env": "^7.16.4",
"@babel/preset-typescript": "^7.16.0",
"@babel/core": "^7.18.2",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@types/debounce": "^1.2.1",
"@types/finalhandler": "^1.1.1",
"@types/humanize-duration": "^3.27.0",
"@types/jest": "^27.0.3",
"@types/humanize-duration": "^3.27.1",
"@types/jest": "^27.5.1",
"@types/js-yaml": "^4.0.5",
"@types/object-assign-deep": "^0.4.0",
"@types/rimraf": "^3.0.2",
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"babel-jest": "^27.3.1",
"eslint": "^8.3.0",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.26.0",
"babel-jest": "^28.1.0",
"eslint": "^8.16.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-jest": "^25.3.0",
"jest": "^27.3.1",
"eslint-plugin-jest": "^26.4.2",
"jest": "^28.1.0",
"tmp": "^0.2.1",
"typescript": "^4.5.2"
"typescript": "^4.7.2"
},
"jest": {
"coverageThreshold": {
+1
View File
@@ -6,5 +6,6 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
for (const line of text.split('\n')) {
const model = zigbeeHerdsmanConverters.devices.find((d) => d.model === line);
if (!model) throw new Error(`${line} does not exist`);
console.log(`- \`${line}\` ${model.vendor} ${model.description}`);
}
+44 -5
View File
@@ -227,9 +227,23 @@ describe('Availability', () => {
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(0);
});
it('Should to enable availabilty for just one device', async () => {
settings.set(['availability'], false);
settings.set(['devices', devices.bulb_color.ieeeAddr, 'availability'], true);
await resetExtension();
MQTT.publish.mockClear();
await advancedTime(utils.minutes(11));
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(1);
});
it('Should retrieve device state when it reconnects', async () => {
MQTT.publish.mockClear();
const device = controller.zigbee.resolveEntity(devices.bulb_color.ieeeAddr);
controller.state.set(device, {state: 'OFF'})
const endpoint = devices.bulb_color.getEndpoint(1);
endpoint.read.mockClear();
@@ -242,13 +256,9 @@ describe('Availability', () => {
expect(endpoint.read).toHaveBeenCalledTimes(0);
await advancedTime(utils.seconds(2));
expect(endpoint.read).toHaveBeenCalledTimes(3);
expect(endpoint.read).toHaveBeenCalledTimes(1);
expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']);
expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']);
expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl',
['colorMode', 'currentX', 'currentY', 'enhancedCurrentHue', 'currentSaturation', 'colorTemperature']);
// Should stop when one request fails
endpoint.read.mockClear();
await zigbeeHerdsman.events.deviceAnnounce({device: devices.bulb_color});
await flushPromises();
@@ -263,6 +273,8 @@ describe('Availability', () => {
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb_color', to: 'bulb_new_name'}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability',
null, {retain: true, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb_new_name/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
await advancedTime(utils.hours(12));
@@ -270,6 +282,16 @@ describe('Availability', () => {
'offline', {retain: true, qos: 0}, expect.any(Function));
});
it('Should publish availabiltiy payload in JSON format', async () => {
settings.set(['advanced', 'legacy_availability_payload'], false);
await resetExtension();
MQTT.publish.mockClear();
await advancedTime(utils.hours(26));
expect(devices.remote.ping).toHaveBeenCalledTimes(0);
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/remote/availability',
stringify({state: 'offline'}), {retain: true, qos: 0}, expect.any(Function));
});
it('Deprecated - should allow to block via advanced.availability_blocklist', async () => {
settings.set(['advanced', 'availability_blocklist'], [devices.bulb_color.ieeeAddr]);
await resetExtension();
@@ -296,4 +318,21 @@ describe('Availability', () => {
await advancedTime(utils.minutes(12));
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(1);
});
it('Should publish availabilty for groups', async () => {
settings.set(['devices', devices.bulb_color_2.ieeeAddr, 'availability'], true);
await resetExtension();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
await advancedTime(utils.minutes(12));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'offline', {retain: true, qos: 0}, expect.any(Function));
MQTT.publish.mockClear();
devices.bulb_color_2.lastSeen = Date.now();
await zigbeeHerdsman.events.lastSeenChanged({device: devices.bulb_color_2});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
});
});
+18 -18
View File
File diff suppressed because one or more lines are too long
+4 -16
View File
@@ -100,12 +100,12 @@ describe('Configure', () => {
expectBulbConfigured();
});
it('Should reconfigure reporting on reportingDisabled event', async () => {
it('Should reconfigure reporting on reconfigure event', async () => {
expectBulbConfigured();
const device = controller.zigbee.resolveEntity(zigbeeHerdsman.devices.bulb);
mockClear(device.zh);
expectBulbNotConfigured();
controller.eventBus.emitReportingDisabled({device});
controller.eventBus.emitReconfigure({device});
await flushPromises();
expectBulbConfigured();
});
@@ -192,19 +192,7 @@ describe('Configure', () => {
expect(logger.warn).toHaveBeenCalledWith(`Skipping configure of 'ZNLDP12LM', device does not require this.`)
});
it('Should not configure when interviewing', async () => {
const device = zigbeeHerdsman.devices.remote;
delete device.meta.configured;
device.interviewing = true;
const endpoint = device.getEndpoint(1);
mockClear(device);
await zigbeeHerdsman.events.lastSeenChanged({device});
await flushPromises();
expectRemoteNotConfigured();
device.interviewing = false;
});
it('Should configure when not interviewCompleted', async () => {
it('Should not configure when interview not completed', async () => {
const device = zigbeeHerdsman.devices.remote;
delete device.meta.configured;
device.interviewCompleted = false;
@@ -212,7 +200,7 @@ describe('Configure', () => {
mockClear(device);
await zigbeeHerdsman.events.lastSeenChanged({device});
await flushPromises();
expectRemoteConfigured();
expectRemoteNotConfigured();
device.interviewCompleted = true;
});
+13 -26
View File
@@ -28,9 +28,10 @@ describe('Controller', () => {
MQTT.restoreOnMock();
zigbeeHerdsman.returnDevices.splice(0);
mockExit = jest.fn();
data.writeDefaultConfiguration();
settings.reRead();
controller = new Controller(jest.fn(), mockExit);
mocksClear.forEach((m) => m.mockClear());
data.writeDefaultConfiguration();
settings.reRead();
data.writeDefaultState();
});
@@ -180,14 +181,6 @@ describe('Controller', () => {
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(1);
});
it('Should remove non whitelisted devices on startup', async () => {
settings.set(['whitelist'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]);
await controller.start();
await flushPromises();
expect(zigbeeHerdsman.devices.bulb_color.removeFromNetwork).toHaveBeenCalledTimes(0);
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(1);
});
it('Should remove device on blocklist on startup', async () => {
settings.set(['blocklist'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]);
await controller.start();
@@ -196,14 +189,6 @@ describe('Controller', () => {
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(0);
});
it('Should remove banned devices on startup', async () => {
settings.set(['ban'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]);
await controller.start();
await flushPromises();
expect(zigbeeHerdsman.devices.bulb_color.removeFromNetwork).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(0);
});
it('Start controller fails', async () => {
zigbeeHerdsman.start.mockImplementationOnce(() => {throw new Error('failed')});
await controller.start();
@@ -218,7 +203,7 @@ describe('Controller', () => {
await flushPromises();
expect(logger.error).toHaveBeenCalledWith('MQTT failed to connect: addr not found');
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(1);
expect(mockExit).toHaveBeenCalledWith(1, false);
});
it('Start controller with permit join true', async () => {
@@ -235,13 +220,13 @@ describe('Controller', () => {
expect(zigbeeHerdsman.setTransmitPower).toHaveBeenCalledWith(14);
});
it('Start controller and stop', async () => {
it('Start controller and stop with restart', async () => {
await controller.start();
await controller.stop();
await controller.stop(true);
expect(MQTT.end).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(0);
expect(mockExit).toHaveBeenCalledWith(0, true);
});
it('Start controller and stop', async () => {
@@ -251,7 +236,7 @@ describe('Controller', () => {
expect(MQTT.end).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(1);
expect(mockExit).toHaveBeenCalledWith(1, false);
});
it('Start controller adapter disconnects', async () => {
@@ -262,7 +247,7 @@ describe('Controller', () => {
expect(MQTT.end).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsman.stop).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockExit).toHaveBeenCalledWith(1);
expect(mockExit).toHaveBeenCalledWith(1, false);
});
it('Handle mqtt message', async () => {
@@ -584,7 +569,7 @@ describe('Controller', () => {
logger.error.mockClear();
controller.state.file = "/";
await controller.state.save();
expect(logger.error).toHaveBeenCalledWith(`Failed to write state to '/' (EISDIR: illegal operation on a directory, open '/')`);
expect(logger.error).toHaveBeenCalledWith(expect.stringMatching(/Failed to write state to \'\/\'/));
});
it('Publish should not cache when set', async () => {
@@ -630,11 +615,13 @@ describe('Controller', () => {
});
it('Should disable legacy options on new network start', async () => {
expect(settings.get().advanced.homeassistant_legacy_entity_attributes).toBeTruthy();
settings.set(['homeassistant'], true);
settings.reRead();
expect(settings.get().homeassistant.legacy_entity_attributes).toBeTruthy();
expect(settings.get().advanced.legacy_api).toBeTruthy();
zigbeeHerdsman.start.mockReturnValueOnce('reset');
await controller.start();
expect(settings.get().advanced.homeassistant_legacy_entity_attributes).toBeFalsy();
expect(settings.get().homeassistant.legacy_entity_attributes).toBeFalsy();
expect(settings.get().advanced.legacy_api).toBeFalsy();
});
+2 -2
View File
@@ -140,7 +140,7 @@ describe('Frontend', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', linkquality: null, update_available: null}),
stringify({state: 'ON', linkquality: null, update_available: null, update: {state: null}}),
{ retain: false, qos: 0 },
expect.any(Function)
);
@@ -151,7 +151,7 @@ describe('Frontend', () => {
// Received message on socket
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(1);
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', linkquality: null, update_available: null}}));
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', linkquality: null, update_available: null, update: {state: null}}}));
// Shouldnt set when not ready
mockWSClient.implementation.send.mockClear();
+218 -58
View File
@@ -24,6 +24,7 @@ describe('HomeAssistant extension', () => {
beforeEach(async () => {
data.writeDefaultConfiguration();
settings.reRead();
settings.set(['homeassistant'], true);
data.writeEmptyState();
controller.state.load();
await resetExtension();
@@ -49,14 +50,22 @@ describe('HomeAssistant extension', () => {
it('Should not have duplicate type/object_ids in a mapping', () => {
const duplicated = [];
require('zigbee-herdsman-converters').devices.forEach((d) => {
const mapping = extension._getMapping()[d.model];
const exposes = typeof d.exposes == 'function' ? d.exposes() : d.exposes;
const device = {definition: d, isDevice: () => true, options: {}, exposes: () => exposes};
const configs = extension.getConfigs(device);
const cfg_type_object_ids = [];
mapping.forEach((c) => {
if (cfg_type_object_ids.includes(c['type'] + '/' + c['object_id'])) {
duplicated.push(d.model);
configs.forEach((c) => {
const id = c['type'] + '/' + c['object_id'];
if (cfg_type_object_ids.includes(id)) {
if (typeof d.exposes == 'function') {
// A dynamic function must exposes all possible attributes for the docs
console.warn(`${d.model} dynamic exposes contains duplicated ${id}`)
} else {
duplicated.push(d.model);
}
} else {
cfg_type_object_ids.push(c['type'] + '/' + c['object_id']);
cfg_type_object_ids.push(id);
}
});
});
@@ -472,7 +481,7 @@ describe('HomeAssistant extension', () => {
device: {
manufacturer: 'Not from Xiaomi',
model: 'custom model',
}
},
},
friendly_name: 'weather_sensor',
retain: false,
@@ -541,6 +550,75 @@ describe('HomeAssistant extension', () => {
);
});
it('Should discover devices with overriden name', async () => {
settings.set(['devices', '0x0017880104e45522'], {
homeassistant: {
name: "Weather Sensor",
},
friendly_name: 'weather_sensor',
retain: false,
})
await resetExtension();
let payload;
await flushPromises();
payload = {
'unit_of_measurement': '°C',
'device_class': 'temperature',
'state_class': 'measurement',
'value_template': '{{ value_json.temperature }}',
'state_topic': 'zigbee2mqtt/weather_sensor',
'json_attributes_topic': 'zigbee2mqtt/weather_sensor',
'name': 'Weather Sensor temperature',
'unique_id': '0x0017880104e45522_temperature_zigbee2mqtt',
'device': {
'identifiers': ['zigbee2mqtt_0x0017880104e45522'],
'name': 'Weather Sensor',
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'enabled_by_default': true,
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/temperature/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
payload = {
'unit_of_measurement': '%',
'device_class': 'humidity',
'state_class': 'measurement',
'value_template': '{{ value_json.humidity }}',
'state_topic': 'zigbee2mqtt/weather_sensor',
'json_attributes_topic': 'zigbee2mqtt/weather_sensor',
'name': 'Weather Sensor humidity',
'unique_id': '0x0017880104e45522_humidity_zigbee2mqtt',
'enabled_by_default': true,
'device': {
'identifiers': ['zigbee2mqtt_0x0017880104e45522'],
'name': 'Weather Sensor',
'sw_version': null,
'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)',
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/sensor/0x0017880104e45522/humidity/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Should discover devices with overriden user configuration affecting type and object_id', async () => {
settings.set(['devices', '0x0017880104e45541'], {
friendly_name: 'my_switch',
@@ -552,8 +630,7 @@ describe('HomeAssistant extension', () => {
light: {
type: 'this should be ignored',
name: 'my_light_name_override'
}
},
},
})
@@ -668,29 +745,35 @@ describe('HomeAssistant extension', () => {
let payload;
payload = {
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
"action_template":"{% set values = {None:None,'idle':'off','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
"action_topic":"zigbee2mqtt/TS0601_thermostat",
"availability":[
{
"topic":"zigbee2mqtt/bridge/state"
}
],
"away_mode_command_topic":"zigbee2mqtt/TS0601_thermostat/set/away_mode",
"away_mode_state_template":"{{ value_json.away_mode }}",
"away_mode_state_topic":"zigbee2mqtt/TS0601_thermostat",
"current_temperature_template":"{{ value_json.local_temperature }}",
"current_temperature_topic":"zigbee2mqtt/TS0601_thermostat",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017882104a44559"
],
"manufacturer":"TuYa",
"model":"Radiator valve with thermostat (TS0601_thermostat)",
"name":"TS0601_thermostat",
"sw_version": null,
"identifiers":[
"zigbee2mqtt_0x0017882104a44559"
],
"manufacturer":"TuYa",
"model":"Radiator valve with thermostat (TS0601_thermostat)",
"name":"TS0601_thermostat",
"sw_version":null
},
"hold_command_topic":"zigbee2mqtt/TS0601_thermostat/set/preset",
"hold_modes":[
"schedule",
"manual",
"boost",
"complex",
"comfort",
"eco"
"schedule",
"manual",
"boost",
"complex",
"comfort",
"eco"
],
"hold_state_template":"{{ value_json.preset }}",
"hold_state_topic":"zigbee2mqtt/TS0601_thermostat",
@@ -701,7 +784,9 @@ describe('HomeAssistant extension', () => {
"mode_state_template":"{{ value_json.system_mode }}",
"mode_state_topic":"zigbee2mqtt/TS0601_thermostat",
"modes":[
"heat", "auto", "off"
"heat",
"auto",
"off"
],
"name":"TS0601_thermostat",
"temp_step":0.5,
@@ -827,7 +912,7 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({"color":{"hue": 0, "saturation": 100, "h": 0, "s": 100}, "color_mode": "hs", "linkquality": null, "state": null, "update_available": null}),
stringify({"color":{"hue": 0, "saturation": 100, "h": 0, "s": 100}, "color_mode": "hs", "linkquality": null, "state": null, "update_available": null, "update": {"state": null}}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -843,7 +928,7 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({"color": {"x": 0.4576,"y": 0.41}, "color_mode": "xy", "linkquality": null,"state": null, "update_available": null}),
stringify({"color": {"x": 0.4576,"y": 0.41}, "color_mode": "xy", "linkquality": null,"state": null, "update_available": null, "update": {"state": null}}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -859,7 +944,7 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({"linkquality": null,"state": "ON", "update_available": null}),
stringify({"linkquality": null,"state": "ON", "update_available": null, "update": {"state": null}}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -921,6 +1006,19 @@ describe('HomeAssistant extension', () => {
await flushPromises();
});
it('Should discover when options change', async () => {
const device = controller.zigbee.resolveEntity(zigbeeHerdsman.devices.bulb);
MQTT.publish.mockClear();
controller.eventBus.emitEntityOptionsChanged({entity: device, from: {}, to: {'test': 123}});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
`homeassistant/light/${device.ID}/light/config`,
expect.any(String),
expect.any(Object),
expect.any(Function),
);
});
it('Should send all status when home assistant comes online (default topic)', async () => {
data.writeDefaultState();
extension.state.load();
@@ -934,13 +1032,13 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null}),
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null, "update": {"state": null}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/remote',
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null}),
stringify({"action":null,"action_duration":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null, "update": {"state": null}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -958,13 +1056,13 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null}),
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null, "update": {"state": null}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/remote',
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null}),
stringify({"action":null,"action_duration":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null, "update": {"state": null}}),
{ retain: true, qos: 0 },
expect.any(Function)
);
@@ -1138,6 +1236,50 @@ describe('HomeAssistant extension', () => {
);
});
it('Should refresh discovery when group is renamed', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/rename', stringify({"from": "ha_discovery_group", "to": "ha_discovery_group_new","homeassistant_rename":true}));
await flushPromises();
const payload = {
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"brightness":true,
"brightness_scale":254,
"color_mode":true,
"command_topic":"zigbee2mqtt/ha_discovery_group_new/set",
"device":{
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group_new",
"sw_version": version,
},
"json_attributes_topic":"zigbee2mqtt/ha_discovery_group_new",
"max_mireds": 454,
"min_mireds": 250,
"name":"ha_discovery_group_new",
"schema":"json",
"state_topic":"zigbee2mqtt/ha_discovery_group_new",
"supported_color_modes":[
"xy",
"color_temp"
],
"unique_id":"9_light_zigbee2mqtt"
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/1221051039810110150109113116116_9/light/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/1221051039810110150109113116116_9/light/config',
null,
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Shouldnt refresh discovery when device is renamed and homeassistant_rename is false', async () => {
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "weather_sensor", "to": "weather_sensor_renamed","homeassistant_rename":false}));
@@ -1212,7 +1354,7 @@ describe('HomeAssistant extension', () => {
it('Should discover trigger when click is published', async () => {
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(5);
expect(discovered.length).toBe(7);
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/click/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/action/config');
@@ -1287,21 +1429,21 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({action: "single", click: "single", battery: null, linkquality: null, voltage: null}),
stringify({action: "single", click: "single", battery: null, linkquality: null, voltage: null, power_outage_count: null, device_temperature: null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({action: "", battery: null, linkquality: null, voltage: null, click: null}),
stringify({action: "", battery: null, linkquality: null, voltage: null, click: null, power_outage_count: null, device_temperature: null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({click: "", action: null, battery: null, linkquality: null, voltage: null}),
stringify({click: "", action: null, battery: null, linkquality: null, voltage: null, power_outage_count: null, device_temperature: null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -1388,7 +1530,7 @@ describe('HomeAssistant extension', () => {
await resetExtension();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(4);
expect(discovered.length).toBe(6);
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/action/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/battery/config');
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/linkquality/config');
@@ -1399,7 +1541,7 @@ describe('HomeAssistant extension', () => {
await resetExtension();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(3);
expect(discovered.length).toBe(5);
expect(discovered).not.toContain('homeassistant/sensor/0x0017880104e45520/click/config');
expect(discovered).not.toContain('homeassistant/sensor/0x0017880104e45520/action/config');
@@ -1444,7 +1586,7 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button',
stringify({action: "single", "battery":null,"linkquality":null,"voltage":null}),
stringify({action: "single", "battery":null,"linkquality":null,"voltage":null, "power_outage_count": null, "device_temperature": null}),
{ retain: false, qos: 0 },
expect.any(Function),
);
@@ -1481,33 +1623,15 @@ describe('HomeAssistant extension', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(4);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single', click: null, battery: null, linkquality: null, voltage: null});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single', click: null, battery: null, linkquality: null, voltage: null, power_outage_count: null, device_temperature: null});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/button');
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({action: '', click: null, battery: null, linkquality: null, voltage: null});
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({action: '', click: null, battery: null, linkquality: null, voltage: null, power_outage_count: null, device_temperature: null});
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('homeassistant/device_automation/0x0017880104e45520/action_single/config');
expect(MQTT.publish.mock.calls[3][0]).toStrictEqual('zigbee2mqtt/button/action');
});
it('Load Home Assistant mapping from external converters', async () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
await resetExtension();
const homeassistantSwitch = {
type: 'switch',
object_id: 'switch',
discovery_payload: {
payload_off: 'OFF',
payload_on: 'ON',
value_template: '{{ value_json.state }}',
command_topic: true,
},
};
expect(extension._getMapping()['external_converters_device_1']).toEqual([homeassistantSwitch]);
});
it('Should clear outdated configs', async () => {
// Non-existing group -> clear
MQTT.publish.mockClear();
@@ -1665,8 +1789,44 @@ describe('HomeAssistant extension', () => {
);
});
it('Should discover with json availability payload value_template', async () => {
settings.set(['advanced', 'legacy_availability_payload'], false);
await resetExtension();
const payload = {
"availability":[{"topic":"zigbee2mqtt/bridge/state","value_template":'{{ value_json.state }}'}],
"brightness":true,
"brightness_scale":254,
"color_mode":true,
"command_topic":"zigbee2mqtt/ha_discovery_group/set",
"device":{
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
"name":"ha_discovery_group",
"sw_version": version,
},
"max_mireds": 454,
"min_mireds": 250,
"json_attributes_topic":"zigbee2mqtt/ha_discovery_group",
"name":"ha_discovery_group",
"schema":"json",
"state_topic":"zigbee2mqtt/ha_discovery_group",
"supported_color_modes":[
"xy",
"color_temp"
],
"unique_id":"9_light_zigbee2mqtt"
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/light/1221051039810110150109113116116_9/light/config',
stringify(payload),
{ retain: true, qos: 0 },
expect.any(Function),
);
});
it('Should discover last_seen when enabled', async () => {
settings.set(['advanced', 'last_seen'], ' epoch');
settings.set(['advanced', 'last_seen'], 'ISO_8601');
await resetExtension();
const payload = {
+11 -11
View File
@@ -56,7 +56,7 @@ describe('Bridge legacy', () => {
it('Should allow whitelist', async () => {
const bulb_color = zigbeeHerdsman.devices.bulb_color;
const bulb = zigbeeHerdsman.devices.bulb;
expect(settings.get().whitelist).toStrictEqual([]);
expect(settings.get().passlist).toStrictEqual([]);
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/whitelist', 'bulb_color');
await flushPromises();
@@ -68,7 +68,7 @@ describe('Bridge legacy', () => {
);
MQTT.publish.mockClear()
expect(settings.get().whitelist).toStrictEqual([bulb_color.ieeeAddr]);
expect(settings.get().passlist).toStrictEqual([bulb_color.ieeeAddr]);
MQTT.events.message('zigbee2mqtt/bridge/config/whitelist', 'bulb');
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -79,10 +79,10 @@ describe('Bridge legacy', () => {
);
MQTT.publish.mockClear()
expect(settings.get().whitelist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
expect(settings.get().passlist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
MQTT.events.message('zigbee2mqtt/bridge/config/whitelist', 'bulb');
await flushPromises();
expect(settings.get().whitelist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
expect(settings.get().passlist).toStrictEqual([bulb_color.ieeeAddr, bulb.ieeeAddr]);
expect(MQTT.publish).toHaveBeenCalledTimes(0);
});
@@ -366,7 +366,7 @@ describe('Bridge legacy', () => {
controller.state.state = {'0x000b57fffec6a5b3': {brightness: 100}};
const device = zigbeeHerdsman.devices.bulb_color;
device.removeFromNetwork.mockClear();
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/remove', 'bulb_color');
@@ -381,14 +381,14 @@ describe('Bridge legacy', () => {
expect.any(Function)
);
expect(controller.state.state).toStrictEqual({});
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
});
it('Should allow to force remove device', async () => {
controller.state.state = {'0x000b57fffec6a5b3': {brightness: 100}};
const device = zigbeeHerdsman.devices.bulb_color;
device.removeFromDatabase.mockClear();
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/force_remove', 'bulb_color');
@@ -403,13 +403,13 @@ describe('Bridge legacy', () => {
expect.any(Function)
);
expect(controller.state.state).toStrictEqual({});
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
});
it('Should allow to ban device', async () => {
it('Should allow to block device', async () => {
const device = zigbeeHerdsman.devices.bulb_color;
device.removeFromNetwork.mockClear();
expect(settings.get().ban.length).toBe(0);
expect(settings.get().blocklist.length).toBe(0);
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/ban', 'bulb_color');
@@ -423,7 +423,7 @@ describe('Bridge legacy', () => {
{qos: 0, retain: false},
expect.any(Function)
);
expect(settings.get().ban).toStrictEqual(['0x000b57fffec6a5b3']);
expect(settings.get().blocklist).toStrictEqual(['0x000b57fffec6a5b3']);
});
it('Shouldnt crash when removing non-existing device', async () => {
+26 -13
View File
@@ -25,6 +25,7 @@ describe('Logger', () => {
it('Create log directory', () => {
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
const dirs = fs.readdirSync(dir.name);
expect(dirs.length).toBe(1);
@@ -32,6 +33,7 @@ describe('Logger', () => {
it('Should cleanup', () => {
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
for (const d of fs.readdirSync(dir.name)) {
@@ -49,6 +51,7 @@ describe('Logger', () => {
it('Should not cleanup when there is no timestamp set', () => {
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
for (let i = 30; i < 40; i++) {
fs.mkdirSync(path.join(dir.name, `log_${i}`));
@@ -62,6 +65,7 @@ describe('Logger', () => {
it('Set and get log level', () => {
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
logger.setLevel('debug');
expect(logger.getLevel()).toBe('debug');
@@ -74,23 +78,26 @@ describe('Logger', () => {
}
const logger = require('../lib/util/logger').default;
expect(logger.winston.transports.length).toBe(2);
logger.init();
expect(logger.winston().transports.length).toBe(2);
logger.addTransport(new DummyTransport());
expect(logger.winston.transports.length).toBe(3);
expect(logger.winston().transports.length).toBe(3);
});
it('Set and get log level warn <-> warning', () => {
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
logger.setLevel('warn');
expect(logger.winston.transports[0].level).toBe('warning');
expect(logger.winston().transports[0].level).toBe('warning');
expect(logger.getLevel()).toBe('warn');
});
it('Logger should be console and file by default', () => {
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
const pipes = logger.winston._readableState.pipes;
const pipes = logger.winston()._readableState.pipes;
expect(pipes.length).toBe(2);
expect(pipes[0].constructor.name).toBe('Console');
expect(pipes[0].silent).toBe(false);
@@ -101,8 +108,9 @@ describe('Logger', () => {
it('Logger can be file only', () => {
settings.set(['advanced', 'log_output'], ['file']);
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
const pipes = logger.winston._readableState.pipes;
const pipes = logger.winston()._readableState.pipes;
expect(pipes.length).toBe(2);
expect(pipes[0].constructor.name).toBe('Console');
expect(pipes[0].silent).toBe(true);
@@ -113,8 +121,9 @@ describe('Logger', () => {
it('Logger can be console only', () => {
settings.set(['advanced', 'log_output'], ['console']);
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
const pipes = logger.winston._readableState.pipes;
const pipes = logger.winston()._readableState.pipes;
expect(pipes.constructor.name).toBe('Console');
expect(pipes.silent).toBe(false);
});
@@ -122,8 +131,9 @@ describe('Logger', () => {
it('Logger can be nothing', () => {
settings.set(['advanced', 'log_output'], []);
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
const pipes = logger.winston._readableState.pipes;
const pipes = logger.winston()._readableState.pipes;
expect(pipes.constructor.name).toBe('Console');
expect(pipes.silent).toBe(true);
});
@@ -131,8 +141,9 @@ describe('Logger', () => {
it('Should allow to disable log rotation', () => {
settings.set(['advanced', 'log_rotation'], false);
const logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
const pipes = logger.winston._readableState.pipes;
const pipes = logger.winston()._readableState.pipes;
expect(pipes[1].constructor.name).toBe('File');
expect(pipes[1].maxFiles).toBeNull();
expect(pipes[1].tailable).toBeFalsy();
@@ -142,6 +153,7 @@ describe('Logger', () => {
it('Should allow to symlink logs to current directory', () => {
settings.set(['advanced', 'log_symlink_current'], true);
let logger = require('../lib/util/logger').default;
logger.init();
logger.logOutput();
expect(fs.readdirSync(dir.name).includes('current')).toBeTruthy()
@@ -151,23 +163,24 @@ describe('Logger', () => {
it('Log', () => {
const logger = require('../lib/util/logger').default;
const warn = jest.spyOn(logger.winston, 'warning');
logger.init();
const warn = jest.spyOn(logger.winston(), 'warning');
logger.warn('warn');
expect(warn).toHaveBeenCalledWith('warn');
const debug = jest.spyOn(logger.winston, 'debug');
const debug = jest.spyOn(logger.winston(), 'debug');
logger.debug('debug');
expect(debug).toHaveBeenCalledWith('debug');
const warning = jest.spyOn(logger.winston, 'warning');
const warning = jest.spyOn(logger.winston(), 'warning');
logger.warning('warning');
expect(warning).toHaveBeenCalledWith('warning');
const info = jest.spyOn(logger.winston, 'info');
const info = jest.spyOn(logger.winston(), 'info');
logger.info('info');
expect(info).toHaveBeenCalledWith('info');
const error = jest.spyOn(logger.winston, 'error');
const error = jest.spyOn(logger.winston(), 'error');
logger.error('error');
expect(error).toHaveBeenCalledWith('error');
});
+4 -2
View File
@@ -48,6 +48,8 @@ describe('On event', () => {
expect(call[0]).toBe('start')
expect(call[1]).toStrictEqual({})
expect(call[2]).toBe(device);
expect(call[3]).toStrictEqual(settings.getDevice(device.ieeeAddr));
expect(call[4]).toStrictEqual({});
});
it('Should call with stop event', async () => {
@@ -66,7 +68,7 @@ describe('On event', () => {
await zigbeeHerdsman.events.deviceAnnounce({device});
await flushPromises();
expect(mockOnEvent).toHaveBeenCalledTimes(1);
expect(mockOnEvent).toHaveBeenCalledWith('deviceAnnounce', {device}, device, settings.getDevice(device.ieeeAddr));
expect(mockOnEvent).toHaveBeenCalledWith('deviceAnnounce', {device}, device, settings.getDevice(device.ieeeAddr), {});
});
it('Should call index onEvent with zigbee event', async () => {
@@ -74,6 +76,6 @@ describe('On event', () => {
await zigbeeHerdsman.events.deviceAnnounce({device});
await flushPromises();
expect(zigbeeHerdsmanConverters.onEvent).toHaveBeenCalledTimes(1);
expect(zigbeeHerdsmanConverters.onEvent).toHaveBeenCalledWith('deviceAnnounce', {device}, device);
expect(zigbeeHerdsmanConverters.onEvent).toHaveBeenCalledWith('deviceAnnounce', {device}, device, settings.getDevice(device.ieeeAddr), {});
});
});
+34 -10
View File
@@ -1,3 +1,5 @@
const path = require('path');
const data = require('./stub/data');
const logger = require('./stub/logger');
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
@@ -7,10 +9,19 @@ const Controller = require('../lib/controller');
const flushPromises = require('./lib/flushPromises');
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
const stringify = require('json-stable-stringify-without-jsonify');
const zigbeeOTA = require('zigbee-herdsman-converters/lib/ota/zigbeeOTA');
const spyUseIndexOverride = jest.spyOn(zigbeeOTA, 'useIndexOverride');
describe('OTA update', () => {
let controller;
let resetExtension = async () => {
await controller.enableDisableExtension(false, 'OTAUpdate');
await controller.enableDisableExtension(true, 'OTAUpdate');
}
const mockClear = (mapped) => {
mapped.ota.updateToLatest = jest.fn();
mapped.ota.isUpdateAvailable = jest.fn();
@@ -20,7 +31,7 @@ describe('OTA update', () => {
data.writeDefaultConfiguration();
settings.reRead();
data.writeDefaultConfiguration();
settings.set(['advanced', 'ikea_ota_use_test_url'], true);
settings.set(['ota', 'ikea_ota_use_test_url'], true);
settings.reRead();
jest.useFakeTimers();
controller = new Controller(jest.fn(), jest.fn());
@@ -70,10 +81,11 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, logger, expect.any(Function));
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, ≈ 60 minutes remaining`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`);
expect(device.save).toHaveBeenCalledTimes(1);
expect(device.dateCode).toBe('20190102');
expect(device.softwareBuildID).toBe(2);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}'`);
expect(device.save).toHaveBeenCalledTimes(2);
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhen': 'immediate'});
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhen': 'active'});
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({"update_available":false,"update":{"state":"updating","progress":0}}),
@@ -91,7 +103,7 @@ describe('OTA update', () => {
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
stringify({"data":{"id": "bulb","from":{"software_build_id":1,"date_code":"20190101"},"to":{"software_build_id":2,"date_code":"20190102"}},"status":"ok"}),
stringify({"data":{"from":{"date_code":"20190101","software_build_id":1},"id":"bulb","to":{"date_code":"20190103","software_build_id":3}},"status":"ok"}),
{retain: false, qos: 0}, expect.any(Function)
);
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -370,11 +382,11 @@ describe('OTA update', () => {
expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, logger, expect.any(Function));
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, ≈ 60 minutes remaining`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`);
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
expect(logger.info).toHaveBeenCalledWith(`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}'`);
expect(logger.error).toHaveBeenCalledTimes(0);
expect(device.save).toHaveBeenCalledTimes(1);
expect(device.dateCode).toBe('20190102');
expect(device.softwareBuildID).toBe(2);
expect(device.save).toHaveBeenCalledTimes(2);
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {'sendWhen': 'immediate'});
});
it('Legacy api: Should handle when OTA update fails', async () => {
@@ -455,4 +467,16 @@ describe('OTA update', () => {
await flushPromises();
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
});
it('Set zigbee_ota_override_index_location', async () => {
settings.set(['ota', 'zigbee_ota_override_index_location'], 'local.index.json');
await resetExtension();
expect(spyUseIndexOverride).toHaveBeenCalledWith(path.join(data.mockDir, 'local.index.json'));
spyUseIndexOverride.mockClear();
settings.set(['ota', 'zigbee_ota_override_index_location'], 'http://my.site/index.json');
await resetExtension();
expect(spyUseIndexOverride).toHaveBeenCalledWith('http://my.site/index.json');
spyUseIndexOverride.mockClear();
});
});
+8 -8
View File
@@ -367,7 +367,7 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/thermostat_group/set', stringify({child_lock: 'LOCK'}));
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 7, length_hi: 0, length_lo: 1, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true});
expect(group.command).toHaveBeenCalledWith("manuSpecificTuya", "dataRequest", {dpValues: [{data: [1], datatype: 1, dp: 7}], seq: expect.any(Number)}, {disableDefaultResponse: true});
});
it('Should publish messages to groups with on and brightness', async () => {
@@ -919,11 +919,11 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(3);
expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}]);
expect(endpoint.command.mock.calls[1]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 0, transtime: 0}, {}]);
expect(endpoint.command.mock.calls[2]).toEqual(["genOnOff", "on", {}, {}]);
expect(endpoint.command.mock.calls[2]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}]);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 1}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]);
});
it('Should turn device off when brightness 0 is send with transition', async () => {
@@ -938,11 +938,11 @@ describe('Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(3);
expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}]);
expect(endpoint.command.mock.calls[1]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 0, transtime: 30}, {}]);
expect(endpoint.command.mock.calls[2]).toEqual(["genOnOff", "on", {}, {}]);
expect(endpoint.command.mock.calls[2]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}]);
expect(MQTT.publish).toHaveBeenCalledTimes(3);
expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 1}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]);
expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]);
});
it('Should allow to set color via hue and saturation', async () => {
@@ -1294,7 +1294,7 @@ describe('Publish', () => {
await MQTT.events.message('zigbee2mqtt/MKS-CM-W5/l3/set', stringify({state: 'ON'}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuya", "setData", {data: [1], datatype: 1, dp: 3, length_hi: 0, length_lo: 1, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true});
expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuya", "dataRequest", {dpValues: [{data: [1], datatype: 1, dp: 3}], seq: expect.any(Number)}, {disableDefaultResponse: true});
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/MKS-CM-W5');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({state_l3: 'ON'});
+4 -54
View File
@@ -254,42 +254,6 @@ describe('Receive', () => {
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should handle a zigbee message with voltage 2850', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {'65281': {'1': 2850}}
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 35, voltage: 2850});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should handle a zigbee message with voltage 2650', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {'65281': {'1': 2650}}
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 14, voltage: 2650});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should handle a zigbee message with voltage 2000', async () => {
const device = zigbeeHerdsman.devices.WXKG02LM_rev1;
const data = {'65281': {'1': 2000}}
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({battery: 0, voltage: 2000});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should publish 1 message when converted twice', async () => {
const device = zigbeeHerdsman.devices.RTCGQ11LM;
const data = {'65281': {'1': 3045, '3': 19, '4': 17320, '5': 35, '6': [0, 3], '10': 51107, '11': 381, '100': 0}}
@@ -298,7 +262,7 @@ describe('Receive', () => {
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/occupancy_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'battery': 100, 'illuminance': 381, "illuminance_lux": 381, 'voltage': 3045, 'temperature': 19});
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({'battery': 100, 'illuminance': 381, "illuminance_lux": 381, 'voltage': 3045, 'device_temperature': 19});
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
@@ -350,20 +314,6 @@ describe('Receive', () => {
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
});
it('Should handle forwarded Xiaomi messages', async () => {
const device = zigbeeHerdsman.devices.ZNCZ02LM;
const payload = {data: {measuredValue: -85}, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10, groupID: 6539};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/weather_sensor',
stringify({temperature: -0.85}),
{"qos": 1, "retain": false},
expect.any(Function),
)
});
it('Should handle messages from Xiaomi router devices', async () => {
const device = zigbeeHerdsman.devices.ZNCZ02LM;
const data = {onOff: 1};
@@ -421,7 +371,7 @@ describe('Receive', () => {
it('Should handle a command', async () => {
const device = zigbeeHerdsman.devices.E1743;
const data = {};
const payload = {data, cluster: 'genLevelCtrl', device, endpoint: device.getEndpoint(1), type: 'commandStopWithOnOff', linkquality: 10};
const payload = {data, cluster: 'genLevelCtrl', device, endpoint: device.getEndpoint(1), type: 'commandStopWithOnOff', linkquality: 10, meta: {zclTransactionSequenceNumber: 1}};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
@@ -437,10 +387,10 @@ describe('Receive', () => {
const oldNow = Date.now;
Date.now = jest.fn()
Date.now.mockReturnValue(new Date(150));
await zigbeeHerdsman.events.message(payload);
await zigbeeHerdsman.events.message({...payload, meta: {zclTransactionSequenceNumber: 2}});
await flushPromises();
Date.now.mockReturnValue(new Date(200));
await zigbeeHerdsman.events.message(payload);
await zigbeeHerdsman.events.message({...payload, meta: {zclTransactionSequenceNumber: 3}});
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(2);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
+92 -13
View File
@@ -66,7 +66,7 @@ describe('Settings', () => {
it('Should apply environment variables', () => {
process.env['ZIGBEE2MQTT_CONFIG_SERIAL_DISABLE_LED'] = 'true';
process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_SOFT_RESET_TIMEOUT'] = 1;
process.env['ZIGBEE2MQTT_CONFIG_EXPERIMENTAL_OUTPUT'] = 'csvtest';
process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_OUTPUT'] = 'csvtest';
process.env['ZIGBEE2MQTT_CONFIG_MAP_OPTIONS_GRAPHVIZ_COLORS_FILL'] = '{"enddevice": "#ff0000", "coordinator": "#00ff00", "router": "#0000ff"}';
process.env['ZIGBEE2MQTT_CONFIG_MQTT_BASE_TOPIC'] = 'testtopic';
@@ -77,7 +77,7 @@ describe('Settings', () => {
expected.groups = {};
expected.serial.disable_led = true;
expected.advanced.soft_reset_timeout = 1;
expected.experimental.output = 'csvtest';
expected.advanced.output = 'csvtest';
expected.map_options.graphviz.colors.fill = {enddevice: '#ff0000', coordinator: '#00ff00', router: '#0000ff'};
expected.mqtt.base_topic = 'testtopic';
@@ -150,6 +150,7 @@ describe('Settings', () => {
write(configurationFile, contentConfiguration);
const expected = {
base_topic: 'zigbee2mqtt',
include_device_information: false,
force_disable_retain: false,
password: "mysecretpassword",
@@ -672,14 +673,6 @@ describe('Settings', () => {
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Should ban devices', () => {
write(configurationFile, {});
settings.banDevice('0x123');
expect(settings.get().ban).toStrictEqual(['0x123']);
settings.banDevice('0x1234');
expect(settings.get().ban).toStrictEqual(['0x123', '0x1234']);
});
it('Should add devices to blocklist', () => {
write(configurationFile, {});
settings.blockDevice('0x123');
@@ -705,6 +698,18 @@ describe('Settings', () => {
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration shouldnt be valid when invalid QOS value is used', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'myname', retain: false, qos: 3}},
});
settings.reRead();
const error = `QOS for 'myname' not valid, should be 0, 1 or 2 got 3`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration shouldnt be valid when duplicate friendly_name are used', async () => {
write(configurationFile, {
...minimalConfig,
@@ -742,15 +747,15 @@ describe('Settings', () => {
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
it('Configuration friendly name cannot contain null char', async () => {
it('Configuration friendly name cannot contain control char', async () => {
write(configurationFile, {
...minimalConfig,
devices: {'0x0017880104e45519': {friendly_name: 'blaa/blaa' + String.fromCharCode(0), retain: false}},
devices: {'0x0017880104e45519': {friendly_name: 'blaa/blaa\u009f', retain: false}},
});
settings.reRead();
const error = `friendly_name is not allowed to contain null char`;
const error = `friendly_name is not allowed to contain control char`;
expect(settings.validate()).toEqual(expect.arrayContaining([error]));
});
@@ -830,4 +835,78 @@ describe('Settings', () => {
const after = fs.statSync(configurationFile).mtimeMs;
expect(before).toBe(after);
});
it('Frontend config', () => {
write(configurationFile, {...minimalConfig,
frontend: true,
});
settings.reRead();
expect(settings.get().frontend).toStrictEqual({port: 8080, auth_token: false, host: '0.0.0.0'})
});
it('Baudrate config', () => {
write(configurationFile, {...minimalConfig,
advanced: {baudrate: 20},
});
settings.reRead();
expect(settings.get().serial.baudrate).toStrictEqual(20)
});
it('ikea_ota_use_test_url config', () => {
write(configurationFile, {...minimalConfig,
advanced: {ikea_ota_use_test_url: true},
});
settings.reRead();
expect(settings.get().ota.ikea_ota_use_test_url).toStrictEqual(true)
});
it('transmit_power config', () => {
write(configurationFile, {...minimalConfig,
experimental: {transmit_power: 1337},
});
settings.reRead();
expect(settings.get().advanced.transmit_power).toStrictEqual(1337)
});
it('output config', () => {
write(configurationFile, {...minimalConfig,
experimental: {output: 'json'},
});
settings.reRead();
expect(settings.get().advanced.output).toStrictEqual('json')
});
it('Baudrartsctste config', () => {
write(configurationFile, {...minimalConfig,
advanced: {rtscts: true},
});
settings.reRead();
expect(settings.get().serial.rtscts).toStrictEqual(true)
});
it('Deprecated: Home Assistant config', () => {
write(configurationFile, {...minimalConfig,
homeassistant: {discovery_topic: 'new'},
advanced: {homeassistant_discovery_topic: 'old', homeassistant_status_topic: 'olds'},
});
settings.reRead();
expect(settings.get().homeassistant).toStrictEqual({discovery_topic: 'new', legacy_entity_attributes: true, legacy_triggers: true, status_topic: 'olds'})
});
it('Deprecated: ban/whitelist config', () => {
write(configurationFile, {...minimalConfig,
ban: ['ban'], whitelist: ['whitelist'], passlist: ['passlist'], blocklist: ['blocklist']
});
settings.reRead();
expect(settings.get().blocklist).toStrictEqual(['blocklist', 'ban'])
expect(settings.get().passlist).toStrictEqual(['passlist', 'whitelist'])
});
});
+2 -1
View File
@@ -21,7 +21,8 @@ function writeDefaultConfiguration() {
devices: {
"0x000b57fffec6a5b2": {
retain: true,
friendly_name: "bulb"
friendly_name: "bulb",
description: "this is my bulb",
},
"0x0017880104e45517": {
retain: true,
+2
View File
@@ -12,6 +12,7 @@ const callTransports = (level, message) => {
}
const mock = {
init: jest.fn(),
info: jest.fn().mockImplementation((msg) => callTransports('info', msg)),
warn: jest.fn().mockImplementation((msg) => callTransports('warn', msg)),
error: jest.fn().mockImplementation((msg) => callTransports('error', msg)),
@@ -23,6 +24,7 @@ const mock = {
setLevel: (newLevel) => {level = newLevel},
getLevel: () => level,
setTransportsEnabled: (value) => {transportsEnabled = value},
end: jest.fn(),
};
jest.mock('../../lib/util/logger', () => (mock));
+1 -1
View File
@@ -188,7 +188,7 @@ const devices = {
'MKS-CM-W5': new Device('Router', '0x90fd9ffffe4b64ac', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64aa', [], {})], true, "Mains (single phase)", "qnazj70", false),
'GL-S-007ZS': new Device('Router', '0x0017880104e45526', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45526')], true, "Mains (single phase)", 'GL-S-007ZS'),
'U202DST600ZB': new Device('Router', '0x0017880104e43559', 6540,4151, [new Endpoint(10, [0, 6], [], '0x0017880104e43559'), new Endpoint(11, [0, 6], [], '0x0017880104e43559')], true, "Mains (single phase)", 'U202DST600ZB'),
'3157100': new Device('Router', '0x0017880104e44559', 6542,4151, [new Endpoint(1, [], [], '0x0017880104e44559')], true, "Mains (single phase)", '3157100'),
'3157100': new Device('Router', '0x0017880104e44559', 6542,4151, [new Endpoint(1, [], [], '0x0017880104e44559')], true, "Mains (single phase)", '3157100', false, 'Centralite'),
'J1': new Device('Router', '0x0017880104a44559', 6543,4151, [new Endpoint(1, [], [], '0x0017880104a44559')], true, "Mains (single phase)", 'J1 (5502)'),
'TS0601_thermostat': TS0601_thermostat,
'external_converter_device': new Device('EndDevice', '0x0017880104e45511', 1114, 'external', [new Endpoint(1, [], [], '0x0017880104e45511')], false, null, 'external_converter_device' ),