Compare commits

..
2082 Commits
Author SHA1 Message Date
Koen Kanters fd8bafc81d chore(dev): release 2.12.0 (#32157) 2026-06-09 21:02:26 +02:00
Koen Kanters 68daf4f1de fix(ignore): update zigbee-herdsman-converters to 26.63.0 (#32252) 2026-06-09 18:57:20 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 41f6886591 fix(ignore): bump the minor-patch group across 1 directory with 3 updates (#32236)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-09 20:24:54 +02:00
Koen Kanters 582f153896 fix(ignore): update zigbee-herdsman to 10.4.0 (#32249) 2026-06-09 18:19:56 +00:00
Nerivec 6c2c874641 fix(ignore): missing type for new OTA endpoint (#32195) 2026-06-03 19:24:16 +00:00
Koen Kanters 120f6ac779 fix(ignore): update zigbee-herdsman-converters to 26.62.0 (#32194) 2026-06-03 19:05:29 +00:00
de58711e11 feat: Add ability to abort running OTA (#32022)
Co-authored-by: Andy <andrei_lazarov@yahoo.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-06-03 21:01:44 +02:00
Koen Kanters ea6ccac938 fix(ignore): update zigbee-herdsman to 10.3.0 (#32193) 2026-06-03 18:57:34 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ef8dcb4dc2 fix(ignore): bump the minor-patch group with 2 updates (#32183)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 20:42:49 +02:00
Koen Kanters babf7a17d4 fix(ignore): update zigbee-herdsman-converters to 26.61.2 (#32180) 2026-06-02 18:31:41 +00:00
Koen Kanters f9e1bac588 fix(ignore): update zigbee-herdsman to 10.2.0 (#32179) 2026-06-02 18:25:44 +00:00
github-actions[bot] 6aac849452 chore: promote to dev 2026-06-01 18:52:00 +00:00
Koen Kanters 2b5a670ba2 chore(dev): release 2.11.0 (#31938) 2026-06-01 20:28:15 +02:00
Koen Kanters 58feb78362 fix(ignore): update zigbee-herdsman-converters to 26.61.1 (#32156) 2026-06-01 18:21:24 +00:00
Koen Kanters c6e13ccb45 fix(ignore): update zigbee-herdsman-converters to 26.60.0 (#32150) 2026-05-31 19:07:26 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
ac8c45f67f fix(ignore): bump the minor-patch group with 2 updates (#32137)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2026-05-29 21:08:01 +00:00
Koen Kanters a8364ad67b fix(ignore): update zigbee-herdsman-converters to 26.59.1 (#32136) 2026-05-29 18:54:19 +00:00
Koen Kanters e2b49113c7 fix(ignore): update zigbee-herdsman-converters to 26.58.0 (#32126) 2026-05-28 19:42:40 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7b9407d020 fix(deps-dev): bump tmp from 0.2.5 to 0.2.6 (#32122)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-28 21:15:16 +02:00
Koen Kanters 873ad6082c chore: merge master to dev 2026-05-28 21:07:03 +02:00
Koen Kanters 8beaf488f7 chore: update CONTRIBUTING.md 2026-05-28 21:06:41 +02:00
Koen Kanters 7a1c2d52f0 fix(ignore): update zigbee-herdsman-converters to 26.57.0 (#32113) 2026-05-28 18:40:36 +00:00
Koen Kanters 5a79c338d4 chore: merge master to dev 2026-05-26 21:31:53 +02:00
Koen Kanters 16fd422c4a chore: add note about AI pull requests 2026-05-26 21:31:34 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e5f70d5ba8 fix(ignore): bump the minor-patch group with 3 updates (#32106)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-26 20:57:57 +02:00
Koen Kanters 0618aca73a chore: merge master to dev 2026-05-23 20:53:41 +02:00
Koen Kanters cfc7ab8dd8 chore: fix command injection in issueBot.mjs 2026-05-23 20:53:25 +02:00
Koen Kanters 56aab4e59c fix(ignore): update zigbee-herdsman-converters to 26.56.0 (#32087) 2026-05-23 18:36:39 +00:00
Koen Kanters 7075b78a46 fix(ignore): update zigbee-herdsman-converters to 26.55.0 (#32068) 2026-05-21 19:45:51 +00:00
Andy b31e8c286e fix: Fix default value of "optimistic" group setting (#32054) 2026-05-21 21:34:52 +02:00
Koen Kanters 2c5b536134 fix(ignore): update zigbee-herdsman-converters to 26.54.0 (#32065) 2026-05-20 20:17:53 +00:00
Koen Kanters 52201c96bf fix(ignore): update zigbee-herdsman-converters to 26.53.0 (#32051) 2026-05-19 18:13:25 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> bf964cc6d9 fix(deps): bump ws from 8.20.0 to 8.20.1 (#32041)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 19:27:30 +02:00
Koen Kanters 1e114dee70 fix(ignore): update zigbee-herdsman-converters to 26.52.0 (#32040) 2026-05-18 19:35:03 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Koen Kanters
897aa8fb7c fix(deps): bump brace-expansion from 2.0.2 to 5.0.6 (#32038)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-05-18 21:28:14 +02:00
Nerivec bbcbed12d5 fix: Prevent invalid external JS file name on save (#32037) 2026-05-18 21:22:18 +02:00
Koen Kanters 36c4db7f8f fix(ignore): update zigbee-herdsman to 10.1.0 (#32027) 2026-05-17 15:46:23 +00:00
Koen Kanters 5458e5084d fix(ignore): update zigbee-herdsman-converters to 26.51.1 (#32016) 2026-05-15 12:18:35 +00:00
Koen Kanters 744e4a6d5c fix(ignore): update zigbee-herdsman-converters to 26.51.0 (#31999) 2026-05-13 19:04:44 +00:00
Koen Kanters 6ea21c383b fix(ignore): update zigbee-herdsman-converters to 26.50.0 (#31988) 2026-05-12 18:19:05 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1a1c09449c fix(deps): bump pnpm/action-setup from 5 to 6 (#31698)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 19:56:50 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9426c3ea11 fix(ignore): bump the minor-patch group with 3 updates (#31982)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 19:47:15 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4d9e9504ef fix(deps): bump actions/dependency-review-action from 4 to 5 (#31980)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 19:47:03 +02:00
Koen Kanters 32499d14d5 fix(ignore): update zigbee-herdsman-converters to 26.49.0 (#31976) 2026-05-11 19:25:35 +00:00
Richard c4fd415cbe fix: Use Jinja-safe property access in HA discovery templates (#31930) 2026-05-11 17:28:04 +02:00
Koen Kanters 502030c016 fix(ignore): update zigbee-herdsman-converters to 26.48.0 (#31959) 2026-05-09 19:44:55 +00:00
Koen Kanters 7a05464f03 fix(ignore): update zigbee-herdsman-converters to 26.47.0 (#31946) 2026-05-08 18:34:03 +00:00
Nerivec 15fd9b371e feat: allow to disable external JS extensions (#31826) 2026-05-08 20:26:17 +02:00
Koen Kanters 04a43652d6 fix(ignore): update zigbee-herdsman to 10.0.8 (#31945) 2026-05-08 18:16:17 +00:00
github-actions[bot] 7b37e97e00 chore: promote to dev 2026-05-07 17:57:27 +00:00
Koen Kanters 94751abaac chore(dev): release 2.10.1 (#31831) 2026-05-07 19:44:19 +02:00
Koen Kanters cc8456617a fix(ignore): update zigbee-herdsman-converters to 26.46.0 (#31937) 2026-05-07 17:36:55 +00:00
Koen Kanters 8727abdefc fix(ignore): update zigbee-herdsman-converters to 26.45.0 (#31925) 2026-05-06 20:05:18 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 24e90273c9 fix(ignore): bump express-static-gzip from 3.0.0 to 3.0.1 in the minor-patch group (#31924)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-06 21:59:24 +02:00
Koen Kanters 845bdb7dba fix(ignore): update zigbee-herdsman-converters to 26.44.0 (#31900) 2026-05-05 18:57:33 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 55f12ff520 fix(ignore): bump @biomejs/biome from 2.4.13 to 2.4.14 in the minor-patch group across 1 directory (#31881)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-05 20:30:06 +02:00
Koen Kanters 19eb05a0e0 fix(ignore): update zigbee-herdsman-converters to 26.43.0 (#31878) 2026-05-05 05:29:59 +00:00
Nerivec 9f7ea9b7c7 fix: Replace deprecated url.parse (#31845) 2026-05-04 20:29:24 +02:00
github-actions[bot] 0d7b8f189c chore: promote to dev 2026-05-01 17:40:55 +00:00
Koen Kanters 4639243cf9 chore(dev): release 2.10.0 (#31572) 2026-05-01 19:28:02 +02:00
Koen Kanters df26459b46 fix(ignore): update zigbee-herdsman-converters to 26.42.0 (#31830) 2026-05-01 15:30:23 +00:00
Koen Kanters c58162344f fix(ignore): update zigbee-herdsman-converters to 26.41.0 (#31820) 2026-04-30 19:56:07 +00:00
Andy 5974286445 fix: Improve transmit power description (#31735) 2026-04-28 22:13:25 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4e527bbce8 fix(deps): bump googleapis/release-please-action from 4 to 5 (#31805)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 22:11:58 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b3983b35de fix(ignore): bump the minor-patch group with 3 updates (#31806)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 22:11:47 +02:00
Koen KantersandMikhail Samin 429c5aea58 fix(ignore): update zigbee-herdsman-converters to 26.40.0 (#31800)
Co-authored-by: Mikhail Samin <i@mishasamin.xyz>
2026-04-26 20:05:21 +00:00
Koen Kanters e71507835b fix(ignore): update zigbee-herdsman-converters to 26.39.1 (#31783) 2026-04-23 19:24:07 +00:00
Koen Kanters 3bb3d56c05 fix(ignore): update zigbee-herdsman-converters to 26.38.1 (#31777) 2026-04-22 19:19:16 +00:00
Koen Kanters 305b7bcafa fix(ignore): update zigbee-herdsman-converters to 26.38.0 (#31768) 2026-04-21 19:39:41 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7ba232e27d fix(ignore): bump the minor-patch group with 2 updates (#31758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 20:44:01 +02:00
Koen Kanters 78f440bada fix(ignore): update zigbee-herdsman-converters to 26.37.0 (#31749) 2026-04-19 19:39:12 +00:00
Koen Kanters a54775dea1 fix(ignore): update zigbee-herdsman-converters to 26.36.0 (#31737) 2026-04-17 18:01:27 +00:00
hoellenandKoen Kanters 041972669a feat: Home Assistant: add group entities in discovery config (#31663)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-04-17 19:14:54 +02:00
Koen Kanters 0a7dfcef59 fix(ignore): update zigbee-herdsman-converters to 26.35.0 (#31713) 2026-04-14 19:09:08 +00:00
Koen Kanters 55d8d1f4d0 fix(ignore): update zigbee-herdsman-converters to 26.34.0 (#31702) 2026-04-13 19:19:40 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> cdd7357522 fix(ignore): bump @biomejs/biome from 2.4.10 to 2.4.11 in the minor-patch group (#31699)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:00:46 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4294512486 chore(deps): bump actions/github-script from 8 to 9 (#31697)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 21:00:32 +02:00
Koen Kanters 0c9ec722db fix(ignore): update zigbee-herdsman-converters to 26.33.1 (#31674) 2026-04-11 12:19:45 +00:00
Koen Kanters a0204ad3f0 fix(ignore): update zigbee-herdsman to 10.0.7 (#31673) 2026-04-11 11:32:03 +00:00
toastedcornflakes 4a63e65981 fix: Clarify units of pause_on_backoff_gt (#31668) 2026-04-11 12:47:09 +02:00
Koen Kanters 4818cc4163 fix(ignore): update zigbee-herdsman-converters to 26.32.0 (#31653) 2026-04-09 19:18:29 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Koen Kanters
9e27181b64 fix(ignore): bump typescript from 5.9.3 to 6.0.2 (#31560)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-04-08 20:28:59 +02:00
Koen Kanters a50065cbb4 fix(ignore): update zigbee-herdsman-converters to 26.31.0 (#31637) 2026-04-07 19:14:49 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e6dd5c73bc fix(ignore): bump @types/node from 24.12.0 to 24.12.2 in the minor-patch group across 1 directory (#31628)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 20:55:57 +02:00
Koen Kanters aef3242319 fix(ignore): update zigbee-herdsman to 10.0.6 (#31636) 2026-04-07 18:31:15 +00:00
Koen Kanters 6b567c1286 fix(ignore): update zigbee-herdsman-converters to 26.30.0 (#31608) 2026-04-05 06:07:21 +00:00
Koen Kanters 23f9847994 fix(ignore): update zigbee-herdsman-converters to 26.29.0 (#31586) 2026-04-02 18:14:41 +00:00
Koen Kanters 08e4e9edeb fix(ignore): update zigbee-herdsman-converters to 26.28.0 (#31574) 2026-04-01 20:08:13 +00:00
github-actions[bot] 673d81f51a chore: promote to dev 2026-04-01 19:40:05 +00:00
Koen Kanters 2b485a98c5 chore(dev): release 2.9.2 (#31229) 2026-04-01 21:26:12 +02:00
Koen Kanters 62ab814d73 fix(ignore): update zigbee-herdsman-converters to 26.27.0 (#31563) 2026-03-31 19:00:42 +00:00
Koen Kanters fb3189b743 fix(ignore): update zigbee-herdsman to 10.0.5 (#31562) 2026-03-31 18:57:20 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
73d8ae8956 fix(ignore): bump the minor-patch group with 3 updates (#31559)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2026-03-31 20:46:21 +02:00
Koen Kanters 969db9ae85 fix(ignore): update zigbee-herdsman-converters to 26.26.0 (#31510) 2026-03-25 19:20:04 +00:00
Koen Kanters 66985a8ce8 fix(ignore): update zigbee-herdsman-converters to 26.25.0 (#31497) 2026-03-24 21:22:34 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 52b063bb32 fix(ignore): bump the minor-patch group with 3 updates (#31489)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-24 21:48:41 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3f2c00cdb7 chore(deps): bump pnpm/action-setup from 4 to 5 (#31488)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-24 21:48:28 +01:00
Koen Kanters dde637af2f fix(ignore): update zigbee-herdsman-converters to 26.24.0 (#31479) 2026-03-22 20:06:24 +00:00
Koen Kanters 867179c430 fix(ignore): update zigbee-herdsman to 10.0.4 (#31478) 2026-03-22 19:52:17 +00:00
Koen Kanters 2e5e362818 fix(ignore): update zigbee-herdsman-converters to 26.23.0 (#31462) 2026-03-20 20:26:14 +00:00
Koen Kanters 64286ebb30 fix(ignore): update zigbee-herdsman-converters to 26.22.0 (#31451) 2026-03-20 20:17:22 +00:00
Koen Kanters 6fb9d563e6 fix(ignore): update zigbee-herdsman to 10.0.3 (#31450) 2026-03-19 19:01:07 +00:00
Koen Kanters e054d7124b fix(ignore): update zigbee-herdsman-converters to 26.21.0 (#31427) 2026-03-17 19:18:59 +00:00
Koen Kanters 35567cb67f chore: change sorting to prevent merge conflict when both deps are updated at the same time 2026-03-17 20:14:34 +01:00
Koen Kanters 075f87c782 fix(ignore): update zigbee-herdsman to 10.0.2 (#31413) 2026-03-16 20:00:37 +00:00
Koen Kanters 9f49d0ec67 fix(ignore): update zigbee-herdsman-converters to 26.20.0 (#31415) 2026-03-16 19:57:51 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 67aa5099df fix(ignore): bump the minor-patch group with 2 updates (#31411)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 17:42:22 +01:00
Koen Kanters 69641d5467 fix(ignore): update zigbee-herdsman-converters to 26.19.1 (#31384) 2026-03-13 13:58:52 +00:00
Koen Kanters 6fca3744c9 fix(ignore): update zigbee-herdsman to 10.0.1 (#31383) 2026-03-13 13:11:23 +00:00
Koen Kanters f60db2d7a6 fix(ignore): update zigbee-herdsman-converters to 26.18.0 (#31362) 2026-03-12 06:47:49 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 08bf2cbc07 chore(deps): bump docker/build-push-action from 6 to 7 (#31325)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 07:14:47 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> c790e7b68f chore(deps): bump docker/metadata-action from 5 to 6 (#31327)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 07:14:14 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a8633760bc chore(deps): bump docker/login-action from 3 to 4 (#31326)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 07:14:10 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ff01c8718d chore(deps): bump docker/setup-qemu-action from 3 to 4 (#31324)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 07:13:55 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> cd6977bce6 chore(deps): bump docker/setup-buildx-action from 3 to 4 (#31323)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-12 07:13:52 +01:00
Koen Kanters 9143924482 fix(ignore): update zigbee-herdsman-converters to 26.17.0 (#31314) 2026-03-09 06:01:53 +00:00
NerivecandKoen Kanters 0f781ee9c0 fix: Match new cluster-related typing from ZH (#31298)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-03-09 06:55:11 +01:00
Andrei LAZAROVandNerivec 9f1eb764a6 fix: Export definition version (#31270)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2026-03-09 06:28:20 +01:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
b7f1cecb49 fix(ignore): bump the minor-patch group with 3 updates (#31295)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2026-03-07 12:24:25 +00:00
Koen Kanters 05946b5955 fix(ignore): update zigbee-herdsman-converters to 26.16.1 (#31268) 2026-03-07 06:14:12 +00:00
Nerivec 4a8369295c fix: Reintroduce onboarding improvements (#31273) 2026-03-07 06:21:11 +01:00
Koen Kanters 1747618afa fix(ignore): update zigbee-herdsman to 9.0.11 (#31257) 2026-03-03 19:19:31 +00:00
Koen Kanters 284d86e93b fix(ignore): update zigbee-herdsman-converters to 26.14.0 (#31246) 2026-03-03 06:06:45 +00:00
Koen Kanters d6323825e4 fix(ignore): update zigbee-herdsman to 9.0.10 (#31245) 2026-03-03 05:52:27 +00:00
DanielDeni b7b43030eb fix: Home Assistant: fix entity names for derived weather sensors by removing device_class (#31234) 2026-03-03 06:03:42 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> aefbb8be14 fix(ignore): bump @biomejs/biome from 2.4.4 to 2.4.5 in the minor-patch group (#31237)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 06:02:12 +01:00
Koen Kanters fc3a1968f6 fix(ignore): update zigbee-herdsman-converters to 26.13.0 (#31231) 2026-03-02 11:45:35 +00:00
github-actions[bot] c56fbca457 chore: promote to dev 2026-03-02 11:26:18 +00:00
Koen Kanters 85875aee27 chore(dev): release 2.9.1 (#31217) 2026-03-02 12:12:38 +01:00
Koen Kanters 7f0a9aa2a3 fix: Fix onboarding (#31228) 2026-03-02 12:10:18 +01:00
github-actions[bot] ff528567e6 chore: promote to dev 2026-03-01 14:08:34 +00:00
Koen Kanters 3473aa3f40 chore(dev): release 2.9.0 (#30896) 2026-03-01 14:57:03 +01:00
Koen Kanters 8ee5b1f03b fix(ignore): update zigbee-herdsman to 9.0.9 (#31215) 2026-03-01 13:46:14 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 89192f9dec fix(ignore): bump the minor-patch group with 2 updates (#31211)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-28 23:10:27 +00:00
Koen Kanters a9a6ebe007 fix(ignore): update zigbee-herdsman-converters to 26.12.0 (#31208) 2026-02-28 20:03:57 +00:00
Koen Kanters c7d54ec360 fix(ignore): update zigbee-herdsman to 9.0.8 (#31207) 2026-02-28 19:42:46 +00:00
Koen Kanters c880d36f29 fix(ignore): update zigbee-herdsman-converters to 26.11.0 (#31185) 2026-02-25 20:56:05 +00:00
Koen Kanters e1e856d7da fix(ignore): Fix enableDisableExtension (#31176) 2026-02-24 20:46:30 +01:00
Koen Kanters b9d9e5e75c fix(ignore): update zigbee-herdsman-converters to 26.10.1 (#31175) 2026-02-24 19:31:23 +00:00
Nerivec f8385b8899 fix: Improve startup signals behavior (#31130) 2026-02-24 20:23:52 +01:00
Koen Kanters a77f48f5c4 fix: Fix Home Assistant options cannot be updated while running (#31151) 2026-02-24 20:23:35 +01:00
Koen Kanters 79bcf276f5 fix(ignore): update zigbee-herdsman to 9.0.7 (#31174) 2026-02-24 19:06:50 +00:00
Nerivec 93dc9a3664 fix: update zigbee2mqtt-windfront to 2.9.0 (#31162) 2026-02-23 21:02:59 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1d5184f2bd fix(ignore): bump @biomejs/biome from 2.4.1 to 2.4.4 in the minor-patch group (#31159)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-23 21:29:40 +01:00
Nerivec 8ecf353dd6 feat: Improve onboarding (#31152) 2026-02-23 21:29:31 +01:00
Koen Kanters 8560c11e06 fix(ignore): update zigbee-herdsman to 9.0.6 (#31149) 2026-02-22 20:29:08 +00:00
Koen Kanters 4d287eed0c fix(ignore): update zigbee-herdsman-converters to 26.9.0 (#31137) 2026-02-21 20:20:19 +00:00
DanielDeni 8d1240eba1 fix: Home Assistant: add weather station sensor discovery lookup entries (#31136) 2026-02-21 21:07:11 +01:00
NerivecandCopilot 0c43c98306 fix: Improve configure attempts lazy+cleanup (#31126)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-21 21:06:32 +01:00
Koen Kanters 396eb38e4b fix(ignore): update zigbee-herdsman-converters to 26.8.0 (#31118) 2026-02-19 19:43:14 +00:00
Koen Kanters 5957085fcc fix(ignore): update zigbee-herdsman to 9.0.5 (#31117) 2026-02-19 19:36:47 +00:00
Koen Kanters 02a2fd8fef fix: Home Assistant: add state_class: measurement for external_humidity and occupancy_level https://github.com/Koenkk/zigbee2mqtt/issues/31022 2026-02-19 20:29:43 +01:00
Koen Kanters 9d566f4c01 fix(ignore): update zigbee-herdsman-converters to 26.7.0 (#31090) 2026-02-17 20:15:20 +00:00
dependabot[bot] 42d88fe90d fix(ignore): bump the minor-patch group with 4 updates (#31081) 2026-02-16 19:54:17 +01:00
Koen Kanters 909d2b32b7 fix(ignore): update zigbee-herdsman-converters to 26.6.0 (#31076) 2026-02-15 19:34:36 +00:00
Koen Kanters 061262ff97 fix(ignore): update zigbee-herdsman-converters to 26.5.0 (#31058) 2026-02-13 20:49:19 +00:00
Koen Kanters 142754b71c fix(ignore): update zigbee-herdsman-converters to 26.4.0 (#31036) 2026-02-11 20:42:49 +00:00
Koen Kanters c599e19bf2 fix(ignore): update zigbee-herdsman-converters to 26.3.0 (#31028) 2026-02-10 19:47:25 +00:00
Koen Kanters 28a9f39dbc fix: Home Assistant: set state_class measurement for external_temperature https://github.com/Koenkk/zigbee2mqtt/issues/31022 2026-02-10 20:30:58 +01:00
Koen Kanters 4e360d0bf2 fix(ignore): update zigbee-herdsman-converters to 26.2.1 (#31015) 2026-02-09 21:06:38 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Koen Kanters
15e385bac6 fix(ignore): bump the minor-patch group with 3 updates (#31011)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-02-09 20:57:32 +01:00
SnazzyBean c7dae39799 fix: Home Assistant: expose siren entity for IAS warning devices (#31000) 2026-02-09 20:42:03 +01:00
Koen Kanters b585e31304 fix(ignore): update zigbee-herdsman-converters to 26.2.0 (#30998) 2026-02-08 20:38:11 +00:00
Koen Kanters bf888de478 fix(ignore): update zigbee-herdsman to 9.0.4 (#30996) 2026-02-08 20:08:09 +00:00
Koen Kanters f1156db227 fix(ignore): update zigbee-herdsman-converters to 26.1.0 (#30981) 2026-02-06 21:16:11 +00:00
DataGhost d475aa5be5 fix: Home Assistant: add measurement state_class to _count entities (#30978) 2026-02-06 21:47:26 +01:00
+2
Koen Kantersdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>burmistrzakrhysfredrhysPatrick ZAJDAIgnacio Hernandez-RosRohan KapoorNerivecStephan GarlandAndrei LAZAROV
b71461e03c fix(ignore): Changes for new ZHC definition.version (#30402)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: burmistrzak <61958704+burmistrzak@users.noreply.github.com>
Co-authored-by: rhysfred <rhysfred@users.noreply.github.com>
Co-authored-by: rhys <rhys@frontleftspeaker.com>
Co-authored-by: Patrick ZAJDA <patrick@zajda.fr>
Co-authored-by: Ignacio Hernandez-Ros <ignacio@hernandez-ros.com>
Co-authored-by: Rohan Kapoor <rohan@rohankapoor.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
Co-authored-by: Stephan Garland <stephan.garland@affirm.com>
Co-authored-by: Andrei LAZAROV <andrei_lazarov@yahoo.com>
2026-02-05 21:29:58 +01:00
Koenkk 6cce05a514 fix(ignore): update zigbee-herdsman-converters to 26.0.0 2026-02-05 21:29:58 +01:00
Koen Kanters 30d311e849 fix(ignore): update zigbee-herdsman to 9.0.3 (#30960) 2026-02-05 20:15:03 +00:00
Koen Kanters 402ed6094e fix(ignore): update zigbee-herdsman-converters to 25.122.0 (#30935) 2026-02-03 19:31:34 +00:00
Andrei LAZAROV e381d54f7e chore: readme: update integration logos and add links (#30926) 2026-02-03 20:05:11 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4b039103c0 fix(ignore): bump mqtt from 5.14.1 to 5.15.0 in the minor-patch group (#30918)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 20:43:51 +01:00
Koen Kanters 8d8d37e388 fix: Home Assistant: discover temperature_probe as temperature device_class https://github.com/Koenkk/zigbee2mqtt/issues/30862 2026-02-02 20:12:34 +01:00
Koen Kanters 09d010ea06 fix(ignore): update zigbee-herdsman-converters to 25.121.0 (#30898) 2026-02-01 20:05:04 +00:00
github-actions[bot] ede1dc5951 chore: promote to dev 2026-02-01 19:37:17 +00:00
Koen Kanters b0b02c30b2 chore(dev): release 2.8.0 (#30420) 2026-02-01 20:19:58 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9ddbb2a21a fix(ignore): bump zigbee2mqtt-windfront from 2.8.0 to 2.8.1 in the minor-patch group (#30879)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-01 20:17:41 +01:00
Koen Kanters 49b752da35 fix(ignore): update zigbee-herdsman-converters to 25.120.0 (#30876) 2026-01-31 13:35:35 +00:00
Koen Kanters 788b0d3993 fix(ignore): update zigbee-herdsman-converters to 25.119.0 (#30874) 2026-01-31 12:48:45 +00:00
Koen Kanters f68834049c fix(ignore): update zigbee-herdsman-converters to 25.118.0 (#30865) 2026-01-30 20:26:15 +00:00
Koen Kanters 4afbce62eb fix(ignore): update zigbee-herdsman-converters to 25.117.0 (#30849) 2026-01-29 19:07:37 +00:00
burmistrzak 4f412cb4da fix: Home Assistant: Change device_class of eco2 to volatile_organic_compounds_parts (#30840) 2026-01-29 19:48:41 +01:00
Koen Kanters 5a4d6b56ad fix(ignore): update zigbee-herdsman-converters to 25.116.0 (#30839) 2026-01-28 20:59:48 +00:00
burmistrzak 26a0fd4de0 fix: Home Assistant: discover temperature sensor for thermostats (#30804) 2026-01-28 21:47:03 +01:00
dependabot[bot] 8d980ae6ec fix(ignore): bump zigbee2mqtt-windfront in the minor-patch group
Bumps the minor-patch group with 1 update: [zigbee2mqtt-windfront](https://github.com/Nerivec/zigbee2mqtt-windfront).


Updates `zigbee2mqtt-windfront` from 2.7.0 to 2.8.0
- [Release notes](https://github.com/Nerivec/zigbee2mqtt-windfront/releases)
- [Commits](https://github.com/Nerivec/zigbee2mqtt-windfront/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: zigbee2mqtt-windfront
  dependency-version: 2.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 20:46:31 +01:00
Koen Kanters 99e06a8d3e fix(ignore): update zigbee-herdsman-converters to 25.115.0 (#30826) 2026-01-27 19:46:20 +00:00
NerivecandKoen Kanters 476efaa6e1 fix: OTA availability detection (#30815)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-01-27 20:37:18 +01:00
Koenkk 10478e797e fix(ignore): update zigbee-herdsman to 9.0.2 2026-01-27 20:37:18 +01:00
Stephan GarlandandKoen Kanters 5de47a0d93 fix: Network map: escape double-quotes and backslashes in device attributes (#30746)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-01-27 19:59:53 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 14bffd58b1 fix(ignore): bump @biomejs/biome from 2.3.12 to 2.3.13 in the minor-patch group (#30810)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 21:49:13 +01:00
Koen Kanters 999df2913f fix(ignore): update zigbee-herdsman-converters to 25.114.0 (#30803) 2026-01-25 20:55:24 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
da1f77ec6e fix(ignore): bump the minor-patch group with 2 updates (#30790)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2026-01-24 20:59:45 +00:00
Koen Kanters 036ee471dd fix(ignore): update zigbee-herdsman-converters to 25.113.0 (#30789) 2026-01-24 20:31:04 +00:00
NerivecandKoen Kanters dd1c449796 feat: Improve OTA (#30566)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-01-24 21:27:44 +01:00
Koen Kanters 6d5e7e334a fix(ignore): update zigbee-herdsman to 9.0.1 (#30788) 2026-01-24 20:21:57 +00:00
burmistrzak 8e682956e5 fix: Home Assistant: Use temperature_delta for calibration (#30784) 2026-01-24 21:03:39 +01:00
Koen Kanters 971ddaa3cb fix(ignore): update zigbee-herdsman to 9.0.0 (#30787) 2026-01-24 19:45:15 +00:00
Koen Kanters ad51d12a2d fix(ignore): update zigbee-herdsman-converters to 25.112.0 (#30767) 2026-01-23 19:49:13 +00:00
Rohan Kapoor d9e4244f80 fix(ignore): Replace multiple area occupancy fields with camelCase instead of PascalCase (#30745) 2026-01-23 20:38:15 +01:00
Koen Kanters 2bb44f7a8b fix(ignore): update zigbee-herdsman-converters to 25.111.0 (#30739) 2026-01-21 20:53:41 +00:00
Koen Kanters 20af50201e fix(ignore): update zigbee-herdsman-converters to 25.110.0 (#30726) 2026-01-20 19:45:17 +00:00
Rohan Kapoor 2e36084852 fix: Home Assistant: treat Area1-4Occupancy as occupancy (#30712) 2026-01-20 19:34:39 +00:00
Koen Kanters 9d266bda9a fix(ignore): update zigbee-herdsman to 8.1.0 (#30725) 2026-01-20 19:29:58 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 043dd0ff1c fix(ignore): bump @types/node from 24.10.7 to 24.10.9 in the minor-patch group (#30713)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-20 20:09:54 +01:00
Koen Kanters ee6afaf1c6 fix(ignore): update zigbee-herdsman-converters to 25.109.0 (#30709) 2026-01-18 20:48:31 +00:00
Ignacio Hernandez-Ros 4815d5149d fix: Home Assistant: add gas device class (#30653) 2026-01-18 20:29:39 +01:00
Patrick ZAJDAandKoen Kanters 6f0b02e009 fix: Home Assistant: expose group settings override (#30627)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-01-17 15:25:36 +01:00
Koen Kanters 5e1b7d4830 fix(ignore): update zigbee-herdsman-converters to 25.108.0 (#30672) 2026-01-16 20:04:18 +00:00
Koen Kanters 535f75e90c fix(ignore): update zigbee-herdsman-converters to 25.107.0 (#30662) 2026-01-15 21:08:45 +00:00
Koen Kanters d44cdbde4f fix(ignore): update zigbee-herdsman to 8.0.3 (#30632) 2026-01-13 20:19:13 +00:00
Koen Kanters 9a41ec2a39 fix(ignore): update zigbee-herdsman-converters to 25.106.0 (#30631) 2026-01-13 20:03:22 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1d87d2f234 fix(ignore): bump the minor-patch group with 2 updates (#30617)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-12 20:53:11 +01:00
Koen Kanters 29c757145d fix(ignore): update zigbee-herdsman-converters to 25.105.0 (#30605) 2026-01-11 20:47:06 +00:00
Koen Kanters e68403d8d3 fix(ignore): update zigbee-herdsman-converters to 25.104.0 (#30564) 2026-01-08 20:09:11 +00:00
Koen Kanters a32c8eec36 fix: Home Assistant: mark load_estimate as state_class measurement https://github.com/Koenkk/zigbee-herdsman-converters/issues/11240 2026-01-08 20:54:25 +01:00
Koen Kanters d4522c53ca fix(ignore): update zigbee-herdsman-converters to 25.103.0 (#30552) 2026-01-07 19:38:42 +00:00
Koen Kanters 57245e5234 fix(ignore): update zigbee-herdsman-converters to 25.102.0 (#30537) 2026-01-06 20:44:54 +00:00
Koen Kanters 4313baca09 fix(ignore): update zigbee-herdsman-converters to 25.101.0 (#30520) 2026-01-05 20:56:26 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ed52c49e34 fix(ignore): bump @biomejs/biome from 2.3.10 to 2.3.11 in the minor-patch group (#30511)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 21:29:35 +01:00
Koen Kanters a2b81ab894 fix(ignore): update zigbee-herdsman-converters to 25.100.0 (#30499) 2026-01-04 20:27:22 +00:00
Koen Kanters 3e60f915ff fix: Home Assisant: set state class to measurement for illuminane_raw https://github.com/Koenkk/zigbee2mqtt/issues/30439 2026-01-04 20:02:44 +01:00
rhysfredandrhys 1eaa2f3e75 fix: Log error before renaming failed to load converters (#30436)
Co-authored-by: rhys <rhys@frontleftspeaker.com>
2026-01-04 19:51:46 +01:00
Koen Kantersandburmistrzak 3adf7d5d8e fix(ignore): update zigbee-herdsman-converters to 25.99.0 (#30481)
Co-authored-by: burmistrzak <61958704+burmistrzak@users.noreply.github.com>
2026-01-04 18:51:31 +00:00
Koen Kanters 1147cf16dd fix(ignore): update zigbee-herdsman to 8.0.2 (#30480) 2026-01-03 20:59:33 +00:00
github-actions[bot] 97a9b15240 chore: promote to dev 2026-01-01 13:53:53 +00:00
Koen Kanters 3a49c95786 chore(dev): release 2.7.2 (#30051) 2026-01-01 14:41:19 +01:00
Koen Kanters 0eba0c953d fix(ignore): update zigbee-herdsman-converters to 25.98.0 (#30417) 2026-01-01 13:23:35 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4d6269e7e8 fix(ignore): bump zigbee2mqtt-windfront from 2.6.1 to 2.6.2 in the minor-patch group (#30412)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-31 19:56:27 +00:00
Koen Kanters 3727f078ec fix(ignore): update zigbee-herdsman-converters to 25.97.0 (#30396) 2025-12-30 14:29:46 +00:00
dependabot[bot] 93c9704fcd fix(ignore): bump @types/node from 24.10.2 to 24.10.4 in the minor-patch group (#30390) 2025-12-30 13:43:39 +01:00
Koen Kanters 2721b0ea78 fix(ignore): update zigbee-herdsman-converters to 25.96.0 (#30383) 2025-12-28 20:58:41 +00:00
Koen Kanters 4c48e9cfbc fix(ignore): update zigbee-herdsman-converters to 25.95.0 (#30371) 2025-12-27 20:42:04 +00:00
Koen Kanters 7cb5c58dc0 fix(ignore): update zigbee-herdsman-converters to 25.94.0 (#30355) 2025-12-26 14:37:38 +00:00
Koen Kanters c61f67f061 fix(ignore): update zigbee-herdsman-converters to 25.93.0 (#30331) 2025-12-23 20:58:54 +00:00
Nerivec 58d98c7263 fix: log dir tz format not working on some systems (#30324) 2025-12-23 17:45:04 +01:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
b10fa93700 fix(ignore): bump the minor-patch group with 2 updates (#30309)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-12-22 15:23:39 +00:00
Koen Kanters c28a024664 fix(ignore): update zigbee-herdsman-converters to 25.92.0 (#30300) 2025-12-21 21:28:27 +00:00
Nerivec 8549b37228 fix: Proper timezone in logs dir (#30297) 2025-12-21 20:44:05 +01:00
Koen Kanters 4c02e3da8f fix(ignore): update zigbee-herdsman-converters to 25.91.0 (#30279) 2025-12-19 21:02:06 +00:00
Koen Kanters ab0894715a fix(ignore): update zigbee-herdsman-converters to 25.90.0 (#30267) 2025-12-18 21:36:30 +00:00
Koen Kanters 1688a3b532 fix(ignore): update zigbee-herdsman to 8.0.1 (#30240) 2025-12-16 16:51:45 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 97694d2241 chore(deps): bump actions/cache from 4 to 5 (#30221)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 17:45:15 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d6088c56db chore(deps): bump peter-evans/create-pull-request from 7 to 8 (#30222)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-16 17:45:06 +01:00
Koen Kanters b39b8d478e fix: Don't fail to start when removing old log directory fails (#30216) 2025-12-16 17:44:38 +01:00
Koen Kanters 01304cfc66 fix(ignore): update zigbee-herdsman-converters to 25.89.0 (#30215) 2025-12-14 20:40:29 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1d15c2142f fix(ignore): bump the minor-patch group with 2 updates (#30207)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 16:40:27 +00:00
Koen Kanters 3818c50b8a fix(ignore): update zigbee-herdsman-converters to 25.88.0 (#30196) 2025-12-13 20:34:30 +00:00
Koen KantersandNerivec 7f5f6a5849 fix(ignore): update zigbee-herdsman to 8.0.0 and zigbee-herdsman-converters to 25.87.0 (#30186)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-12-13 20:40:09 +01:00
Nerivec 0025ef87c5 fix: Biome floating promises detection (#30137) 2025-12-13 20:38:03 +01:00
Koen Kanters 3460f157df fix(ignore): update zigbee-herdsman-converters to 25.86.0 (#30143) 2025-12-10 21:14:41 +00:00
Koen Kanters ecb0af4057 fix(ignore): update zigbee-herdsman to 7.0.6 (#30142) 2025-12-10 21:11:13 +00:00
Koen Kanters 5db6ae0a19 fix(ignore): update zigbee-herdsman-converters to 25.85.0 (#30120) 2025-12-09 20:15:54 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ad3c09036b fix(ignore): bump the minor-patch group across 1 directory with 4 updates (#30117)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 20:33:03 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 0c89629526 chore(deps): bump alpine from 3.22 to 3.23 in /docker (#30094)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 20:04:22 +01:00
Koen Kanters 4574ffc71a fix(ignore): update zigbee-herdsman to 7.0.5 (#30116) 2025-12-09 18:46:55 +00:00
Koen Kanters 1707f3abf3 chore: merge master to dev 2025-12-07 21:26:17 +01:00
Koen Kanters 8916fb7b50 chore: lint issueBot 2025-12-07 21:25:58 +01:00
Koen Kanters 6e70ef24d0 chore: merge master to dev 2025-12-07 21:22:54 +01:00
Koen Kanters 8a3c4bd59f chore: issueBot: handle duplicates 2025-12-07 21:22:34 +01:00
Koen Kanters c18d1329c8 fix(ignore): update zigbee-herdsman-converters to 25.84.0 (#30081) 2025-12-07 19:56:11 +00:00
Yuxin Wang ed1e7b333f fix: Remove extra > in onboarding(#30065) 2025-12-07 20:22:04 +01:00
github-actions[bot] 95c38810a8 chore: promote to dev 2025-12-06 20:39:43 +00:00
Koen Kanters b70701f8b8 chore: fix changelog generator undefined whitelabel vendor/description 2025-12-06 21:23:09 +01:00
Koen Kanters 6d30fa156c chore(dev): release 2.7.1 (#29926) 2025-12-06 21:14:47 +01:00
Koen Kanters 9b6035387b fix(ignore): update zigbee-herdsman-converters to 25.83.1 (#30050) 2025-12-06 20:06:52 +00:00
burmistrzakandKoen Kanters 9ca85dfac8 fix: Home Assistant: Translate boolean to state topic for current_humidity_topic (#30014)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-12-06 20:46:27 +01:00
Koen Kanters 334fa2a60d fix(ignore): update zigbee-herdsman to 7.0.4 (#30041) 2025-12-06 13:22:31 +00:00
Koen Kanters a7f1345a91 fix(ignore): update zigbee-herdsman-converters to 25.83.0 (#30011) 2025-12-04 20:54:41 +00:00
Koen Kanters bae9bb8812 fix(ignore): update zigbee-herdsman to 7.0.3 (#30008) 2025-12-04 20:02:17 +00:00
Koen Kanters 740edd8b58 fix(ignore): update zigbee-herdsman-converters to 25.82.0 (#29990) 2025-12-03 19:29:19 +00:00
Koen Kanters 267fee3414 Revert "chore: retry 'Docker build and push' on failure"
This reverts commit 91f4b76970.
2025-12-03 20:10:50 +01:00
burmistrzak fa99c6dcca fix: Home Assistant: Accept multiple access levels for current_humidity source property (#29936) 2025-12-03 19:34:35 +01:00
Koen Kanters 91f4b76970 chore: retry 'Docker build and push' on failure 2025-12-02 21:57:12 +01:00
Koen Kanters fd4282b98d fix(ignore): update zigbee-herdsman-converters to 25.81.0 (#29959) 2025-12-02 20:48:36 +00:00
Koen Kanters 403bd4a725 fix(ignore): update zigbee-herdsman to 7.0.2 (#29956) 2025-12-02 20:16:21 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ce27ff3cbf fix(ignore): bump finalhandler from 2.1.0 to 2.1.1 in the minor-patch group (#29920)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-02 21:00:30 +01:00
github-actions[bot] 5f474e03b8 chore: promote to dev 2025-12-01 20:35:56 +00:00
Koen Kanters 110149fa20 chore(dev): release 2.7.0 (#29396) 2025-12-01 21:22:06 +01:00
Koen Kanters 445890a071 fix(ignore): update zigbee-herdsman-converters to 25.80.0 (#29924) 2025-12-01 20:17:02 +00:00
Nerivec 3dce8548ce fix(ignore): bump zigbee2mqtt-windfront to 2.4.2 (#29919) 2025-12-01 16:02:29 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 4a020fd8de fix(ignore): bump zigbee2mqtt-windfront from 2.4.0 to 2.4.1 in the minor-patch group (#29905)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-30 15:36:04 +00:00
Koen Kanters d411fc5c69 fix(ignore): update zigbee-herdsman-converters to 25.79.0 (#29903) 2025-11-30 14:06:44 +00:00
burmistrzakandKoen Kanters 631c4f6a57 fix: Home Assistant: expose current_humidity for climate devices exposing humidity (#29842)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-11-29 13:32:28 +01:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
c02ed09211 fix(ignore): bump the minor-patch group with 2 updates (#29859)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-11-27 20:12:22 +00:00
Koen Kanters 49b4876768 fix(ignore): update zigbee-herdsman-converters to 25.78.0 (#29856) 2025-11-27 19:16:34 +00:00
Koen Kanters 802271ed37 fix(ignore): update zigbee-herdsman to 7.0.1 (#29855) 2025-11-27 19:00:08 +00:00
Nerivec 42fc6e03a7 fix: Reporting payload detection fixes (#29854) 2025-11-27 19:46:31 +01:00
Koen Kanters 35d8a5da7f fix(ignore): update zigbee-herdsman-converters to 25.77.0 (#29838) 2025-11-26 20:00:02 +00:00
Koen Kanters 72d862f4a0 fix(ignore): update zigbee-herdsman-converters to 25.76.0 (#29823) 2025-11-25 19:57:30 +00:00
Nerivec ad0696856a fix(ignore): prop name convention on new API endpoints (#29820) 2025-11-25 20:18:14 +01:00
NerivecandKoen Kanters a61646d584 feat: Add action bridge/request API (#29730)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-11-24 21:57:48 +01:00
Koen Kanters 854df498bd fix(ignore): update zigbee-herdsman-converters to 25.75.0 (#29811) 2025-11-24 20:54:10 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Koen Kanters
e2232e9e91 fix(ignore): bump the minor-patch group with 3 updates (#29804)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-11-24 21:33:29 +01:00
dependabot[bot] 6775c4f17b chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 21:31:41 +01:00
Nerivec 17520f7cfb fix(ignore): update zigbee2mqtt-windfront to 2.3.1 (#29788) 2025-11-23 22:27:04 +00:00
Koen Kanters 1707bcab88 chore: merge master to dev 2025-11-23 22:05:07 +01:00
Koen Kanters c577c8e5f0 chore: lint issueBot 2025-11-23 22:04:48 +01:00
Koen Kanters 7b8efbe2c4 chore: merge master to dev 2025-11-23 21:59:31 +01:00
Koen Kanters 459b695f60 chore: issueBot: add logging and search in title 2025-11-23 21:59:13 +01:00
NerivecandKoen Kanters f26ade4f93 feat: Add new bind/reporting/map features (#29750)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-11-23 21:50:11 +01:00
Koenkk 3e2aefbcf8 fix(ignore): update zigbee-herdsman to 7.0.0 2025-11-23 21:50:11 +01:00
Koen Kanters ba77ce7c8d fix(ignore): update zigbee-herdsman-converters to 25.74.0 (#29770) 2025-11-22 20:47:44 +00:00
Alex Freidel 4c21b66079 fix: Add port 8080 EXPOSE to container (#29754) 2025-11-22 13:24:24 +01:00
Koen Kanters be774a4d0d fix(ignore): update zigbee-herdsman-converters to 25.73.0 (#29752) 2025-11-21 19:48:11 +00:00
Koen Kanters 7b7b988960 fix(ignore): update zigbee-herdsman-converters to 25.72.0 (#29735) 2025-11-20 21:00:08 +00:00
Koen Kanters d6bf5174be fix(ignore): update zigbee-herdsman-converters to 25.71.0 (#29716) 2025-11-19 20:57:49 +00:00
Koen Kanters 73225e2436 fix(ignore): update zigbee-herdsman-converters to 25.70.0 (#29684) 2025-11-17 21:08:22 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Koen Kanters
afedab9680 chore(deps): bump js-yaml from 4.1.0 to 4.1.1 (#29675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-11-17 21:58:42 +01:00
Koen Kanters 53b7eaabf0 fix(ignore): update zigbee-herdsman to 6.4.2 (#29683) 2025-11-17 20:49:33 +00:00
Koen Kantersandburmistrzak 16d0e56968 fix(ignore): update zigbee-herdsman-converters to 25.69.0 (#29659)
Co-authored-by: burmistrzak <61958704+burmistrzak@users.noreply.github.com>
2025-11-17 20:11:05 +00:00
dependabot[bot] aea41b5c82 chore(deps): bump js-yaml from 4.1.0 to 4.1.1 (#29649) 2025-11-16 15:28:44 +01:00
Koen Kanters 564e9ab9eb fix(ignore): update zigbee-herdsman-converters to 25.68.0 (#29606) 2025-11-12 19:51:37 +00:00
Koen Kanters 61c6ec6111 fix(ignore): update zigbee-herdsman to 6.4.1 (#29595) 2025-11-11 21:09:25 +00:00
Koen Kanters 4794b3c81f fix(ignore): update zigbee-herdsman-converters to 25.67.0 (#29572) 2025-11-10 17:16:16 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 517b286583 fix(ignore): bump @biomejs/biome from 2.3.3 to 2.3.4 in the minor-patch group (#29571)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 18:09:42 +01:00
Louis Sautier aee173d10a chore: update build command in README (#29562) 2025-11-10 18:03:18 +01:00
Koen Kanters 940fff244c fix(ignore): update zigbee-herdsman-converters to 25.66.0 (#29558) 2025-11-09 20:50:48 +00:00
Koen Kanters 8822aedb5e fix(ignore): update zigbee-herdsman-converters to 25.65.0 (#29523) 2025-11-07 20:42:22 +00:00
Koen Kanters e788c520a9 fix(ignore): update zigbee-herdsman to 6.4.0 (#29522) 2025-11-07 20:37:54 +00:00
Koen Kanters 39c5bc8c04 fix(ignore): update zigbee-herdsman to 6.3.3 (#29500) 2025-11-06 21:53:56 +00:00
Koen Kanters 5c4e43fd10 fix(ignore): update zigbee-herdsman-converters to 25.64.0 (#29499) 2025-11-06 19:23:23 +00:00
Koen Kanters 5a6a9cf4d2 fix(ignore): update zigbee-herdsman-converters to 25.63.0 (#29482) 2025-11-05 20:18:35 +00:00
Koen Kanters ad0e91e61e chore: merge master to dev 2025-11-04 21:22:30 +01:00
Koen Kanters 5797b56871 chore: refactor issue bot 2025-11-04 21:22:09 +01:00
Koen Kanters ea1c5a8f4e fix(ignore): update zigbee-herdsman-converters to 25.62.0 (#29449) 2025-11-04 20:08:45 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 8099322cc2 fix(ignore): bump debounce from 2.2.0 to 3.0.0 (#29444)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 22:18:45 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 619b939b14 fix(ignore): bump the minor-patch group with 2 updates (#29443)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 22:18:32 +01:00
Koen Kanters eeed56ef2c fix(ignore): update zigbee-herdsman-converters to 25.60.0 (#29426) 2025-11-02 20:13:40 +00:00
github-actions[bot] e9b4a6221b chore: promote to dev 2025-11-01 21:04:00 +00:00
Koen Kanters 6e001b48ec chore(dev): release 2.6.3 (#28788) 2025-11-01 21:46:15 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> dc7749b57e fix(ignore): bump the minor-patch group across 1 directory with 4 updates (#29395)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 21:40:59 +01:00
Koen Kanters 40bf14b93c fix(ignore): update zigbee-herdsman-converters to 25.59.0 (#29382) 2025-11-01 20:26:36 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> b2c31f89c6 fix(ignore): bump @types/serve-static from 1.15.10 to 2.2.0 (#29394)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 20:50:11 +01:00
Koen Kanters faa65f78a2 fix(ignore): update zigbee-herdsman-converters to 25.57.0 (#29370) 2025-10-30 19:48:34 +00:00
Joseph Petersen c30630091d fix: Fix input onboarding environment variable disabled check (#29354) 2025-10-30 20:23:21 +01:00
Koen Kanters 61d99b7ece fix(ignore): update zigbee-herdsman-converters to 25.56.0 (#29338) 2025-10-28 20:23:32 +00:00
Koen Kanters e037a2c21c fix: Fix onEvent called twice first time when device options are changed (#29322) 2025-10-28 21:19:55 +01:00
Koen Kanters 592c256f96 fix(ignore): update zigbee-herdsman-converters to 25.55.0 (#29323) 2025-10-27 20:50:16 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 82f1092302 fix(ignore): bump the minor-patch group with 5 updates (#29314)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 20:58:01 +01:00
Koen Kanters 39e43d8f40 chore: merge master to dev 2025-10-27 20:43:36 +01:00
Koen Kanters 1ea7f650a2 chore: fix external converter/new device support bot 2025-10-27 20:43:11 +01:00
Koen Kanters 003c81b745 fix(ignore): update zigbee-herdsman-converters to 25.54.0 (#29306) 2025-10-26 21:28:39 +00:00
Koen Kanters 6c48498e0d fix: Require at least Node ^22.2.0 (#29285) 2025-10-25 20:39:14 +02:00
Koen Kanters e96630669a chore: merge master to dev 2025-10-25 20:33:06 +02:00
Koen Kanters 961265ad5e chore: extend external converter/new device bot to detected already support zigbee model 2025-10-25 20:32:39 +02:00
Koen Kanters 8ac2f55d87 fix: Require at least Node ^20.15.0 (#29284) 2025-10-25 20:14:58 +02:00
Koen Kanters 469d40c4aa chore: merge master to dev 2025-10-25 20:05:16 +02:00
Koen Kanters 5b844ea64e chore: fix external definition and new device support issue template render 2025-10-25 20:04:52 +02:00
Koen Kanters d719c1f368 fix(ignore): update zigbee-herdsman-converters to 25.53.0 (#29266) 2025-10-24 21:10:42 +00:00
Koen Kanters 0993652420 chore: merge master to dev 2025-10-23 21:01:31 +02:00
Koen Kanters 846b06ccab chore: update external converter template 2025-10-23 21:01:05 +02:00
Koen Kanters 3330d2aaed chore: merge master to dev 2025-10-23 20:52:40 +02:00
Koen Kanters bc650ccf82 chore: update external converter/new device support bot https://github.com/Nerivec/zigbee2mqtt-windfront/pull/298 2025-10-23 20:52:15 +02:00
Koen Kanters 968df11b16 chore: merge master to dev 2025-10-23 20:41:32 +02:00
Koen Kanters c4c129987d chore: Update and add new issue templates https://github.com/Nerivec/zigbee2mqtt-windfront/pull/298 2025-10-23 20:41:01 +02:00
Koen Kanters 05382a9692 fix(ignore): update zigbee-herdsman-converters to 25.52.0 (#29247) 2025-10-23 18:06:36 +00:00
Koen Kanters ef15924260 fix(ignore): update zigbee-herdsman to 6.3.2 (#29246) 2025-10-23 17:57:41 +00:00
Koen Kanters c0190c18ed fix: Home Assistant: fix device and group configuration URL (#29211) 2025-10-22 19:54:54 +02:00
Koen Kanters 238ea7c4c8 fix(ignore): update zigbee-herdsman-converters to 25.51.1 (#29212) 2025-10-21 19:28:14 +00:00
David Dreschner 174ba64449 fix: Home Assistant: make PI heating demand from writable (#29188) 2025-10-21 20:30:17 +02:00
Koen Kanters 408052282d fix(ignore): update zigbee-herdsman-converters to 25.51.0 (#29195) 2025-10-20 19:06:56 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d8fc69dac3 fix(ignore): bump @types/node from 24.7.2 to 24.8.1 in the minor-patch group (#29191)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 20:48:52 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 0da5fe6837 chore(deps): bump actions/setup-node from 5 to 6 (#29190)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 20:48:40 +02:00
Koen Kanters df70f11a15 fix(ignore): update zigbee-herdsman-converters to 25.50.0 (#29180) 2025-10-19 19:18:25 +00:00
Koen Kanters 3ecb0c5e41 fix(ignore): update zigbee-herdsman-converters to 25.49.0 (#29154) 2025-10-18 12:46:47 +00:00
Koen Kanters 52d250b72f chore: add OIDC permission for tokenless publising 2025-10-18 14:38:34 +02:00
Koen Kanters feac6aa677 fix(ignore): update zigbee-herdsman to 6.3.1 (#29153) 2025-10-18 12:30:56 +00:00
Koen Kanters 1d69715a5a fix(ignore): update zigbee-herdsman-converters to 25.48.0 (#29141) 2025-10-17 18:09:35 +00:00
Koen Kanters eaaed1fec7 fix: Rename ZigBee -> Zigbee (#29131) 2025-10-16 22:40:39 +02:00
Koen Kanters b81dcdbf40 chore: merge master to dev 2025-10-16 22:19:39 +02:00
Koen Kanters 129a80d1bb chore: update device support bot 2025-10-16 22:19:23 +02:00
Koen Kanters 03a81c6ab1 fix(ignore): update zigbee-herdsman-converters to 25.47.0 (#29130) 2025-10-16 19:00:35 +00:00
Koen Kanters 799f71b9b4 chore: merge master to dev 2025-10-16 20:47:32 +02:00
Koen Kanters 38d305eee6 chore: add new device support bot 2025-10-16 20:47:04 +02:00
Koen Kanters 354d06aebc fix(ignore): update zigbee-herdsman to 6.3.0 (#29113) 2025-10-15 19:49:31 +00:00
Koen KantersandDavid Dreschner ad9b114b1a fix(ignore): update zigbee-herdsman-converters to 25.46.0 (#29096)
Co-authored-by: David Dreschner <34408442+DerDreschner@users.noreply.github.com>
2025-10-15 19:20:31 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> aa916ad604 fix(ignore): bump the minor-patch group with 3 updates (#29068)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-14 22:01:32 +02:00
Koen Kanters bafc8e4284 fix(ignore): update zigbee-herdsman-converters to 25.44.0 (#29050) 2025-10-12 19:26:32 +00:00
Koen Kanters 9c19aa9494 fix(ignore): update zigbee-herdsman-converters to 25.43.0 (#29014) 2025-10-11 12:48:52 +00:00
Koen Kanters 85c894dc4f chore: Update AGENTS.md and copilot-instructions.md (#28966) 2025-10-08 22:11:10 +02:00
Koen Kanters 5f893c5657 chore: merge master to dev 2025-10-08 21:32:27 +02:00
Koen Kanters 8865429d79 chore: update wrong device template 2025-10-08 21:32:08 +02:00
Koen Kanters 1a77675043 chore: merge master to dev 2025-10-08 21:30:43 +02:00
Koen Kanters 8018422823 chore: update wrong device template 2025-10-08 21:30:22 +02:00
Koen Kanters 8b1f5a555c fix(ignore): update zigbee-herdsman-converters to 25.42.0 (#28963) 2025-10-08 19:07:11 +00:00
Koen Kanters 79a27e3b06 fix(ignore): update zigbee-herdsman-converters to 25.41.0 (#28945) 2025-10-07 20:30:50 +00:00
Koen Kanters 5f08c3e83b fix(ignore): update zigbee-herdsman-converters to 25.40.0 (#28928) 2025-10-06 18:48:38 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
e24ec79980 fix(ignore): bump the minor-patch group with 4 updates (#28916)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-10-06 20:10:21 +02:00
Nerivec d18dbb360d chore: add copilot prompts (#28919) 2025-10-06 20:09:58 +02:00
Koen Kanters 74335fbb68 fix: Remove Moment.js dependency (#28797) 2025-10-06 20:06:41 +02:00
Koen Kanters 8b4a4be2a4 fix(ignore): update zigbee-herdsman to 6.2.0 (#28900) 2025-10-05 19:56:17 +00:00
Koen Kanters 548f646f2f fix(ignore): update zigbee-herdsman-converters to 25.39.0 (#28899) 2025-10-05 19:28:13 +00:00
Koen Kanters a75a40b042 fix(ignore): update zigbee-herdsman-converters to 25.38.0 (#28872) 2025-10-04 12:42:17 +00:00
Dirk van Donkelaar 09fd675d0f fix: Add pi cooling demand to Home Assistant auto discovery (#28843) 2025-10-04 14:07:26 +02:00
Koen Kanters 1d38da6ac5 fix(ignore): update zigbee-herdsman-converters to 25.37.2 (#28859) 2025-10-03 19:18:52 +00:00
Koen Kanters c66008b36b fix(ignore): update zigbee-herdsman-converters to 25.37.1 (#28857) 2025-10-03 19:00:53 +00:00
Dirk van Donkelaar ab82f0e9a8 chore: Fix test command in CONTRIBUTING.md (#28844) 2025-10-03 19:47:43 +02:00
Koen Kanters d1ce146542 fix(ignore): update zigbee-herdsman-converters to 25.37.0 (#28837) 2025-10-02 19:34:49 +00:00
Koen Kanters cf93c649aa chore: vitest remove globals 2025-10-01 20:30:11 +02:00
Koen Kanters ca489daad8 fix(ignore): update zigbee-herdsman-converters to 25.36.0 (#28790) 2025-10-01 18:26:29 +00:00
github-actions[bot] 16f3f4d649 chore: promote to dev 2025-10-01 18:00:16 +00:00
Koen Kanters 4d247f3ae1 chore(dev): release 2.6.2 (#28441) 2025-10-01 19:47:06 +02:00
dependabot[bot] c9d01f4118 fix(ignore): bump the minor-patch group with 4 updates (#28778) 2025-10-01 19:36:13 +02:00
Koen Kanters 1283859a12 fix(ignore): update zigbee-herdsman-converters to 25.35.1 (#28774) 2025-09-30 19:36:38 +00:00
Koen Kanters a0d91cc063 fix(ignore): update zigbee-herdsman to 6.1.5 (#28773) 2025-09-30 18:53:15 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 116634193a fix(ignore): bump zigbee2mqtt-windfront from 2.1.0 to 2.2.0 in the minor-patch group (#28764)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 21:44:25 +02:00
Koen Kanters f6c0ce735f fix(ignore): update zigbee-herdsman-converters to 25.34.0 (#28763) 2025-09-29 19:30:13 +00:00
Koen Kanters 508318de57 fix(ignore): update zigbee-herdsman to 6.1.4 (#28762) 2025-09-29 19:06:13 +00:00
Koen Kanters 26c74b5778 fix(ignore): update zigbee-herdsman-converters to 25.33.0 (#28758) 2025-09-28 19:41:34 +00:00
Koen Kanters 87c474d2f5 fix(ignore): update zigbee-herdsman-converters to 25.32.0 (#28746) 2025-09-26 19:02:13 +00:00
Koen Kantersandnurikk 2c70deb411 fix(ignore): update zigbee2mqtt-frontend to 0.9.21 (#28745)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-09-26 18:45:24 +00:00
Koen Kanters a310627e19 fix(ignore): update zigbee-herdsman-converters to 25.31.0 (#28726) 2025-09-24 19:07:57 +00:00
Koen Kanters 77926bc1c3 fix(ignore): update zigbee-herdsman-converters to 25.30.0 (#28707) 2025-09-23 19:53:50 +00:00
Koen Kanters 892f92448e fix(ignore): update zigbee-herdsman to 6.1.3 (#28717) 2025-09-23 19:44:54 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e8847d1a34 fix(ignore): bump @types/node from 24.4.0 to 24.5.2 in the minor-patch group (#28700)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 21:53:43 +02:00
Koen Kanters 79c0c0933a fix(ignore): update zigbee-herdsman to 6.1.2 (#28689) 2025-09-21 13:25:20 +00:00
Jori HuismanandNerivec a5e1e8c0c3 fix: Handle empty or null Home Assistant name in device settings (#28364)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-09-21 14:54:18 +02:00
Koen Kanters 382d37e839 fix(ignore): update zigbee-herdsman-converters to 25.29.0 (#28654) 2025-09-18 18:47:04 +00:00
dependabot[bot] c3e1525f60 fix(ignore): bump the minor-patch group with 3 updates (#28638) 2025-09-15 22:29:20 +02:00
Koen Kanters 2cb7dcc12a fix(ignore): update zigbee-herdsman-converters to 25.26.0 (#28629) 2025-09-14 18:30:00 +00:00
Koen Kanters b2f74607cc fix(ignore): update zigbee-herdsman-converters to 25.25.0 (#28621) 2025-09-13 19:56:35 +00:00
Koen Kanters 2edf85f6e1 fix(ignore): update zigbee-herdsman-converters to 25.24.0 (#28608) 2025-09-12 19:12:00 +00:00
Koen Kanters 09f33b31a9 fix: Fix DatabaseEntry with ID 'X' does not exist when adding device to delete group ID (#28583) 2025-09-11 20:53:58 +02:00
Koen Kanters 3f258342d8 fix(ignore): update zigbee-herdsman-converters to 25.23.0 (#28598) 2025-09-11 18:41:41 +00:00
Koen Kanters c28ca79d8a fix(ignore): update zigbee-herdsman to 6.1.1 (#28597) 2025-09-11 18:23:33 +00:00
Koen Kanters 5f6fae0df7 fix(ignore): update zigbee-herdsman-converters to 25.22.0 (#28580) 2025-09-10 18:58:53 +00:00
Koen Kanters 15e00ea8a4 fix: HomeAssistant: add default_entity_id to discovery message (#28560) 2025-09-09 21:09:42 +02:00
Koen Kanters 01d5863487 fix(ignore): update zigbee-herdsman-converters to 25.21.0 (#28561) 2025-09-09 18:57:23 +00:00
Koen Kanters fefae9957c fix: Make windfront the default frontend (#28546) 2025-09-09 20:05:41 +02:00
Koen Kanters a297b9255b fix(ignore): update zigbee-herdsman-converters to 25.20.0 (#28545) 2025-09-08 19:03:46 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 86b458111b fix(ignore): bump the minor-patch group with 3 updates (#28541)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 20:43:58 +02:00
dependabot[bot] f022f55be6 chore(deps): bump actions/stale from 9 to 10 (#28539) 2025-09-08 20:20:46 +02:00
dependabot[bot] dc991be28f chore(deps): bump actions/github-script from 7 to 8 (#28540) 2025-09-08 20:20:30 +02:00
dependabot[bot] 62b62d224f chore(deps): bump actions/setup-node from 4 to 5 (#28538) 2025-09-08 20:08:57 +02:00
Koen Kanters 5f2630c93b fix(ignore): update zigbee-herdsman-converters to 25.19.0 (#28533) 2025-09-07 20:19:09 +00:00
Koen Kanters e8e4977327 fix(ignore): update zigbee-herdsman-converters to 25.18.0 (#28518) 2025-09-06 18:45:43 +00:00
Koen Kanters 54d729e37a fix(ignore): update zigbee-herdsman-converters to 25.17.1 (#28511) 2025-09-05 18:33:51 +00:00
Koen Kanters 3018e40dae fix(ignore): update zigbee-herdsman-converters to 25.17.0 (#28480) 2025-09-04 18:52:23 +00:00
Koen Kanters 1df1f5d388 chore: merge master to dev 2025-09-03 19:58:04 +02:00
Koen Kanters d44463a330 chore: fix copilot-instructions 2025-09-03 19:57:41 +02:00
Nerivec e52203a1b8 fix(ignore): update zigbee2mqtt-windfront to 2.1.0 (#28471) 2025-09-03 10:55:48 +00:00
Koen Kanters 073a261738 chore: merge master to dev 2025-09-02 21:00:50 +02:00
Koen Kanters 64ebb98ad1 chore: add copilot instructions 2025-09-02 21:00:30 +02:00
Koen Kanters 3f27f79d7d fix(ignore): update zigbee-herdsman-converters to 25.15.0 (#28462) 2025-09-02 18:57:50 +00:00
Koen Kanters 06862c7259 fix(ignore): update zigbee-herdsman to 6.1.0 (#28460) 2025-09-02 18:00:00 +00:00
github-actions[bot] 0482d8b4e9 chore: promote to dev 2025-09-01 19:14:09 +00:00
Koen Kanters e9889e5d20 chore(dev): release 2.6.1 (#28144) 2025-09-01 20:57:13 +02:00
Koen Kanters 77886530fe fix(ignore): update zigbee-herdsman to 6.0.4 (#28438) 2025-09-01 18:52:39 +00:00
Koen Kanters bf22ddac46 fix(ignore): update zigbee-herdsman-converters to 25.14.0 (#28439) 2025-09-01 18:18:38 +00:00
Koen Kanters 203019aa26 fix(ignore): update zigbee-herdsman-converters to 25.13.0 (#28427) 2025-08-31 13:14:19 +00:00
Nerivec 08903f68d9 fix(ignore): update zigbee2mqtt-windfront to 2.0.1 (#28420) 2025-08-30 18:57:33 +00:00
Koen Kanters bdeab61001 fix(ignore): update zigbee-herdsman-converters to 25.12.0 (#28417) 2025-08-30 12:03:46 +00:00
Koen Kanters c07fce936f fix(ignore): update zigbee-herdsman-converters to 25.11.0 (#28407) 2025-08-28 18:31:42 +00:00
Koen Kanters 264440ebcc fix(ignore): update zigbee-herdsman to 6.0.2 (#28406) 2025-08-28 18:19:11 +00:00
Koen Kanters 32d82651e2 fix(ignore): update zigbee-herdsman-converters to 25.10.0 (#28399) 2025-08-27 18:29:45 +00:00
NerivecandKoen Kanters 9079c16d16 chore: switch benchmarking tool (#28396)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-08-27 20:12:30 +02:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
a8fd23b6cc fix(ignore): bump the minor-patch group with 2 updates (#28383)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-08-27 19:58:35 +02:00
Nerivec 2df363f3af fix: update zigbee2mqtt-windfront to 2.0.0 (#28389) 2025-08-26 15:47:44 +00:00
Koen Kanters 0147441c93 fix(ignore): update zigbee-herdsman-converters to 25.9.0 (#28380) 2025-08-25 20:49:30 +02:00
Koen Kanters 4d52fdd290 chore: Update changelog generation to include zigbee2mqtt-windfront (#28345) 2025-08-25 20:42:08 +02:00
Koen Kanters 8e6ba7975b fix(ignore): update zigbee-herdsman-converters to 25.8.0 (#28373) 2025-08-24 19:23:32 +00:00
Koen Kanters 7bd7bce69c fix(ignore): update zigbee-herdsman-converters to 25.7.0 (#28361) 2025-08-22 14:36:25 +00:00
Koen Kanters 4bd0c04bdc fix(ignore): update zigbee-herdsman-converters to 25.6.0 (#28344) 2025-08-20 18:50:40 +00:00
Joseph Spiros ccfab84e7f fix: Home Assistant: use "occupancy" device_class for human presence sensors (#28329) 2025-08-20 20:14:23 +02:00
Koen Kanters 23f831a1fe fix(ignore): update zigbee-herdsman-converters to 25.5.0 (#28328) 2025-08-19 18:35:14 +00:00
Koen KantersandNerivec 384bd309a6 fix(ignore): update zigbee-herdsman-converters to 25.4.0 (#28313)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-08-18 21:33:42 +02:00
Koen KantersandNerivec 5f7c00515a fix(ignore): update zigbee-herdsman-converters to 25.3.0 (#28301)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-08-18 21:33:42 +02:00
Koen Kanters bed8beccbc fix(ignore): update zigbee-herdsman to 6.0.1 (#28287) 2025-08-18 21:33:42 +02:00
Nerivec b21f926b3e fix: Support for new ZH typing (#28257) 2025-08-18 21:33:42 +02:00
Nerivec 6e7cc30da7 chore: skip unnecessary deps in dev-types branch (#28312) 2025-08-18 21:22:02 +02:00
Koen Kanters 4c9784255b fix(ignore): update zigbee-herdsman-converters to 25.2.0 (#28280) 2025-08-15 10:44:18 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
1dd6cc3da5 fix(ignore): bump the minor-patch group with 5 updates (#28237)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-08-14 20:52:49 +02:00
Nerivec d04ab53f1d fix(ignore): update zigbee2mqtt-windfront to 1.8.1 (#28272) 2025-08-14 10:52:50 +00:00
Nerivec f4cfcbacde fix(ignore): update zigbee2mqtt-windfront to 1.8.0 (#28266) 2025-08-13 22:52:18 +00:00
Koen Kanters 2b1780879a fix(ignore): update zigbee-herdsman-converters to 25.1.0 (#28264) 2025-08-13 19:39:33 +00:00
Koen Kantersandnurikk 800b85c812 fix(ignore): update zigbee2mqtt-frontend to 0.9.20 (#28250)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-08-12 18:09:26 +00:00
Alexis "Horgix" Chotard c5fa13215c chore(github): fix link to "fail to start" FAQ in problem report template (#28244) 2025-08-12 19:42:19 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 6414ade05d chore(deps): bump actions/checkout from 4 to 5 (#28238)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 19:36:31 +02:00
Koen Kanters 5286570bd6 fix: Update to refactored zhc api and fix onEvent(stop) not being called when device is force removed (#27984) 2025-08-11 20:32:59 +02:00
Koen Kanters 3160219824 fix(ignore): update zigbee-herdsman-converters to 25.0.0 (#28236) 2025-08-11 20:26:49 +02:00
Koen Kanters bd1be88602 fix(ignore): update zigbee-herdsman to 6.0.0 (#28235) 2025-08-11 20:26:07 +02:00
Nerivec a102fac2c8 fix: Set minimum for some settings (#28209) 2025-08-09 14:40:27 +02:00
Nerivec 63a4207061 fix: Always unlink node_modules symlinks on stop (#28202) 2025-08-08 19:38:04 +02:00
Nerivec 092c95b716 fix(ignore): update zigbee2mqtt-windfront to 1.6.0 (#28207) 2025-08-08 10:55:58 +00:00
Koen Kanters 09f046c72d fix(ignore): update zigbee-herdsman-converters to 24.14.0 (#28199) 2025-08-07 18:28:28 +00:00
Kamil Breguła b7cbd9b5d8 fix: Home Assistant: set icon and state class for orientation sensors (#28192) 2025-08-07 19:29:22 +02:00
Koen Kanters e12add17be chore: merge master to dev 2025-08-06 20:04:47 +02:00
dependabot[bot] bda5bf9e96 chore(deps-dev): bump tmp from 0.2.3 to 0.2.4 (#28186) 2025-08-06 20:04:33 +02:00
Koen Kanters 2056b89bac fix(ignore): update zigbee-herdsman-converters to 24.13.0 (#28175) 2025-08-05 19:04:52 +00:00
Koen Kanters d698770169 Revert "Revert "chore: cleanup Dockerfile (#28166)""
This reverts commit b41e3e781e.
2025-08-05 19:44:31 +02:00
Koen Kanters b41e3e781e Revert "chore: cleanup Dockerfile (#28166)"
This reverts commit e8cec3542f.
2025-08-04 21:40:19 +02:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>NerivecKoen Kanters
b72abd675f fix(ignore): bump the minor-patch group with 4 updates (#28147)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-08-04 21:32:51 +02:00
Koen Kanters 9836f0e27a fix(ignore): update zigbee-herdsman-converters to 24.12.0 (#28170) 2025-08-04 19:27:39 +00:00
NerivecandKoen Kanters e8cec3542f chore: cleanup Dockerfile (#28166)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-08-04 20:53:07 +02:00
krkk 050ebfc9f5 fix: Remove 60 MB (28%) of dev dependencies from Docker image (#28160) 2025-08-04 20:36:44 +02:00
github-actions[bot] 449b99b0c0 chore: promote to dev 2025-08-01 15:52:58 +00:00
Koen Kanters 0bdc3fd1f7 chore(dev): release 2.6.0 (#27881) 2025-08-01 17:37:30 +02:00
Koen Kanters 376554943d fix(ignore): update zigbee-herdsman-converters to 24.11.0 (#28143) 2025-08-01 15:22:20 +00:00
Koen Kanters 9fcc41b199 fix(ignore): update zigbee-herdsman to 5.0.4 (#28141) 2025-08-01 15:04:45 +00:00
Nerivec fe5639ce40 fix(ignore): update zigbee2mqtt-windfront to 1.5.2 (#28140) 2025-08-01 14:56:52 +00:00
Rob van OostenrijkandNerivec 0182c4b32e fix: Ignore symbolic links in data during backup (#28132)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-07-31 20:26:36 +02:00
Koen Kanters c12f07c5a0 chore: don't run codspeed on release-please prs 2025-07-30 21:34:29 +02:00
Nerivec 31d3e19f38 fix(ignore): update zigbee2mqtt-windfront to 1.5.1 (#28123) 2025-07-30 19:06:35 +00:00
Koen Kanters ee1c884031 fix(ignore): update zigbee-herdsman-converters to 24.10.0 (#28122) 2025-07-30 18:55:55 +00:00
Koen Kanters e5f677bdd1 fix(ignore): update zigbee-herdsman to 5.0.3 (#28121) 2025-07-30 18:48:26 +00:00
Koen Kanters 6dd812f71c fix(ignore): fix 71cf2b4160 2025-07-29 20:47:51 +02:00
Koenkk 71cf2b4160 fix(ignore): update zigbee-herdsman-converters to 24.9.0 2025-07-29 18:34:40 +00:00
Koen Kanters e9f2a3f532 fix(ignore): update zigbee-herdsman to 5.0.2 (#28110) 2025-07-29 18:34:03 +00:00
Nerivec 0725374db7 chore: update readme (#28085) 2025-07-27 13:01:47 +02:00
Nerivec 6f7c6eec04 chore: import vitest instead of relying on globals (#28084) 2025-07-26 20:05:13 +02:00
Nerivec 29e56adc15 fix(ignore): update zigbee2mqtt-windfront to 1.5.0 (#28082) 2025-07-26 17:00:38 +00:00
Koen KantersandNerivec caf91cd7ff fix(ignore): update zigbee-herdsman-converters to 24.8.0 (#28081)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-07-26 16:37:46 +00:00
Koen Kanters 6432571c61 fix(ignore): update zigbee-herdsman to 5.0.1 (#28080) 2025-07-26 11:13:06 +00:00
Koen Kanters 713551b525 fix(ignore): fix breaking changes of https://github.com/Koenkk/zigbee2mqtt/pull/28077 2025-07-25 20:30:48 +02:00
Koen Kanters 9a215efe26 fix(ignore): update zigbee-herdsman to 5.0.0 (#28077) 2025-07-25 18:25:44 +00:00
Nerivec 65eef1b690 fix: Publish definition source in bridge/devices (#28076) 2025-07-25 19:22:17 +02:00
Koen Kanters 81bafa5520 fix(ignore): update zigbee-herdsman-converters to 24.7.0 (#28070) 2025-07-24 18:37:06 +00:00
Nerivec d322dc2047 fix(ignore): update zigbee2mqtt-windfront to 1.4.1 (#28062) 2025-07-23 19:37:34 +00:00
Nerivec 5f9019a95f fix: Publish endpoint names in bridge/devices (#28057) 2025-07-23 20:12:16 +02:00
Koen Kanters 4bc6a59ff3 fix(ignore): update zigbee-herdsman-converters to 24.6.0 (#28051) 2025-07-22 18:26:50 +00:00
dependabot[bot]dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Nerivec
e505a93a40 fix(ignore): bump the minor-patch group with 4 updates (#28046)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-07-22 19:58:18 +02:00
Koen Kanters bc74d0b11b fix(ignore): update zigbee-herdsman-converters to 24.5.0 (#28045) 2025-07-21 18:23:20 +00:00
Koen Kanters cc6f7b02bc fix(ignore): update zigbee-herdsman to 4.5.0 (#28044) 2025-07-21 18:18:32 +00:00
Koen Kanters 74e9357a59 fix(ignore): update zigbee-herdsman-converters to 24.4.0 (#28022) 2025-07-18 20:13:44 +00:00
Koen Kantersandnurikk 8b876823da fix(ignore): update zigbee2mqtt-frontend to 0.9.19 (#28021)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-07-18 22:05:48 +02:00
Nerivec db20369447 fix: Various fixes to align behaviors (+add benchmarks for dev) (#28011) 2025-07-18 21:34:51 +02:00
Koen Kanters 0e0a5618af fix: Exit with code 2 when adapters disconnects (#28013) 2025-07-18 20:32:39 +02:00
Koen Kanters 21fa5cbded fix(ignore): update zigbee-herdsman-converters to 24.3.0 (#28015) 2025-07-17 12:28:32 +00:00
Koen Kanters e2b7df9f84 fix(ignore): Remove retain/qos from mqtt publish logging https://github.com/Koenkk/zigbee2mqtt/pull/27983 2025-07-15 13:56:17 +02:00
Donald FlissingerandKoen Kanters ef07505b31 fix: Set MQTT retain and qos=1 on (re)connect (#27983)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-07-15 13:29:57 +02:00
Nerivec 435a6cceb9 fix: Improve startup settings validation (#27988) 2025-07-15 13:15:11 +02:00
Nerivec a51216e3c7 fix(ignore): update zigbee2mqtt-windfront to 1.3.1 (#27999) 2025-07-15 10:55:37 +00:00
Koen Kanters 6ede403688 fix(ignore): update zigbee-herdsman-converters to 24.2.0 (#27986) 2025-07-14 11:49:06 +00:00
NerivecandKoen Kanters ecadda45e7 fix: Fix settings schema defaults (#27961)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-07-12 18:36:56 +02:00
Koen Kanters a837702b2c fix(ignore): update zigbee-herdsman-converters to 24.1.0 (#27973) 2025-07-12 12:40:05 +00:00
Koen Kanters 57e04bb547 chore: merge master to dev 2025-07-12 13:36:44 +02:00
Koen Kanters ffdd353e16 chore: Add device database.db entry to problem report template 2025-07-12 13:36:24 +02:00
Nerivec 0215e76118 feat: Allow to disable internal frontend UI serving for standalone serving (#27851) 2025-07-12 09:33:26 +02:00
Nerivec 833605b71f fix(ignore): add test to specifically check HA envs (#27969) 2025-07-11 19:53:05 +02:00
Boris Conforty f4d821deca fix: log error message when external JS fails to load (#27837) 2025-07-11 17:36:06 +02:00
Koen Kanters c465ef8352 fix(ignore): update zigbee-herdsman-converters to 24.0.0 (#27946) 2025-07-10 13:06:50 +00:00
Koen Kanters 0dd4b6a991 chore: fix Docker Dependabot 2025-07-10 14:45:45 +02:00
Nerivec 1b37baefd3 fix(ignore): update zigbee2mqtt-windfront to 1.3.0 (#27953) 2025-07-09 22:37:02 +00:00
Koen Kanters b4a9424d60 fix(ignore): update zigbee-herdsman-converters to 23.72.0 (#27943) 2025-07-09 12:48:46 +00:00
Koen Kanters 93c1824b1d fix(ignore): update zigbee-herdsman-converters to 23.71.0 (#27931) 2025-07-08 11:27:01 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 8ceb08704f fix(ignore): bump the minor-patch group with 2 updates (#27924)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-07 20:47:07 +02:00
Koen Kanters eb82973067 fix(ignore): update zigbee-herdsman to 4.4.1 (#27918) 2025-07-06 19:21:12 +00:00
Koen Kantersandnurikk a3e7f69b3f fix(ignore): update zigbee2mqtt-frontend to 0.9.17 (#27917)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-07-06 21:15:40 +02:00
Koen Kanters 373fa43136 chore: change dependabot versioning strategy to increase 2025-07-06 21:13:57 +02:00
Koen Kanters e04732462c fix(ignore): update zigbee-herdsman-converters to 23.70.1 (#27916) 2025-07-06 19:01:12 +00:00
Koen Kanters e663e86e09 fix(ignore): update zigbee-herdsman to 4.4.0 (#27906) 2025-07-04 18:37:18 +00:00
Koen Kanters 163a15d664 fix(ignore): update zigbee-herdsman-converters to 23.70.0 (#27905) 2025-07-04 20:34:17 +02:00
Koen Kanters 4afdd4e892 fix(ignore): update zigbee-herdsman to 4.3.2 (#27900) 2025-07-04 11:47:05 +00:00
Nerivec 190cfe99ac fix: erroneous Health OS memory percent calculation (#27892) 2025-07-03 16:31:03 +02:00
github-actions[bot] c9284f6b33 chore: promote to dev 2025-07-02 19:48:07 +00:00
Koen Kanters 5063c7d3ad chore(dev): release 2.5.1 (#27879) 2025-07-02 21:36:29 +02:00
Koen Kanters 4dd7dabf08 fix: Various Windfront frontend improvements @nerivec (#27880) 2025-07-02 21:35:07 +02:00
Koen Kanters 917d8527a5 fix(ignore): update zigbee-herdsman to 4.3.1 (#27878) 2025-07-02 19:15:36 +00:00
github-actions[bot] f307467e37 chore: promote to dev 2025-07-01 18:38:36 +00:00
Koen Kanters c0cd2c6b96 chore(dev): release 2.5.0 (#27573) 2025-07-01 20:27:23 +02:00
Koen Kanters 4a4ee8888d chore: fix ci not triggering on tag 2025-07-01 20:27:16 +02:00
Koen Kanters 5285b7b95e fix(ignore): update zigbee-herdsman-converters to 23.69.0 (#27860) 2025-07-01 17:53:36 +00:00
dependabot[bot] c8dc26b241 fix(ignore): bump the minor-patch group across 1 directory with 4 updates (#27858) 2025-07-01 17:59:28 +02:00
Koen Kanters 25ea77e038 chore: fix ci not running for pull request 2025-06-30 21:05:47 +02:00
Koen Kanters 50c7d2d6ad fix(ignore): update zigbee-herdsman to 4.3.0 (#27853) 2025-06-30 18:40:20 +00:00
Koen Kanters 3ccbaf21d2 fix(ignore): update zigbee-herdsman-converters to 23.68.0 (#27852) 2025-06-30 18:35:49 +00:00
Koen Kanters 6930fa6f3f fix(ignore): update zigbee-herdsman-converters to 23.67.0 (#27842) 2025-06-29 19:06:51 +00:00
Koen Kanters 16faef1511 fix(ignore): update zigbee-herdsman to 4.2.1 (#27835) 2025-06-28 08:24:18 +00:00
Koen Kanters c87d4524d5 fix(ignore): Migrate to Biome 2 (#27785) 2025-06-28 10:20:41 +02:00
Koen Kanters 33234d9ce4 fix(ignore): update zigbee-herdsman-converters to 23.66.0 (#27831) 2025-06-27 18:49:45 +00:00
Koen Kanters 4190ba43b4 fix(ignore): update zigbee-herdsman-converters to 23.65.0 (#27804) 2025-06-24 20:02:56 +00:00
Koen Kanters b7d9c78309 fix(ignore): update zigbee-herdsman-converters to 23.64.0 (#27799) 2025-06-23 18:51:22 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 50b0ffd7e7 fix(ignore): bump the minor-patch group with 2 updates (#27797)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-23 20:35:51 +02:00
Koen Kanters 15f3eaf966 fix(ignore): update zigbee-herdsman to 4.2.0 (#27786) 2025-06-22 20:14:19 +00:00
Koen Kanters 0d5de750ff fix(ignore): update zigbee-herdsman-converters to 23.63.0 (#27784) 2025-06-22 19:32:39 +00:00
Koen Kantersandnurikk 7109900bcf fix(ignore): update zigbee2mqtt-frontend to 0.9.16 (#27783)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-06-22 19:26:13 +00:00
Koen Kanters 281e10c15f fix(ignore): update zigbee-herdsman-converters to 23.62.0 (#27767) 2025-06-19 18:23:04 +00:00
Nerivec cdad6ccd75 fix(ignore): update zigbee2mqtt-windfront to 1.1.0 (#27763) 2025-06-19 19:49:25 +02:00
Koen Kanters b537f9a93a fix(ignore): update zigbee-herdsman-converters to 23.61.0 (#27759) 2025-06-18 19:57:08 +00:00
Koen Kanters 1b56e63aab chore: Use commit SHA instead of Update in dev-types commit message https://github.com/Koenkk/zigbee2mqtt/pull/27746 2025-06-18 20:00:38 +02:00
Koen Kanters f33d3606be fix(ignore): update zigbee-herdsman-converters to 23.60.0 (#27752) 2025-06-17 18:25:09 +00:00
Koen Kanters fb60cfde38 chore: Publish types (#27746) 2025-06-17 20:20:34 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> de800450ad fix(ignore): bump @types/node from 22.15.30 to 24.0.3 (#27745)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 19:52:03 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 977672976f fix(ignore): bump zigbee2mqtt-windfront from 1.0.5 to 1.0.6 in the minor-patch group (#27744)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 19:51:52 +02:00
NerivecandKoen Kanters cd9b752ede feat: New health extension & extras in bridge/info (#27164)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-06-16 19:45:36 +02:00
Koen Kanters 242815e139 fix(ignore): update zigbee-herdsman to 4.1.2 (#27729) 2025-06-15 11:47:16 +00:00
Koen Kanters 87ff0f3711 fix(ignore): update zigbee-herdsman-converters to 23.59.0 (#27712) 2025-06-13 18:45:44 +00:00
Koen Kantersandnurikk 8ecb9be98e fix(ignore): update zigbee2mqtt-frontend to 0.9.15 (#27711)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-06-13 18:23:04 +00:00
NerivecandKoen Kanters 0e66b29983 fix: Generate group ID if empty string given (#27702)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-06-12 20:28:55 +02:00
Koen Kanters 6a8d20858e fix(ignore): update zigbee-herdsman-converters to 23.58.0 (#27695) 2025-06-11 18:14:46 +00:00
Koen Kanters e4b253e08b fix: Fix devices not immediately marked as offline when timeout expires (#27691) 2025-06-11 19:48:52 +02:00
Koen Kanters 56d3a8f9d8 fix(ignore): update zigbee-herdsman-converters to 23.57.1 (#27689) 2025-06-10 19:10:57 +00:00
Koen Kanters f6c3887c81 fix: Bump Alpine to 3.22 (#27690) 2025-06-10 21:05:39 +02:00
Koen Kanters 2affe188c5 chore: bump pnpm 2025-06-10 20:25:01 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 587a02f663 fix(ignore): bump the minor-patch group with 6 updates (#27678)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-10 20:09:49 +02:00
Koen Kanters 0181263d32 fix: Rename fix typo in backoff settings https://github.com/Koenkk/zigbee2mqtt/issues/27642 2025-06-08 19:34:05 +02:00
Nerivec eaa3e458b5 fix(ignore): update zigbee2mqtt-windfront to 1.0.5 (#27656) 2025-06-08 18:33:34 +02:00
Koen Kanters 252bfbaa20 fix(ignore): update zigbee-herdsman-converters to 23.57.0 (#27646) 2025-06-07 17:27:45 +00:00
Koen Kantersandnurikk 66e1318a79 fix(ignore): update zigbee2mqtt-frontend to 0.9.14 (#27645)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-06-07 17:23:03 +00:00
Nerivec 9a4ce6fcd0 fix: update zigbee2mqtt-windfront to 1.0.4 (#27641) 2025-06-06 23:57:37 +02:00
Koen Kanters 4b95b54be7 fix(ignore): update zigbee-herdsman to 4.1.1 (#27640) 2025-06-06 20:13:33 +00:00
Koen Kanters f637e7d59c fix(ignore): update zigbee-herdsman-converters to 23.56.0 (#27639) 2025-06-06 19:41:31 +00:00
Nerivec 018fad27af fix(ignore): API correctness (#27637) 2025-06-06 20:01:07 +02:00
Koen Kanters 4594142c09 fix(ignore): update zigbee-herdsman-converters to 23.55.0 (#27618) 2025-06-04 18:16:29 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ac80bb6430 fix(ignore): bump the minor-patch group with 4 updates (#27610)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 21:15:11 +02:00
Koen Kanters 1765fe8925 fix(ignore): update zigbee-herdsman-converters to 23.54.0 (#27600) 2025-06-02 18:11:51 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7e82a3d1e9 fix(ignore): bump the minor-patch group with 3 updates (#27598)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 19:51:26 +02:00
github-actions[bot] 9df6cccd31 chore: promote to dev 2025-06-01 18:18:38 +00:00
Koen Kanters bdb94da46e chore(dev): release 2.4.0 (#27277) 2025-06-01 20:06:39 +02:00
Koen Kanters e0b0248172 fix(ignore): update zigbee-herdsman-converters to 23.53.0 (#27572) 2025-06-01 18:00:44 +00:00
Nerivec 5e44cf77b3 fix(ignore): update zigbee2mqtt-windfront to 1.0.2 (#27567) 2025-06-01 19:43:23 +02:00
Nerivec 621cae2a63 fix(ignore): networkmap typing (#27566) 2025-06-01 19:42:24 +02:00
Koen Kanters 711b456b06 fix(ignore): update zigbee-herdsman-converters to 23.52.1 (#27563) 2025-05-31 11:48:01 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 278e3fb40e fix(ignore): bump the minor-patch group across 1 directory with 2 updates (#27562)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-31 13:42:59 +02:00
Koen Kanters 6ed18c757d fix(ignore): update zigbee-herdsman-converters to 23.52.0 (#27559) 2025-05-31 11:38:28 +00:00
Koen Kanters 1ca24700f2 chore: merge master to dev 2025-05-31 12:34:46 +02:00
Koen Kanters cefc1dbf8d chore: update dependabot config 2025-05-31 12:34:21 +02:00
Nerivec a5e4e9ef61 fix: API missing a few request endpoints (#27551) 2025-05-30 19:18:28 +02:00
Koen Kanters f5920fd53b fix(ignore): update zigbee-herdsman-converters to 23.51.0 (#27540) 2025-05-28 18:43:03 +00:00
Koen Kanters a289f981bf fix(ignore): Fix passive states not updating in frontend (#27532) 2025-05-28 19:28:52 +02:00
Koen Kanters 00dda245e4 fix(ignore): update zigbee-herdsman-converters to 23.50.1 (#27531) 2025-05-27 19:09:21 +00:00
Koen Kanters 1960e026d2 fix(ignore): update zigbee-herdsman-converters to 23.50.0 (#27521) 2025-05-26 19:15:36 +00:00
Ilya Kirov 4bf45630e2 fix: HA integration for external sensors Aqara W100 (#27520) 2025-05-26 21:09:43 +02:00
Ilya Kirov ed03c40dad fix: Fix import error on Windows (ERR_UNSUPPORTED_ESM_URL_SCHEME) (#27519) 2025-05-26 20:32:45 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 02b08834e4 chore(deps-dev): bump the minor-patch group with 4 updates (#27518)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-26 20:31:28 +02:00
Nerivec 1b85968e5e feat: WindFront new frontend support (#27489) 2025-05-26 20:14:34 +02:00
Koen Kanters ea49d1ae44 fix(ignore): update zigbee-herdsman-converters to 23.49.0 (#27479) 2025-05-22 18:40:25 +00:00
Dustin Utecht a085b92c68 chore: Fixed brokenzStackEraseAllNvMem script (#27473) 2025-05-22 20:18:52 +02:00
Koen Kanters 1303340aaf fix(ignore): update zigbee-herdsman-converters to 23.48.0 (#27470) 2025-05-21 20:00:09 +00:00
Koen Kanters 693f0d0a35 fix(ignore): update zigbee-herdsman-converters to 23.47.0 (#27463) 2025-05-20 20:01:26 +00:00
Koen Kanters 3edcfd7ff6 fix(ignore): update zigbee-herdsman to 4.1.0 (#27462) 2025-05-20 19:32:54 +00:00
Koen Kanters 0ead302c0a fix: Don't copy external JS to dist folder (#27397) 2025-05-19 21:01:51 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 418991386e chore(deps): bump the minor-patch group with 2 updates (#27448)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 20:32:14 +02:00
Koen Kanters 7449c2357c fix(ignore): update zigbee-herdsman-converters to 23.46.0 (#27444) 2025-05-18 18:17:55 +00:00
Koen Kanters 8c3252c213 fix(ignore): update zigbee-herdsman-converters to 23.45.0 (#27422) 2025-05-16 20:19:33 +00:00
Koen Kanters 21c9faffaf fix(ignore): update zigbee-herdsman-converters to 23.44.0 (#27404) 2025-05-14 19:04:59 +00:00
Koen Kanters e0611269f3 fix(ignore): update zigbee-herdsman-converters to 23.43.0 (#27394) 2025-05-13 18:26:20 +00:00
Koen Kanters edcfe32572 fix(ignore): update zigbee-herdsman-converters to 23.42.0 (#27392) 2025-05-12 19:46:59 +00:00
Nerivec d697c29c7b fix: Rerun onboarding if Z2M start failed after previous onboarding (#27386) 2025-05-12 21:33:55 +02:00
dependabot[bot] ba41985667 chore(deps): bump humanize-duration from 3.32.1 to 3.32.2 in the minor-patch group (#27388) 2025-05-12 19:32:50 +02:00
Koen Kantersandnurikk f656d9629c fix(ignore): update zigbee2mqtt-frontend to 0.9.13 (#27382)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-05-11 20:00:25 +00:00
Koen Kanters cc3521c877 fix(ignore): update zigbee-herdsman to 4.0.2 (#27380) 2025-05-11 19:31:37 +00:00
Koen Kantersandnurikk 615c53c93c fix(ignore): update zigbee2mqtt-frontend to 0.9.12 (#27379)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-05-11 13:11:10 +00:00
Koen Kanters f7e50519e3 fix(ignore): update zigbee-herdsman-converters to 23.41.0 (#27371) 2025-05-10 12:37:58 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e09d5fd526 chore(deps): bump express-static-gzip from 2.2.0 to 3.0.0 (#27369)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-10 14:27:11 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 8ef01fd9b1 chore(deps): bump the minor-patch group with 2 updates (#27368)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-10 14:26:52 +02:00
Koen Kanters 19116995bb chore: merge master to dev 2025-05-10 14:18:22 +02:00
Koen Kanters 9d1427c104 chore: Add target-branch for all Dependabot entries 2025-05-10 14:17:59 +02:00
Koen Kanters 317c9b4046 fix(ignore): update zigbee-herdsman-converters to 23.40.1 (#27358) 2025-05-08 18:46:32 +00:00
Koen Kanters aad87cc3ec fix: Drop Node 18 support, add 24 (#27348) 2025-05-07 21:26:13 +02:00
Koen Kantersandbo0tzz d8855b8e6f fix: Fix bind/unbind by coordinator ieeeAddr not working (#27333)
Co-authored-by: bo0tzz <git@bo0tzz.me>
2025-05-07 21:15:05 +02:00
Koen Kanters 7a4f2869b9 fix(ignore): update zigbee-herdsman-converters to 23.40.0 (#27347) 2025-05-07 19:01:10 +00:00
bo0tzzandKoen Kanters 40652d8a8f chore: feature requests don't go stale (#27336)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-05-07 20:42:51 +02:00
Koen Kanters 1d6ba5393a fix(ignore): update zigbee-herdsman-converters to 23.39.0 (#27340) 2025-05-06 20:10:28 +00:00
Koen Kanters 69364c958f fix: Trim name on rename (#27332) 2025-05-06 21:49:24 +02:00
Koen Kanters 164c3222b9 fix(ignore): update zigbee-herdsman-converters to 23.38.0 (#27329) 2025-05-05 18:03:22 +00:00
Koen Kanters a8ba7eb552 chore: merge master to dev 2025-05-05 19:51:41 +02:00
Koen Kanters 5ff2148016 chore: fully switch to Dependabot 2025-05-05 19:51:23 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> bca4bfa074 chore(deps): bump the minor-patch group with 3 updates (#27327)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 19:36:30 +02:00
0xdefec71f 3cbcc4a0ab fix: Improve update script (#27308) 2025-05-04 14:43:22 +02:00
Koen Kanters ff823dbaf0 fix(ignore): update zigbee-herdsman-converters to 23.37.0 (#27300) 2025-05-03 06:56:48 +00:00
Koen Kantersandnurikk 3cb63aa922 fix(ignore): update zigbee2mqtt-frontend to 0.9.11 (#27301)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-05-03 06:50:40 +00:00
Koen Kanters b7b92da8ff fix(ignore): Cleanup publish options (#27226) 2025-05-03 08:45:45 +02:00
Koen Kanters 39e4468308 fix: Only send JSON state over WebSocket (#27225) 2025-05-03 08:42:07 +02:00
github-actions[bot] 908ecb3e2a chore: promote to dev 2025-05-01 18:34:36 +00:00
Koen Kanters aec59cb774 chore(dev): release 2.3.0 (#26991) 2025-05-01 20:16:51 +02:00
Koen Kanters 961a82e213 fix(ignore): update zigbee-herdsman-converters to 23.36.0 (#27276) 2025-05-01 17:39:38 +00:00
Nerivec c36a177916 fix(ignore): API typing fixes. (#27266) 2025-04-30 21:47:09 +02:00
Koen Kanters 97dbe1e8af fix(ignore): update zigbee-herdsman-converters to 23.35.0 (#27253) 2025-04-28 19:26:46 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 60808b4d36 chore(deps): bump the minor-patch group with 2 updates (#27251)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-28 21:05:05 +02:00
Nerivec 7cbf31a129 fix(ignore): Group API proper typing (#27243) 2025-04-28 21:01:14 +02:00
Nerivec 5304b0ceb7 fix(ignore): Add missing API request endpoints (#27242) 2025-04-27 15:24:58 +00:00
Nerivec 9a9f8d1438 fix(ignore): API exporting issues (#27211) 2025-04-26 21:40:17 +02:00
Koen Kanters a5135168c8 fix(ignore): update zigbee-herdsman-converters to 23.34.0 (#27224) 2025-04-26 19:39:54 +00:00
Koen Kanters c1b62e83ef fix(ignore): update zigbee-herdsman-converters to 23.33.0 (#27202) 2025-04-23 19:21:24 +00:00
andy-vdg 4acbdd7685 fix: Add icon example to settings schema (#27194) 2025-04-23 21:05:59 +02:00
Koen Kantersandnurikk 3ad20ddcdc fix(ignore): update zigbee2mqtt-frontend to 0.9.10 (#27191)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-04-22 19:32:43 +00:00
Koen Kanters 69f9002e31 fix: Add interview state (#27163) 2025-04-22 21:27:07 +02:00
Koenkk dfeaf22f70 fix(ignore): update zigbee-herdsman to 4.0.0 2025-04-22 21:27:07 +02:00
Koen Kanters faa6ad9ce8 fix(ignore): update zigbee-herdsman-converters to 23.32.0 (#27178) 2025-04-21 19:47:22 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3e4bda0fda chore(deps-dev): bump the minor-patch group with 2 updates (#27175)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-21 21:20:52 +02:00
Koen Kanters 8195b2dd4a fix(ignore): update zigbee-herdsman-converters to 23.31.0 (#27162) 2025-04-19 18:56:05 +00:00
Koen Kanters ecc4696144 fix(ignore): update zigbee-herdsman-converters to 23.30.0 (#27154) 2025-04-19 18:30:03 +00:00
Koen Kanters 5e5bb18cf9 chore: Biome ignore package.json 2025-04-19 20:27:19 +02:00
Nerivec 6fbb8b5ca2 fix: Migrate to Biome (#27150) 2025-04-18 20:34:47 +02:00
Koen Kanters 130d041f75 fix(ignore): update zigbee-herdsman to 3.5.2 (#27147) 2025-04-18 07:54:12 +00:00
Koen Kanters 5a97643211 chore: merge master to dev 2025-04-17 20:24:45 +02:00
Koen Kanters 3a474187c6 chore: fix problem_report.yaml 2025-04-17 20:24:27 +02:00
Koen Kanters 8be1ae16ab fix(ignore): update zigbee-herdsman-converters to 23.29.0 (#27137) 2025-04-16 18:43:26 +00:00
Koen KantersandNerivec 1e2ad28267 fix: Use Alpine 3.21 for arm v6 and v7 (#25417)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-04-15 21:05:17 +02:00
Koen Kanters 8a9f631973 fix(ignore): update zigbee-herdsman-converters to 23.28.0 (#27130) 2025-04-15 19:01:12 +00:00
Koen Kanters b694dc1525 fix(ignore): update zigbee-herdsman-converters to 23.27.0 (#27121) 2025-04-14 20:30:22 +00:00
Koen Kantersandnurikk 0c0e5a3794 fix(ignore): update zigbee2mqtt-frontend to 0.9.9 (#27120)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-04-14 19:53:03 +00:00
Nerivec 36e2baf081 fix: Use Map for State (#27105) 2025-04-14 21:51:01 +02:00
NerivecandKoen Kanters 6e346073e7 feat: Allow scheduling OTA on device request (#26823)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-04-13 21:50:45 +02:00
Koenkk 06b53be5d8 fix(ignore): update zigbee-herdsman-converters to 23.26.0 2025-04-13 21:50:45 +02:00
Koen Kanters 12f858fbfe chore: merge master to dev 2025-04-13 21:34:42 +02:00
Koen Kanters 690d214caa chore: lint 2025-04-13 21:34:25 +02:00
Koen Kanters ead72c11f5 chore: merge master to dev 2025-04-13 21:14:52 +02:00
Koen Kanters d1c9f88c1d chore: enable dependabot 2025-04-13 21:14:35 +02:00
Koen Kanters b4ce65bb41 fix(ignore): update dependencies (#27094) 2025-04-13 20:53:09 +02:00
Nerivec e3d214ba9c fix: Guard against ZHC throwing (#27093) 2025-04-13 20:52:52 +02:00
Nerivec 8df5a484fc feat: Availability improvements (#26811) 2025-04-13 20:49:28 +02:00
Nerivec fc31e0af0a fix(ignore): partial cleanup for biome move (#27076) 2025-04-12 14:02:47 +02:00
Koen Kanters b8cc120da3 fix(ignore): update zigbee-herdsman-converters to 23.25.0 (#27075) 2025-04-10 19:52:04 +00:00
Nerivec 80502da80f fix: OnEvent fixes (#27063) 2025-04-10 21:08:04 +02:00
Koen Kanters 8e547d06ec fix(ignore): update zigbee-herdsman-converters to 23.24.0 (#27061) 2025-04-09 20:32:20 +00:00
[pʲɵs] 7fb10b1dcb fix: Do not call start() on extensions twice when starting (#27032) 2025-04-09 21:31:33 +02:00
Koen Kanters 850f0820ac fix: Don't allow publishing wildcards in MQTT topic (#27025) 2025-04-08 20:26:22 +02:00
Koen Kanters 17adc44a63 fix(ignore): update zigbee-herdsman-converters to 23.23.0 (#27036) 2025-04-07 19:50:54 +00:00
Koen Kanters 7d8232dc66 chore: merge master to dev 2025-04-06 21:22:59 +02:00
Koen Kanters a2debc08b4 chore: update problem report template 2025-04-06 21:22:43 +02:00
Koen Kanters 6949bbb883 fix(ignore): update zigbee-herdsman to 3.5.1 (#27024) 2025-04-06 19:20:39 +00:00
Koen Kanters 2297b81640 fix(ignore): update zigbee-herdsman-converters to 23.22.0 (#27023) 2025-04-06 19:13:07 +00:00
Koen Kanters d3bf771de0 fix(ignore): update dependencies (#27022) 2025-04-06 21:03:14 +02:00
Koen Kanters a426bf01ea chore: merge master to dev 2025-04-06 20:52:55 +02:00
Koen Kanters f8221ec2da chore: fix update-dependencies 2025-04-06 20:52:37 +02:00
Koen Kantersandnurikk d27ff76921 fix(ignore): update zigbee2mqtt-frontend to 0.9.8 (#27018)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-04-06 14:01:41 +00:00
Koen Kanters 20be4054c1 fix(ignore): update zigbee-herdsman-converters to 23.21.0 (#27001) 2025-04-05 12:06:03 +00:00
Koen Kanters 23e71aab14 fix(ignore): update zigbee-herdsman to 3.5.0 (#27000) 2025-04-05 12:00:44 +00:00
github-actions[bot] a34f547fa5 chore: promote to dev 2025-04-04 20:25:30 +00:00
Koen Kanters a14feef8c1 chore(dev): release 2.2.1 (#26934) 2025-04-04 22:12:32 +02:00
Koen Kanters d6a5128aa8 fix: Fix settings being overwriting when env var is set to a ref (#26988) 2025-04-04 21:57:11 +02:00
Koen Kanters 9d0ee03f38 fix(ignore): update zigbee-herdsman-converters to 23.20.1 (#26989) 2025-04-04 19:37:12 +00:00
Koen Kanters 37927e698a fix(ignore): update zigbee-herdsman-converters to 23.20.0 (#26978) 2025-04-03 20:27:14 +00:00
Koen Kantersandnurikk 89cdb1f180 fix(ignore): update zigbee2mqtt-frontend to 0.9.7 (#26976)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-04-03 18:41:06 +00:00
Koen Kanters e90afb286a fix(ignore): update zigbee-herdsman-converters to 23.19.1 (#26961) 2025-04-02 20:07:16 +00:00
Koen Kantersandnurikk fb4d01ebce fix(ignore): update zigbee2mqtt-frontend to 0.9.6 (#26959)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-04-02 21:29:10 +02:00
Nerivec 9e481fe5e5 fix: Fix issue with some settings when migrating from 1.x.x to 2.2.x (#26947) 2025-04-02 19:54:00 +02:00
Koen Kanters 0b7d0039f7 fix(ignore): update zigbee-herdsman-converters to 23.19.0 (#26938) 2025-04-01 20:13:05 +00:00
Koen Kanters 5257c7eae1 chore: prettier ignore release-notes.md 2025-04-01 21:06:04 +02:00
github-actions[bot] 952116d327 chore: promote to dev 2025-04-01 19:00:47 +00:00
Koen Kanters c5c07e7dfe chore(dev): release 2.2.0 (#26565) 2025-04-01 20:43:39 +02:00
Koen Kanters e02bbdcf0b fix(ignore): update dependencies (#26911) 2025-04-01 20:37:25 +02:00
Koen Kanters 69f0840feb fix(ignore): update zigbee-herdsman-converters to 23.18.0 (#26933) 2025-04-01 18:30:07 +00:00
Koen Kanters ea04f19709 fix(ignore): update zigbee-herdsman-converters to 23.17.0 (#26925) 2025-03-31 19:14:49 +00:00
Koen Kanters 9fe38b1677 fix(ignore): update zigbee-herdsman to 3.4.11 (#26924) 2025-03-31 18:50:39 +00:00
Koen Kanters 6e6a989159 fix(ignore): update zigbee-herdsman-converters to 23.16.0 (#26918) 2025-03-30 19:59:11 +00:00
Koen Kanters b52cfd87c3 fix(ignore): update zigbee-herdsman to 3.4.10 (#26908) 2025-03-29 17:23:23 +00:00
Koen Kanters 516425b44b fix(ignore): update zigbee-herdsman-converters to 23.15.0 (#26900) 2025-03-28 19:39:14 +00:00
Koen Kanters 6a65769060 fix(ignore): update zigbee-herdsman-converters to 23.14.0 (#26886) 2025-03-27 19:27:28 +00:00
Koen Kanters 0a41af5dd2 fix(ignore): update zigbee-herdsman to 3.4.9 (#26877) 2025-03-26 22:18:27 +00:00
clockbrain 267c399047 fix: Add device name to no converter available error message (#26867) 2025-03-26 21:45:33 +01:00
Koen Kanters f824295b7e fix(ignore): update zigbee-herdsman to 3.4.8 (#26866) 2025-03-25 22:50:21 +00:00
Koen Kanters 341c453657 fix(ignore): Fix HA addon onboarding (#26862) 2025-03-25 22:06:00 +01:00
Koen Kanters c5c7f70ad6 fix(ignore): update zigbee-herdsman-converters to 23.13.0 (#26861) 2025-03-25 19:41:18 +00:00
Koen Kanters 440e3a0094 fix(ignore): update zigbee-herdsman to 3.4.7 (#26859) 2025-03-25 18:50:46 +00:00
Koen Kanters 53e8226f0a fix(ignore): update zigbee-herdsman-converters to 23.12.0 (#26835) 2025-03-23 21:06:27 +00:00
Koen Kanters 7a1621a906 fix(ignore): fix EACCES: permission denied, mkdir '/app/dist/external_converters' 2025-03-23 22:02:17 +01:00
Koen Kanters 9e0c84beed fix(ignore): update zigbee-herdsman to 3.4.6 (#26834) 2025-03-23 20:59:26 +00:00
Koen Kanters 5f9b49d66f Merge remote-tracking branch 'origin/master' into dev 2025-03-23 21:54:59 +01:00
Koen Kanters 93d0ee44ba fix(ignore): update dependencies (#26833) 2025-03-23 21:46:52 +01:00
Koen Kanters fc35ac5737 chore: fix update-dependencies 2025-03-23 21:44:40 +01:00
Koen Kanters 2b90d7b3b9 fix(ignore): update zigbee-herdsman to 3.4.5 (#26826) 2025-03-22 19:24:20 +00:00
Nerivec f2a1c9a2be fix(gnore): Onboarding fixes (#26824) 2025-03-22 16:11:06 +01:00
Koen Kanters 0714582eb9 fix(ignore): update zigbee-herdsman to 3.4.4 (#26821) 2025-03-21 21:46:26 +00:00
Koen Kanters 5288549744 fix(ignore): update zigbee-herdsman-converters to 23.11.0 (#26820) 2025-03-21 20:36:17 +00:00
Nerivec 05d6f08a0d feat: Onboarding (#26608) 2025-03-21 21:30:19 +01:00
Koen Kanters 6f1266fd0e chore: remove docker-compose.yml 2025-03-21 21:10:30 +01:00
Koen Kantersandnurikk 9792466aac fix(ignore): update zigbee2mqtt-frontend to 0.9.5 (#26814)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2025-03-21 10:52:52 +00:00
Koen Kanters 52c255bcc2 fix(ignore): update zigbee-herdsman-converters to 23.10.0 (#26792) 2025-03-20 20:35:53 +00:00
Nerivec badf107991 feat: Use dynamic import for external JS (#26522) 2025-03-20 21:08:32 +01:00
Simon f872a5180c feat: Frontend: support filtering notifications (#26805) 2025-03-20 20:59:58 +01:00
Koen Kanters 4f2b052016 fix(ignore): update zigbee-herdsman to 3.4.3 (#26802) 2025-03-19 20:30:37 +00:00
Koen Kanters ef5b3de5e2 chore: enable provenance for release Docker image 2025-03-18 21:20:07 +01:00
Koen Kanters ff7b09eb99 chore: fix provenance 2025-03-18 21:16:36 +01:00
Koen Kanters 1baafbb6cf chore: enable provenance for NPM package 2025-03-18 20:54:49 +01:00
Koen Kanters 17ce9f4c52 chore: enable provenance for NPM package 2025-03-18 20:52:05 +01:00
Koen Kanters e6ce7ecdc1 chore: disable automatic pnpm updates 2025-03-18 20:50:24 +01:00
Koen Kanters 36ba1350a9 chore: remove codeql (enable basic) 2025-03-18 20:45:50 +01:00
Koen Kanters 582bf7d2c6 fix(ignore): update zigbee-herdsman to 3.4.2 (#26789) 2025-03-18 19:18:19 +00:00
Koen Kanters 46fd04364d fix(ignore): update zigbee-herdsman to 3.4.1 (#26779) 2025-03-17 20:22:38 +00:00
Alexander D. Kanevskiy 747f6ac476 fix: Home Assistant: fix electrical measurements sensors category (#26769) 2025-03-17 21:03:05 +01:00
Ömer Güven 6e781eafae fix(ignore): Make update check less verbose (#26774) 2025-03-17 20:43:56 +01:00
Koen Kanters 325199d445 fix(ignore): update zigbee-herdsman-converters to 23.8.0 (#26761) 2025-03-16 22:16:34 +01:00
Koen Kanters e602dc849a fix(ignore): update dependencies (#26751) 2025-03-16 22:09:37 +01:00
Koen Kanters ad0a222df1 Merge branch 'master' into dev 2025-03-16 21:58:34 +01:00
Koen Kanters 6d6372ccf0 chore: Skip updating dependencies on forks 2025-03-16 21:27:40 +01:00
NerivecandKoen Kanters 92955c02a4 feat: Support ZigBee on Host adapter (#26742)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-03-15 08:36:52 +01:00
Koen Kanters e5a80e3b1b fix(ignore): update zigbee-herdsman to 3.4.0 (#26744) 2025-03-15 07:26:13 +00:00
Ömer Güven fb48bd0cb9 fix(ignore): Fix update check (#26723) 2025-03-15 08:09:55 +01:00
Nerivec db578d9d11 fix: Use dynamic import for optional extensions (#26735) 2025-03-15 08:07:56 +01:00
Koen Kanters 8d2ef6df06 fix(ignore): update zigbee-herdsman-converters to 23.7.0 (#26733) 2025-03-13 19:13:26 +00:00
Koen Kanters 5db115057e fix(ignore): Module NodeNext (#26719) 2025-03-12 21:37:32 +01:00
Koen Kanters be722a0500 fix(ignore): Move to Node16 module (#26717) 2025-03-12 20:01:11 +00:00
BlueMöhre e3985002c2 fix: Fix bridge offline message format for frontend (#26707) 2025-03-12 20:23:40 +01:00
Koen Kanters fcbb355f47 Revert "fix(ignore): Move to Node16 module (#26704)"
This reverts commit ea5e0d47a5.
2025-03-12 07:25:37 +01:00
Koen Kanters 06ba62b833 fix(ignore): update zigbee-herdsman-converters to 23.6.0 (#26691) 2025-03-11 21:55:32 +01:00
Koen Kanters ea5e0d47a5 fix(ignore): Move to Node16 module (#26704) 2025-03-11 21:51:04 +01:00
Koen Kanters 8378949e1d fix(ignore): update zigbee-herdsman to 3.3.2 (#26702) 2025-03-11 19:52:35 +00:00
Koen Kanters 78cde99469 chore: Small improvements to CI pipelines (#26690) 2025-03-10 21:09:20 +01:00
Koen Kanters a5195a6eee fix(ignore): update zigbee-herdsman to 3.3.1 (#26674) 2025-03-09 19:32:28 +00:00
Koen Kanters fb9816659b fix(ignore): update dependencies (#26662) 2025-03-09 20:22:16 +01:00
Nerivec f6f7ba68c9 fix: Write ZIGBEE2MQTT_CONFIG_* envs to yaml as overrides (#26653) 2025-03-08 14:32:52 +01:00
Ömer Güven 1b6ad46462 fix: Check if new commits are available in update.sh (#26631) 2025-03-08 14:14:25 +01:00
Koen Kanters ad32fec1d0 fix(ignore): Fix tests failing when zhc is linked via pnpm link (#26630) 2025-03-06 20:27:17 +00:00
Koen Kanters c603f42c88 fix(ignore): update zigbee-herdsman-converters to 23.4.0 (#26629) 2025-03-06 20:20:30 +00:00
Koen Kanters ed7e7c7e53 fix(ignore): update zigbee-herdsman to 3.3.0 (#26628) 2025-03-06 19:20:29 +00:00
Koen Kanters 3941692665 fix(ignore): update zigbee-herdsman-converters to 23.3.0 (#26598) 2025-03-03 20:39:41 +00:00
Nerivec 8775ceb0c1 fix: General cleanup (#26595) 2025-03-03 21:12:57 +01:00
11fb858ff2 fix(ignore): update dependencies (#26554)
Co-authored-by: Simon Rettberg <srett@users.noreply.github.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-03-02 22:21:46 +01:00
Koen Kanters 4fa794a639 fix(ignore): Reduce chaining in homeassistant.ts (#26582) 2025-03-02 20:47:23 +00:00
Koen Kanters 1fe2d8dae6 fix(ignore): Remove uncessary extensions spread (#26581) 2025-03-02 20:30:36 +00:00
bfeac07c4c fix: Implement systemd-notify directly (#26456)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-03-02 08:59:28 +01:00
github-actions[bot] b6c0f4e5db chore: promote to dev 2025-03-02 07:58:15 +00:00
Koen Kanters ba337bd329 chore(dev): release 2.1.3 (#26550) 2025-03-02 08:44:41 +01:00
Koen Kanters 7e7057eccd fix: Home Assistant: fix brightness only lights (#26560) 2025-03-02 08:43:21 +01:00
github-actions[bot] cbcd32064c chore: promote to dev 2025-03-01 20:51:01 +00:00
Koen Kanters d2d7ebeed9 chore(dev): release 2.1.2 (#26217) 2025-03-01 21:37:52 +01:00
Koen Kanters 9f72c5c455 fix(ignore): update zigbee-herdsman-converters to 23.2.0 (#26549) 2025-03-01 20:19:30 +00:00
Koen Kanters e050e209b0 fix: Fix Docker build hanging (#26547) 2025-03-01 21:13:30 +01:00
Koen Kanters 29d25aed66 fix: Make compatible with pnpm 10 (#26546) 2025-03-01 20:56:46 +01:00
Koen Kanters 063aba1b6c fix: Home Assistant: add brightness to supported_color_modes (#26545) 2025-03-01 20:11:41 +01:00
Tom Davis e8031efd2d fix: Add Home Assistant device class to external_temperature_input entity (#26535) 2025-03-01 17:43:30 +01:00
Koen Kanters 102857a7fe fix(ignore): update zigbee-herdsman-converters to 23.1.1 (#26510) 2025-02-26 06:57:43 +00:00
Koen Kanters db7b51ac80 fix(ignore): update zigbee-herdsman-converters to 23.1.0 (#26509) 2025-02-25 21:32:49 +00:00
Koen Kanters d2166f2f4f fix(ignore): update dependencies (#26478) 2025-02-24 21:31:56 +00:00
ghozandKoen Kanters 07145accba fix(ignore): Add publish to meta of toZigbee converters (#26490)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-02-24 22:29:00 +01:00
Koenkk 44f9fe7a14 fix(ignore): update zigbee-herdsman-converters to 23.0.0 2025-02-24 22:29:00 +01:00
Koen Kanters d522d2cd18 fix(ignore): remove obsolete @types/eslint__js 2025-02-24 22:21:55 +01:00
Koen Kanters 4e650b72cd fix(ignore): update zigbee-herdsman to 3.2.7 (#26498) 2025-02-24 19:45:59 +00:00
ghoz a6072adcd0 chore: exclude cache, editor tmp files (#26497) 2025-02-24 20:40:25 +01:00
Koen Kanters 0d193ebac2 Revert "chore: bump pnpm"
This reverts commit 24779e3476.
2025-02-23 10:05:39 +01:00
Koen Kanters ede1a470dd chore: bump node version in workflows 2025-02-22 21:09:34 +01:00
Koen Kanters 24779e3476 chore: bump pnpm 2025-02-22 21:07:31 +01:00
Koen Kanters 67e1a02525 fix(ignore): update zigbee-herdsman-converters to 22.2.0 (#26469) 2025-02-22 13:08:17 +00:00
Lorenz Brun 7ce5b581aa fix: Home Assistant: support speed-controlled fans (#24483) 2025-02-21 20:24:08 +01:00
Koen Kanters 40f41477e7 fix(ignore): update zigbee-herdsman-converters to 22.1.0 (#26445) 2025-02-20 12:42:13 +00:00
Koen Kanters 382c89c49e chore: fix changelog generator 2025-02-18 16:15:46 +01:00
Koen KantersandNerivec 7d63a9a0f2 fix(ignore): update zigbee-herdsman-converters to 22.0.1 (#26418)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-02-18 14:50:01 +00:00
Koen Kanters 93af062a9b fix(ignore): update zigbee-herdsman-converters to 21.38.0 (#26400) 2025-02-17 13:36:53 +00:00
Koen Kanters 758c56647d fix(ignore): update dependencies (#26383) 2025-02-17 14:30:20 +01:00
FilipandFilip Vilicic 845e03e48a fix: Allow configuring number of folders of old logs to keep (#26398)
Co-authored-by: Filip Vilicic <kusefin@hotmail.com>
2025-02-17 14:04:39 +01:00
Koen Kanters dec5ca8b3a fix(ignore): update zigbee-herdsman-converters to 21.37.0 (#26367) 2025-02-14 20:09:39 +00:00
XS400DOHC 1e656c2114 fix: Home Assistant: add missing device_class for some current and power entities (#26324) 2025-02-13 17:52:27 +01:00
Koen Kanters ba76533c2f fix(ignore): update zigbee-herdsman-converters to 21.36.0 (#26333) 2025-02-12 20:41:22 +00:00
Koen Kanters 30d13d6e5e fix(ignore): update zigbee-herdsman-converters to 21.35.0 (#26305) 2025-02-10 20:46:28 +00:00
Koen Kanters 49fe147921 fix(ignore): update zigbee-herdsman to 3.2.6 (#26303) 2025-02-10 19:57:41 +00:00
Koen Kanters 7a0964ebc9 fix(ignore): update zigbee-herdsman-converters to 21.34.0 (#26285) 2025-02-09 20:18:19 +00:00
Rob Coleman b35cdc3474 fix: Fix HA MQTT dict entry indicating that an update is progress (#26267) 2025-02-09 14:59:08 +01:00
Evaldas Auryla 5e55ef0454 fix: Support FreeBSD for update.sh (#26261) 2025-02-09 14:50:36 +01:00
Koen Kanters 3e3cefff1b fix(ignore): update dependencies (#26266) 2025-02-09 14:48:11 +01:00
Koen Kanters 97c74852c9 fix(ignore): update zigbee-herdsman-converters to 21.33.0 (#26258) 2025-02-08 14:06:29 +00:00
Koen Kanters 8542b35706 fix(ignore): update zigbee-herdsman-converters to 21.32.0 (#26245) 2025-02-07 19:32:33 +00:00
ramon18 1eda69a50a fix: Home Assistant: add is_updating attribute (#26231) 2025-02-07 20:06:28 +01:00
github-actions[bot] 16cecd917e chore: promote to dev 2025-02-06 19:43:25 +00:00
Koen Kanters 1accb8b66b chore(dev): release 2.1.1 (#26135) 2025-02-06 20:33:58 +01:00
Koen Kanters a3682b5fc7 chore: fix update_dep 2025-02-06 20:31:47 +01:00
Koen Kanters cfea8b76e2 Update zigbee-herdsman-converters to 21.31.0 (#26216) 2025-02-06 19:23:29 +00:00
Koen Kanters 9e28818c53 Update zigbee-herdsman-converters to 21.30.0 (#26203) 2025-02-05 20:38:05 +00:00
Koen Kanters bfbbfcda37 Update zigbee-herdsman-converters to 21.29.0 (#26183) 2025-02-04 20:25:50 +00:00
Rohan KapoorandKoen Kanters e4e4f16ec8 fix: Rename internal_temperature to internalTemperature to correct typo (#26114)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-02-03 20:09:24 +01:00
Koen Kanters 75c3c5e57f chore: Update release_please.yml 2025-02-02 21:11:51 +01:00
Koen Kanters 6a15328001 Update zigbee-herdsman-converters to 21.28.0 (#26134) 2025-02-02 20:08:59 +00:00
Koen Kanters b960775160 fix(ignore): update dependencies (#26126) 2025-02-02 15:06:40 +01:00
Koen Kanters 41ba32db49 chore: merge master to dev 2025-02-02 15:03:46 +01:00
Koen Kanters 83ff4a7684 chore: Add packageManager to package.json (#26106) 2025-02-02 15:03:26 +01:00
Koen Kanters 98b5a60886 chore: Add packageManager to package.json (#26106) 2025-02-01 22:14:03 +01:00
github-actions[bot] b34723db36 chore: promote to dev 2025-02-01 20:34:39 +00:00
Koen Kanters 5e9d53f0aa chore(dev): release 2.1.0 (#25439) 2025-02-01 21:23:35 +01:00
Koen Kanters 8c7961de1f Update zigbee-herdsman-converters to 21.27.1 (#26102) 2025-02-01 20:01:44 +00:00
Koen Kanters ff466f817f chore: merge master to dev 2025-02-01 20:57:03 +01:00
Koen Kanters c7a7c3d8c2 chore: lint codeql.yaml 2025-02-01 20:56:47 +01:00
Koen Kanters 7540a0d02f chore: merge master to dev 2025-02-01 20:54:57 +01:00
Koen Kanters 33d5f0595d chore: Create codeql.yaml 2025-02-01 20:54:42 +01:00
Koen Kanters c4510d7382 chore: merge master to dev 2025-02-01 20:52:04 +01:00
Koen Kanters e670524241 chore: remove codeql.yaml 2025-02-01 20:51:44 +01:00
Koen Kanters 75f8ed07ed Update zigbee-herdsman to 3.2.5 (#26101) 2025-02-01 19:47:49 +00:00
Koen Kanters 0706a5e039 Update zigbee-herdsman-converters to 21.27.0 (#26071) 2025-01-30 20:15:53 +00:00
Koen Kanters 32329695d0 Update zigbee-herdsman-converters to 21.26.0 (#26043) 2025-01-28 18:37:44 +00:00
Tatham Oddie cdbd55320a fix: Add Home Assistant device class for flow rates (e.g., Sonoff SWV) (#26035) 2025-01-28 19:12:00 +01:00
Koen Kanters 7b199f898e Update zigbee-herdsman-converters to 21.25.0 (#26032) 2025-01-27 20:00:24 +00:00
Rob Coleman 0462b94f5a fix: Only use endpoint-specific states if the device definition uses them (#26019) 2025-01-27 20:54:01 +01:00
Koen Kanters 85460cd5ce fix(ignore): update dependencies (#26005) 2025-01-27 19:44:50 +01:00
Koen Kanters 420a9f2ff6 Update zigbee-herdsman-converters to 21.24.0 (#26000) 2025-01-25 19:04:47 +00:00
gracianodias3 5cf4547fe0 fix: Allow to set throttle via frontend (#25997) 2025-01-25 20:00:09 +01:00
Koen Kanters 2af65cf28e fix: Fix duplicate action publish when advanced.output has attribute (#25963) 2025-01-23 21:01:56 +01:00
Koen Kanters e9f74f0958 Update zigbee-herdsman-converters to 21.23.0 (#25951) 2025-01-22 19:28:32 +00:00
Koen Kanters 2567b4c39e Update zigbee-herdsman to 3.2.4 (#25919) 2025-01-20 21:01:53 +00:00
Koen Kanters ac1671609c Update zigbee-herdsman-converters to 21.22.0 (#25918) 2025-01-20 20:58:50 +00:00
Koen Kanters 8da5fa663d fix(ignore): update dependencies (#25890) 2025-01-19 21:23:36 +01:00
Koen Kanters 3d3c68b54a Update zigbee-herdsman-converters to 21.21.1 (#25872) 2025-01-19 13:04:23 +00:00
Koen Kanters 5e60f1dc8d fix(ignore): Remove adapter auto from schema (#25896) 2025-01-19 12:58:35 +00:00
Koen Kanters a1f74a312a Update zigbee-herdsman to 3.2.3 (#25895) 2025-01-19 12:32:15 +00:00
Koen Kanters b21167b326 Update zigbee-herdsman-converters to 21.20.0 (#25837) 2025-01-15 19:54:49 +00:00
Koen Kanters be17e698a1 Update zigbee-herdsman-converters to 21.19.0 (#25797) 2025-01-13 20:07:23 +00:00
Koen Kanters 7392557af2 Update zigbee-herdsman-converters to 21.18.0 (#25779) 2025-01-12 21:18:15 +00:00
Koen Kanters 360be57079 Update zigbee-herdsman-converters to 21.17.0 (#25770) 2025-01-12 13:43:05 +00:00
Koen Kanters 361f9eef8b Update zigbee-herdsman to 3.2.2 (#25769) 2025-01-12 13:17:20 +00:00
Koen Kanters 5b82d7e4df fix(ignore): update dependencies (#25760) 2025-01-12 14:12:38 +01:00
Koen Kanters 9daeba1f0a fix: Fix Failed to call 'Frontend' 'stop' (TypeError: Cannot read properties of undefined (reading 'close') https://github.com/Koenkk/zigbee2mqtt/issues/25715 2025-01-11 14:38:21 +01:00
Koen Kanters 2d5e7bf9ff fix: Fix Error: write after end error (#25737) 2025-01-11 14:11:26 +01:00
Koen Kanters b5bef45cb2 Update zigbee-herdsman-converters to 21.16.0 (#25735) 2025-01-10 20:15:20 +00:00
NerivecandKoen Kanters f1f73bd464 fix: Long startup time when OnEvent 'start' times out (repeatedly) (#25693)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-01-09 22:22:19 +01:00
Nerivec 23b709cee2 fix(ignore): Ensure config example is never out of sync with settings (#25707) 2025-01-09 20:37:01 +01:00
Koen Kanters 31bdf02a72 Update zigbee-herdsman-converters to 21.15.0 (#25690) 2025-01-08 21:27:05 +00:00
Jens-Uwe MagerandNerivec 26ef565c8a feat: Add a settings option to log to console in json format (#25649)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-01-08 19:22:26 +00:00
Koen Kanters 6f3524b251 fix(ignore): update dependencies (#25535) 2025-01-07 19:08:30 +00:00
Jan BouwhuisandKoen Kanters 318dc04132 feat: Allow Home Assistant OTA Update entity to show progress while updating (#25632)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-01-07 20:04:45 +01:00
Koen Kanters bf354f2121 Update zigbee-herdsman-converters to 21.14.0 (#25635) 2025-01-06 21:15:58 +00:00
Koen Kanters 1667ba64ac fix: Remove invalid warn log_level https://github.com/nurikk/zigbee2mqtt-frontend/issues/2369 (#25634) 2025-01-06 21:45:55 +01:00
SteffenBeermann a28a5baf17 fix: Enable use of MQTT username without password (#25611) 2025-01-06 20:19:18 +01:00
Koen Kanters c33946ffc3 Update zigbee-herdsman-converters to 21.13.0 (#25563) 2025-01-05 13:59:54 +00:00
RoboMagus 0d30eb553b fix: Add semver tags to Docker releases (#25530) 2025-01-05 13:03:38 +00:00
Devin Buhl 43a5b24dcc fix: Container: remove modifying capabilities of /usr/bin/node (#25456) 2025-01-05 13:36:57 +01:00
Koen Kanters 3c46cfb2e9 chore: promote to dev 2025-01-03 22:00:11 +01:00
Koen Kanters 7972950c84 chore: fix ci.yaml 2025-01-03 21:59:40 +01:00
Koen Kanters 060ae99cff chore(dev): release 2.0.0 (#24988) 2025-01-03 20:45:37 +00:00
Koen Kanters b7847d8203 Update zigbee-herdsman-converters to 21.12.0 (#25437) 2025-01-03 20:06:03 +00:00
Zitrium31andKoen Kanters c586df8242 feat: Home Assistant: support endpoints for locks (#25359)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-01-03 19:58:39 +00:00
Ric Klaren 7cf01e9882 fix(ignore): add cap_net_bind_service=+ep to /usr/bin/node (#25385) (#25424) 2025-01-03 20:55:34 +01:00
Koen Kanters 2d6b00bc69 Update zigbee-herdsman to 3.2.1 (#25416) 2025-01-02 13:22:14 +00:00
Koen Kanters 9055eccae9 Revert "fix: add cap_net_bind_service=+ep to /usr/bin/node (#25385)" (#25415) 2025-01-02 14:15:39 +01:00
Koen Kanters 2046c05fd4 chore: merge master to dev 2025-01-02 13:55:12 +01:00
Koen Kanters 902db5ea5b chore: update stale comment 2025-01-02 13:54:56 +01:00
Kenny Levinsen 60c37c11d0 fix: Home Assistant: use extended device identifiers (#25389) 2025-01-02 12:48:26 +00:00
Ric Klaren 61864f0fb2 fix: Add cap_net_bind_service=+ep to /usr/bin/node in Docker container (#25385) (#25387) 2025-01-02 13:39:38 +01:00
Koen Kanters 240ab43f2d Update zigbee-herdsman-converters to 21.11.0 (#25386) 2024-12-30 20:49:56 +00:00
Koen Kanters 2713125768 fix(ignore): remove illuminance_lux from cached payload https://github.com/Koenkk/zigbee2mqtt/issues/25379 2024-12-30 21:34:42 +01:00
Koen Kanters ed75553603 fix(ignore): Fix availabilty, frontend and homeassistant cannot be disabled via the frontend https://github.com/nurikk/zigbee2mqtt-frontend/issues/2339 2024-12-30 21:23:27 +01:00
Koen Kantersandnurikk e48cd22ab7 Update zigbee2mqtt-frontend to 0.9.4 (#25381)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-12-30 16:21:27 +00:00
Koen Kanters 1e7e00e8dd fix(ignore): Improvements for #25299 (#25364) 2024-12-30 14:30:54 +01:00
Koen Kanters 02666e9139 fix(ignore): Fix 2 to 3 migration enabling frontend/availabilty/homeassistant when not set (#25349) 2024-12-30 14:22:18 +01:00
Koen Kanters 47d73eb23a fix(ignore): update dependencies (#25360) 2024-12-29 15:07:18 +01:00
NerivecandKoen Kanters 120a5b43e9 fix(ignore): Use new exports from ZH (#25350)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-28 14:34:12 +00:00
Koen Kanters e8a5521a4c chore: remove onlythis 2024-12-28 15:25:12 +01:00
Koen Kanters 329b8c9c27 fix: Improve device icon serving (#25299) 2024-12-28 15:07:02 +01:00
Koen Kanters 0e19c227fc Update zigbee-herdsman-converters to 21.10.0 (#25348) 2024-12-28 13:35:19 +00:00
Koen Kantersandnurikk e868a38c09 Update zigbee2mqtt-frontend to 0.9.3 (#25347)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-12-28 13:30:31 +00:00
Koen Kanters 301cb331d5 Update zigbee-herdsman to 3.2.0 (#25346) 2024-12-28 13:19:26 +00:00
Ricc68andRicc68 bcec40f84a fix: Fix URL in startup failed bailout message (#25339)
Co-authored-by: Ricc68 <Ricc68@local>
2024-12-28 13:59:50 +01:00
Koen Kanters 62e18a65f3 Update zigbee-herdsman-converters to 21.9.2 (#25316) 2024-12-25 09:38:01 +00:00
Koen Kanters b8e6385e05 chore: add prepack 2024-12-24 22:10:04 +01:00
Koen Kantersandnurikk b65cc0ee74 Update zigbee2mqtt-frontend to 0.9.2 (#25313)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-12-24 13:41:24 +00:00
Koen Kanters eaafc7c28a Update zigbee-herdsman-converters to 21.8.0 (#25296) 2024-12-22 20:14:21 +00:00
Koen Kanters 2128e8323c fix(ignore): update dependencies (#25288) 2024-12-22 20:24:36 +01:00
NerivecandKoen Kanters 06f44533af feat: Use Vitest for testing (#25284)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-22 20:19:45 +01:00
Rohan Kapoor 5d059b7bcb fix: Home Assistant: expose the internal_temperature sensor with the correct device class (#25289) 2024-12-22 19:15:34 +00:00
Koen Kanters 6eae15fb5e Update zigbee-herdsman to 3.1.1 (#25287) 2024-12-22 09:55:36 +00:00
Koen Kanters 2abf55ccf8 fix!: Change default homeassistant.status_topic to homeassistant/status (#25286) 2024-12-21 20:33:44 +00:00
Koen Kanters 803e9060ea Update zigbee-herdsman-converters to 21.7.0 (#25285) 2024-12-21 20:27:27 +00:00
Koen Kanters 7170215e96 fix: Fix log directories not being deleted (#25282) 2024-12-21 16:54:19 +01:00
Nerivec 758ab34747 fix(ignore): import using node: prefix for built-in (#25248) 2024-12-18 22:19:33 +01:00
Koen KantersandNerivec c3b4628a1d fix: Remove oneOf bool/obj from settings schema (#25195)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2024-12-17 22:27:55 +01:00
Koen Kanters e7f8de1e94 Update zigbee-herdsman-converters to 21.6.0 (#25253) 2024-12-17 20:57:56 +00:00
NerivecandKoen Kanters 1c75eef16d fix(ignore): remove dep in favor of built-in (#25250)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-17 21:31:21 +01:00
NerivecandKoen Kanters c88f630299 fix(ignore): overrides (#25246)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-17 21:30:00 +01:00
Tobias G ef6524b93f fix: Fix dual cover Home Assistant discovery (#25240) 2024-12-17 21:25:30 +01:00
Koen Kanters 9822c75497 Update zigbee-herdsman-converters to 21.5.0 (#25221) 2024-12-15 20:22:53 +00:00
Koen Kanters 56746ded19 fix(ignore): update dependencies (#25210) 2024-12-15 19:44:39 +00:00
Nerivec 0b62aefbb2 fix(ignore): Force rebuild of @serialport/bindings-cpp in Docker (#25202) 2024-12-15 20:38:41 +01:00
Koen Kanters e234f42e1f Update zigbee-herdsman to 3.0.4 (#25196) 2024-12-14 13:36:56 +00:00
Koen Kanters 72428897a7 fix: Fix group with only multi endpoints devices not being controllable (#25156) 2024-12-14 14:24:34 +01:00
Koen Kanters 39c007d211 fix(ignore): Add back Home Assistant legacy action sensor (#25192) 2024-12-14 13:30:33 +01:00
Koen Kanters 5e71cf2cc8 fix(ignore): Update zigbee-herdsman-converters to 21.4.0 (#25191) 2024-12-13 21:28:39 +01:00
Koen Kanters 146b6c3860 fix(ignore): Rebuild serialport, attempt to fix #25092 2024-12-13 21:25:50 +01:00
NerivecandKoen Kanters 2b08c688d3 fix: Proper extended PAN ID from getNetworkParameters (#25174)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-13 20:58:21 +01:00
Koenkk 0a20e4a455 fix(ignore): update zigbee-herdsman to 3.0.3 2024-12-13 20:58:21 +01:00
Cody Cutrer 08ac795bb3 fix: OpenHAB: Fix rejectattr argument in event value_template (#25179) 2024-12-13 19:41:47 +00:00
Koen Kanters a2027d79e8 fix(ignore): Add back linux/386 (#25187) 2024-12-13 20:35:11 +01:00
Koen Kanters 1dde41f7ea chore: Lint Dockerfile 2024-12-11 22:45:38 +01:00
e23d300fd9 feat: Docker: RISC-V support (#22824)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-11 22:42:37 +01:00
Koen Kanters 32c341c7f5 fix!: Docker: update Alpine to 3.21, drop x86 support (#25171) 2024-12-11 22:31:24 +01:00
Koen Kanters 9eea58eba4 fix: Fix Cannot read properties of null (reading 'toString') when Home Assistant event entities are enabled https://github.com/Koenkk/zigbee2mqtt/issues/25133 2024-12-11 22:25:54 +01:00
Nerivec ec13ebd3ad fix(ignore): Minor cleanup (#25166) 2024-12-11 21:09:41 +01:00
Koen Kanters 9fad883ae7 chore: merge master to dev 2024-12-10 21:28:20 +01:00
Koen Kanters bc6eccf615 chore: update stale 2024-12-10 21:28:01 +01:00
Koen Kanters 6905c10a05 Update zigbee-herdsman-converters to 21.3.0 (#25143) 2024-12-09 21:36:21 +00:00
Koen Kantersandnurikk 1d176f4358 Update zigbee2mqtt-frontend to 0.9.1 (#25142)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-12-09 21:12:11 +00:00
Cody Cutrer 226945cae0 fix: Fix Home Assistant event entities for openHAB (#25140) 2024-12-09 20:34:19 +01:00
7d63a87aad fix!: Remove permit_join_timeout (#25082)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
Co-authored-by: IIIEII <al@iiieii.ru>
Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com>
2024-12-08 15:49:42 +01:00
Koen Kantersandnurikk f44ea32bca Update zigbee2mqtt-frontend to 0.9.0 (#25128)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-12-08 14:46:00 +00:00
Koen Kanters 7803a166d2 fix(ignore): update dependencies (#25120) 2024-12-08 15:02:27 +01:00
Artem DraftandKoen Kanters ba45445544 fix!: Home Assistant: remove duplicate sensor/select for select/number/button entities (#25026)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-07 21:43:47 +01:00
Nerivec 1a9c79bb2f feat: Typing for MQTT API (#25098) 2024-12-07 20:21:30 +00:00
IIIEII d891320314 fix(ignore): Fixed additional case of calculation of group state when including no… (#25104) 2024-12-07 21:05:55 +01:00
Koen Kanters 639e3bb6fd chore: merge master to dev 2024-12-05 21:11:58 +01:00
Koen Kanters 7dc92d4b6c chore: fix update_dep.yml 2024-12-05 21:11:31 +01:00
Koen Kanters 0687195aca Update zigbee-herdsman to 3.0.1 (#25081) 2024-12-05 20:11:14 +00:00
Koen Kanters 0affca4fe4 chore: merge master to dev 2024-12-05 21:09:05 +01:00
Koen Kanters f8ff89d442 Revert "chore: fix update_dep.yml"
This reverts commit d943b31372.
2024-12-05 21:07:30 +01:00
Koen Kanters 66404cc9f4 chore: merge master to dev 2024-12-05 21:07:00 +01:00
Koen Kanters d943b31372 chore: fix update_dep.yml 2024-12-05 21:06:37 +01:00
alorenteandKoen Kanters b370b80dfb fix: Add more OCI Image Labels (#25074)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-05 20:12:23 +01:00
Koen Kanters 51f364a1d5 fix(ignore): Fix external converter loading (#25063) 2024-12-05 19:58:55 +01:00
Koen Kanters 53208ae06f Merge remote-tracking branch 'origin/master' into dev 2024-12-04 21:25:27 +01:00
IIIEII 9227e139de fix: Fixed calculation of group state when including non-default endpoints (#25059) 2024-12-04 21:20:47 +01:00
Koen Kanters 71e163fcc3 Update zigbee-herdsman-converters to 21.2.1 (#25054) 2024-12-03 21:18:23 +00:00
Koen Kanters 5b907f2f77 chore: fix update_dep(s) 2024-12-03 22:05:55 +01:00
Koen Kanters acba533397 chore: fix ci commit 2024-12-01 21:34:34 +01:00
Koen Kanters 9bd20afb9c fix(ignore): Update usb discovery discussion link 2024-12-01 20:54:46 +01:00
Koen Kanters 32e10d89f4 fix(ignore): update dependencies (#24999) 2024-12-01 20:48:04 +01:00
Koen Kanters 80b00b6254 chore: fix ci.yml 2024-12-01 20:47:39 +01:00
Koen Kanters c2c6b1991c chore: fix release_please 2024-12-01 20:44:34 +01:00
Koen Kanters 1f56496869 Merge pull request #24492 from Koenkk/feat/2.0.0
Zigbee2MQTT 2.0.0
2024-12-01 20:39:46 +01:00
Koen Kanters b581dc6994 fix(ignore): fix tests 2024-12-01 20:37:13 +01:00
Koen Kanters 1ce45c8c41 fix(ignore): update deps 2024-12-01 20:12:46 +01:00
Koen Kanters a6b36051f0 fix!: Ensure data in response is always empty on error (#24971) 2024-12-01 20:11:34 +01:00
Nerivec d78c6facab fix: Cleanup leftover legacy settings logic (#24947) 2024-12-01 20:11:34 +01:00
Koen Kanters 8d58048f34 chore: improve ci 2024-12-01 20:11:34 +01:00
Koen Kanters f54719fb89 chore: fix provenance 2024-12-01 20:11:34 +01:00
NerivecandKoen Kanters 9907005256 feat: Live loading of external JS (converters/extensions) (#24764)
* feat: Live loading of external JS (converters/extensions)

* Fix imports

* Improve error message on MQTT save

* Handle non-existing base path

* Throw on bad converter

* Add tests

* Fix use of ext conv in network map tests.

* More coverage.

* Dont mock zhc for basics, tests actual live loading

* Update

* feat: Live loading of external JS (converters/extensions)

* Fix imports

* Improve error message on MQTT save

* Handle non-existing base path

* Throw on bad converter

* Add tests

* Fix use of ext conv in network map tests.

* More coverage.

* Dont mock zhc for basics, tests actual live loading

* Update

* Fix rebase

* Fix

* Bump zhc

* pretty

* fix typing

* Cleanup `external_converters` setting remnants.

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-01 20:11:34 +01:00
Koen Kanters cc61f67215 fix(ignore): some fixes 2024-12-01 20:11:34 +01:00
Koen Kanters 23d8cea4e2 fix: Cleanup Dockerfile 2024-12-01 20:11:34 +01:00
Koen Kanters 8aef77e57e fix(ignore): Remove external definition type 2024-12-01 20:11:34 +01:00
Nerivec c6958c4991 feat: Automatic settings migration (#24871)
* feat: Automatic settings migration

* Handle failing on unsupported version.

* Handle change not needed.

* Test change not wanted.

* Cleanup.

* Add `external_converters` removal.

* Fix schema export.
2024-12-01 20:11:34 +01:00
Nerivec e052988663 fix: Use built-in async for mqtt (#24786)
* fix: Use async for mqtt.

* Update mocks, fix expects

* Prettier.

* Use appropriate error for publish

* Set max listeners immediately after connect

* Add mqtt v5+ `disconnect` event handler

* Set maximum packet size in `CONNECT` packet

* Fix tests.

* Add setting for maximum packet size.

* Add `reasonString` to disconnect logs

* Fix tests

* Fix

* Prettier
2024-12-01 20:11:34 +01:00
NerivecandKoen Kanters 563514c446 fix!: Rework OTA (#24634)
* fix(ignore): Update zh and zhc

* update

* Update

* fix

* fix!: Rework OTA

* Import only required from zhc.

* Remove uri-js

* Update settings.schema.json

* fix save

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-01 20:11:33 +01:00
Koen Kanters 95279b02af fix(ignore): Small cleanup (#24696) 2024-12-01 20:11:08 +01:00
Koen Kanters e290057e21 fix!: Remove deprecated Home Assistant integration features (#24494)
* Remove `legacy_trigger`

* Remove legacy_entity_attributes

* Remove `LEGACY_MAPPING`

* Finish cleanup

* Updates

* revert some entities removal

* Update

* Updates
2024-12-01 20:11:06 +01:00
Koen Kanters 61cf5094c7 fix 236b084396 2024-12-01 20:10:35 +01:00
Nerivec 9537ef05d5 fix!: Improve bind/bridge/groups parsing and resolving consistency (#24432)
* Improve bind/bridge/groups parsing and resolving consistency

* Fix mqtt payload casing. Improve typing.

* Cleanup tests

* Add test for bind endpoint by ID.

* Match behavior for group response 'default' endpoint.
2024-12-01 20:09:28 +01:00
Koen Kanters 3f76586a50 fix!: Remove configuring group members through configuration.yaml (#24338)
* fix!: Remove configuring group members through `configuration.yaml`

* Updates

* Updates

* fix

* Updates

* Feedback
2024-12-01 20:09:28 +01:00
Nerivec 6f6e7c3ee0 fix: Refactor tests to TS (#24357) 2024-12-01 20:09:27 +01:00
NerivecandKoen Kanters 2124d3450c fix!: Improve permit join (#24257)
* fix: Improve permit join

* Update Home Assistant permit join switch

* Remove `permit_join` from `settings.schema.json`

* Update zigbee-herdsman version to pre-release.

* Fix pnpm overrides

* Update test/homeassistant.test.js

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-12-01 20:07:06 +01:00
Koen KantersandNerivec b6186623f0 feat!: The big cleanup (#24200)
* Remove `deviceGroupMembership`

* Fix controller.ts

* Remove `legacy_api` logic from non-legacy extensions.

* Fix network map coverage.

* Remove all `legacy` extensions.

* Remove `legacy_availability_payload`, `legacy_api`. Remove legacy from configure.

* Fix prettier.

* Remove some leftovers

* Renamed `updateAvailable` to `update_available`

* Remove emitPublishAvailability

* Remove `configuration.yaml`

* Switch to pnpm

* Remove direct mqtt-packet dependency.

* fix pretty

* fix `getDependencyVersion`

* fix pnpm publish

* fix(ignore): fix pnpm publish

* Remove deprecated bridge `config/*`

* Improve update script

* Improve update.sh

---------

Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2024-12-01 20:06:49 +01:00
Koen Kanters 3220b27c8a fix!: HA permit join switch: disable automatically after 254 seconds (#24245) 2024-12-01 20:06:37 +01:00
Koen Kanters 98b977030e fix(ignore): Changes for improved adapter discovery (#24147)
* fix: Comment serial config in example configuration

* Link to issue

* Update link

* Update example config

* Feedback
2024-12-01 20:06:37 +01:00
Koen Kanters 1fdc427358 chore: update update_deps.yml 2024-12-01 20:04:54 +01:00
github-actions[bot] 9a74b015dc chore: promote to dev 2024-12-01 13:44:52 +00:00
Koen Kanters 861cba63b3 chore(dev): release 1.42.0 (#24569) 2024-12-01 14:33:44 +01:00
Koen Kanters 59d0eb4534 Update zigbee-herdsman-converters to 20.58.0 (#24987) 2024-12-01 13:30:48 +00:00
Koen Kanters 57ee303cda Update zigbee-herdsman-converters to 20.57.0 (#24977) 2024-11-30 20:21:19 +00:00
Der Mundschenk & Compagnie 4690fe1a55 fix(ignore): Ignore None actions for HA events (#24972) 2024-11-30 21:09:09 +01:00
Der Mundschenk & CompagnieandKoen Kanters 826547a8e1 fix(ignore): Prevent empty events from being published to HA (#24969)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-11-30 13:46:31 +01:00
Koen Kanters 7047aef7f7 fix(ignore): Fix Cannot read properties of undefined (reading 'clients') https://github.com/Koenkk/zigbee2mqtt/issues/24957 2024-11-29 20:43:30 +01:00
Der Mundschenk & Compagnie d82d782bac fix(ignore): Prevent incorrect event_type due to duplicate keys (#24954) 2024-11-29 20:26:33 +01:00
Koen Kantersandnurikk 6351f4618e Update zigbee2mqtt-frontend to 0.7.6 (#24955)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-11-28 20:44:30 +00:00
Koen Kanters 2c52c9884c Update zigbee-herdsman-converters to 20.56.0 (#24952) 2024-11-28 19:56:10 +00:00
Koen Kanters 5e47a21673 Update zigbee-herdsman-converters to 20.55.0 (#24939) 2024-11-27 19:41:38 +00:00
Der Mundschenk & Compagnie 5ca4cf4acd fix: Home Assistant: template errors when action key is missing or empty in state payload (#24931) 2024-11-27 08:11:07 +01:00
Koen Kanters 267d800122 Update zigbee-herdsman-converters to 20.54.0 (#24920) 2024-11-25 19:26:00 +00:00
Koen KantersandIlya Kirov 790373b896 fix(ignore): Update zigbee-herdsman-converters to 20.53.0 (#24906)
Co-authored-by: Ilya Kirov <kirovilya@gmail.com>
2024-11-24 21:29:37 +00:00
Koen Kanters ca6493528d fix(ignore): Update dependencies (#24894) 2024-11-24 22:01:37 +01:00
Koen Kanters 1e77893f75 fix(ignore): update zigbee-herdsman-converters to 20.52.0 (#24874) 2024-11-22 20:55:41 +00:00
Koen Kanters 82a026e02e fix(ignore): update zigbee-herdsman-converters to 20.51.0 (#24856) 2024-11-21 20:58:55 +00:00
Koen Kanters 988984a1ae fix(ignore): update zigbee-herdsman-converters to 20.50.0 (#24843) 2024-11-20 20:27:28 +00:00
Koen Kanters 5859947207 fix(ignore): update zigbee-herdsman-converters to 20.49.1 (#24830) 2024-11-19 19:43:54 +00:00
Koen Kanters b1508ae52f Update zigbee-herdsman-converters to 20.48.0 (#24820)
* fix(ignore): update zigbee-herdsman-converters to 20.48.0

* u
2024-11-18 21:06:11 +00:00
Koen Kanters 152ad12508 fix(ignore): update zigbee-herdsman to 2.1.9 (#24819) 2024-11-18 20:05:50 +00:00
Koen Kanters 36e452b0f3 fix(ignore): update zigbee-herdsman-converters to 20.47.0 (#24772) 2024-11-14 20:36:53 +00:00
Koen Kanters 2a9bbb21ba fix(ignore): update zigbee-herdsman to 2.1.8 (#24763) 2024-11-13 20:56:59 +00:00
Koen Kanters f7e7115f79 chore: attempt to fix codeql.yaml 2024-11-13 21:53:01 +01:00
Koen Kanters 6e04f7b03e fix(ignore): update zigbee-herdsman-converters to 20.46.0 (#24762) 2024-11-13 20:49:13 +00:00
Nerivec 2a1ab78f7a chore: Update codeql config (#24751)
* Update codeql config

* Fix prettier
2024-11-13 21:01:37 +01:00
Koen Kanters e5d02c4b95 chore: merge master to dev 2024-11-12 21:30:24 +01:00
Koen Kanters 69f7282952 chore: fix README.md badge 2024-11-12 21:30:05 +01:00
Koen Kanters b8c3104aa1 fix(ignore): update zigbee-herdsman-converters to 20.45.0 (#24733) 2024-11-12 20:23:45 +00:00
Der Mundschenk & Compagnie 22e13c52b9 fix: Home Assistant event entities, part 2 (#24717)
* Add Philips Hue Tap dial rotation

* Add support for Hue Tap buttons

* Refactor action parsing
2024-11-11 21:10:20 +01:00
Koen Kanters 32e2637594 fix(ignore): update zigbee-herdsman-converters to 20.44.0 (#24715) 2024-11-10 19:39:18 +00:00
Koen Kanters 928635f1da fix(ignore): update dependencies (#24702) 2024-11-10 15:11:17 +01:00
Koen Kanters a353acc8e3 fix(ignore): update zigbee-herdsman-converters to 20.43.0 (#24680) 2024-11-07 19:53:05 +00:00
Koen Kanters b5d4a253b4 fix(ignore): update zigbee-herdsman-converters to 20.42.0 (#24666) 2024-11-06 20:47:04 +00:00
Boris Dolgov 7267d27779 fix: Temporarily disable serving compressed index file. (#24657)
express-static-gzip does not handle redirects with custom base URLs
properly. As index.html file is only 5KB, compression is not critical
for it.
2024-11-06 21:11:02 +01:00
Koen Kanters f2e5d59c35 fix(ignore): update zigbee-herdsman-converters to 20.41.0 (#24656) 2024-11-05 21:16:36 +00:00
Koen Kanters 1558a5d8ac fix(ignore): update zigbee-herdsman-converters to 20.40.0 (#24644) 2024-11-04 21:07:13 +00:00
Boris Dolgov 87cee1aea6 fix: Replace connect-gzip-static with express-static-gzip to become compatible with Node 23 (#24619)
Old versions of connect-gzip-static are not compatible with node 23,
new versions of connect-gzip-static are not compatible with node 18,
but we want to support both. express-static-gzip is compatible with
all versions.
2024-11-04 20:57:25 +01:00
Koen Kantersandnurikk 0c2cc10ad8 fix(ignore): update zigbee2mqtt-frontend to 0.7.5 (#24631)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-11-04 10:56:25 +00:00
Koen Kanters 84cc92f91a fix(ignore): update dependencies (#24614) 2024-11-03 16:00:30 +01:00
Koen Kanters a6bfab7934 Update zigbee-herdsman-converters to 20.39.2 (#24609)
* fix(ignore): update zigbee-herdsman-converters to 20.39.2

* u
2024-11-03 14:34:04 +00:00
Der Mundschenk & CompagnieandKoen Kanters 848f250dbb feat: Add experimental support for Home Assistant event entities (#24233)
* Expose new event entity for actions

* Unify exposed actions for HA

* Fix event tests

* Only add event entities wen homeassistant/experimental_event_entities is true

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-11-03 14:54:42 +01:00
Magnus Ihse BursieandKoen Kanters c078ccb136 fix: Home Assistant: remove the diagnostic category classification for power (#24575)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-11-02 18:56:57 +00:00
github-actions[bot] 67d3f05fd7 chore: promote to dev 2024-11-01 07:57:31 +00:00
Koen Kanters cc31ceabd1 chore(dev): release 1.41.0 (#24173) 2024-11-01 08:46:52 +01:00
Koen Kanters c5d30b585b fix(ignore): update zigbee-herdsman-converters to 20.38.0 (#24567) 2024-10-31 20:23:53 +00:00
Koen Kanters f1615a255c fix(ignore): update zigbee-herdsman to 2.1.7 (#24566) 2024-10-31 20:17:43 +00:00
Magnus Ihse Bursie a2ce3230ac fix: Add additional discovery lookups for Home Assistant (#24555) 2024-10-31 20:36:52 +01:00
Koen Kanters bd1f6d24a1 fix(ignore): update zigbee-herdsman-converters to 20.37.0 (#24553) 2024-10-30 19:47:11 +00:00
Koen Kanters 9837488f44 fix(ignore): update zigbee-herdsman to 2.1.6 (#24545) 2024-10-29 20:08:04 +00:00
Koen Kanters b620a21d46 fix(ignore): update zigbee-herdsman-converters to 20.36.0 (#24542) 2024-10-29 19:31:53 +00:00
Koen Kanters 2139e7a794 fix(ignore): update dependencies (#24498) 2024-10-29 20:26:52 +01:00
Bacchus777 e0a0f868c0 fix: Improve power_phase_b and power_phase_c Home Assistant discovery (#24538)
Added power_phase_b and power_phase_c
2024-10-29 20:07:07 +01:00
Cody Cutrer ab726061b7 fix: Remove extraneous whitespace in template for binary attributes (#24520)
I assume Home Assistant strips the whitespace at some point before
comparing it to payload_on/payload_off, but I haven't quite found where yet.
I have confirmed Python preserves the whitespace when simply evaluating
the Jinja.

For openHAB, it does not strip the whitespace, causing errors because
it doesn't match the payload_on or payload_off values. At some point I'd
like to bring openHAB inline with Home Assistant, but I need to do so
carefully without breaking something unexpectedly (especially with non-
Home Assistant MQTT integrations). In the meantime, this should fix the
issue in openHAB, without causing issues for Home Assistant or any other
software that follows Home Assistant's MQTT discovery process.
2024-10-28 21:16:40 +01:00
Koen Kanters 074b2e20e0 fix(ignore): update zigbee-herdsman-converters to 20.35.0 (#24514) 2024-10-27 20:54:05 +00:00
Koen Kanters 2b460d3a5f fix(ignore): update zigbee-herdsman-converters to 20.34.0 (#24493) 2024-10-26 13:20:20 +00:00
Koen Kanters c09aeecd95 fix(ignore): update zigbee-herdsman-converters to 20.33.0 (#24457) 2024-10-23 20:27:22 +00:00
Koen Kanters 9ad7c70f24 fix(ignore): update zigbee-herdsman-converters to 20.32.0 (#24434) 2024-10-21 20:24:51 +00:00
Koen Kanters 64778650d3 fix(ignore): update zigbee-herdsman to 2.1.5 (#24433) 2024-10-21 19:51:21 +00:00
Koen Kanters eb606a5b98 fix(ignore): update dependencies (#24411) 2024-10-20 20:22:33 +02:00
Koen Kanters 669a16f13a fix(ignore): update zigbee-herdsman-converters to 20.31.0 (#24408) 2024-10-19 18:48:46 +00:00
Koen Kanters b2d3bbce3c Update zigbee-herdsman-converters to 20.30.0 (#24398)
* fix(ignore): update zigbee-herdsman-converters to 20.30.0

* u
2024-10-19 12:00:50 +00:00
Koen Kanters 12cb6e135d fix(ignore): update zigbee-herdsman to 2.1.4 (#24397) 2024-10-18 18:11:34 +00:00
Koen Kanters d79e71b13e fix(ignore): update zigbee-herdsman-converters to 20.29.0 (#24363) 2024-10-16 20:07:32 +00:00
Koen Kanters dbda1481ec chore: improve testExternalConverter.js 2024-10-16 21:59:24 +02:00
Koen Kanters d12b117b43 fix(ignore): update zigbee-herdsman-converters to 20.28.0 (#24337) 2024-10-14 20:10:31 +00:00
Koen KantersandNerivec e2f19f19b4 fix: Frontend code cleanup @Nerivec (#24322)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2024-10-14 19:56:00 +02:00
Koen Kanters 618b318214 fix(ignore): update zigbee-herdsman-converters to 20.27.2 (#24321) 2024-10-13 20:32:13 +00:00
Koen Kanters 9f12ebb54e fix(ignore): update dependencies (#24309) 2024-10-13 20:10:19 +02:00
Koen Kanters db00759a03 fix(ignore): update zigbee-herdsman-converters to 20.26.0 (#24307) 2024-10-12 20:44:31 +00:00
Koen Kanters 5f601873fe fix(ignore): update zigbee-herdsman-converters to 20.25.0 (#24285) 2024-10-10 20:35:52 +00:00
Vladimir Kotikov 1fdf0a0a47 feat: Allow serving frontend under subpath (#24244)
* Allow app running in the server subdirectory

* Use path.posix to manage urls

* Add base_utl to config schema
2024-10-10 14:22:48 +02:00
Koen Kanters 3bb4af253a fix(ignore): update zigbee-herdsman-converters to 20.24.0 (#24246) 2024-10-06 21:07:58 +00:00
Massimiliano 076ada2325 fix: Fix group state change for open/close devices (#24226)
* Fix group state change if OPEN

* Runned Prettier
2024-10-06 21:35:46 +02:00
Koen Kanters 5c3f71292d fix(ignore): update dependencies (#24236) 2024-10-06 21:33:07 +02:00
Koen Kanters 02292590ee fix(ignore): update zigbee-herdsman-converters to 20.23.1 (#24225) 2024-10-05 13:40:41 +00:00
Koen Kanters b09ae10588 fix(ignore): update zigbee-herdsman-converters to 20.23.0 (#24201) 2024-10-03 19:34:26 +00:00
Koen Kanters e5fac76060 fix(ignore): update zigbee-herdsman-converters to 20.22.0 (#24186) 2024-10-02 19:48:01 +00:00
Ivan F. MartinezandKoen Kanters 17ac9cf8e7 feat: Add throttle option for devices (#24122)
* basic spam control

* used npm run pretty:write

* add test for SPAMMER description to comply with 100% coverage test

* define friendly name to spammer test devices

* Update README.md

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

* trying now with throttleit library

* lint corrections

* last lint request

* correct await / async definiction

* remove description support

* change first command to be executed

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-10-02 21:15:15 +02:00
github-actions[bot] 063d4620d2 chore: promote to dev 2024-10-01 19:41:45 +00:00
Koen Kanters e06848db8c chore(dev): release 1.40.2 (#23885) 2024-10-01 21:30:57 +02:00
Koen Kanters c82f93230d fix(ignore): update zigbee-herdsman-converters to 20.21.0 (#24172)
* fix(ignore): update zigbee-herdsman-converters to 20.21.0

* fix
2024-10-01 21:27:13 +02:00
Koen Kanters 13e4937db2 fix(ignore): update zigbee-herdsman to 2.1.3 (#24171) 2024-10-01 18:45:21 +00:00
Koen Kanters 58c856bbd0 fix(ignore): update zigbee-herdsman-converters to 20.20.0 (#24144) 2024-09-29 18:25:56 +00:00
Koen Kanters df1913bc56 fix(ignore): update zigbee-herdsman to 2.1.2 (#24138) 2024-09-29 11:47:51 +00:00
Koen Kanters 37f80dfb69 fix(ignore): update dependencies (#24132) 2024-09-29 13:20:17 +02:00
Koen Kanters 8fe76e090f fix(ignore): update zigbee-herdsman-converters to 20.19.0 (#24130)
* fix(ignore): update zigbee-herdsman-converters to 20.19.0

* fix tests
2024-09-28 18:47:32 +00:00
Koen Kanters 15d845bf92 fix(ignore): update zigbee-herdsman-converters to 20.18.0 (#24105) 2024-09-25 19:33:27 +00:00
Koen Kanters 22e1480f74 Update zigbee-herdsman-converters to 20.17.1 (#24092)
* fix(ignore): update zigbee-herdsman-converters to 20.17.1

* Update
2024-09-24 20:29:25 +00:00
Koen Kanters 5dd1bfb8a6 fix(ignore): update zigbee-herdsman to 2.1.1 (#24093) 2024-09-24 20:01:58 +00:00
Koen Kanters b1c7fd0033 fix(ignore): update zigbee-herdsman-converters to 20.17.0 (#24077) 2024-09-23 18:46:59 +00:00
Koen Kanters 2314147d0e fix(ignore): update zigbee-herdsman to 2.1.0 (#24076) 2024-09-23 18:35:57 +00:00
Koen Kanters ee4becb64e fix(ignore): update zigbee-herdsman to 2.0.0 (#24064) 2024-09-22 19:36:21 +00:00
Koen Kanters a5bb49aa71 fix(ignore): update dependencies (#24057)
* fix(ignore): update dependencies

* u
2024-09-22 20:08:50 +02:00
Koen Kanters d289194a71 fix(ignore): update zigbee-herdsman to 1.1.0 (#24054) 2024-09-21 19:06:54 +00:00
Koen Kanters 7ad51ce662 fix: Fix Home Assistant Invalid state message error when state is too long (#24045) 2024-09-21 00:06:44 +02:00
Koen Kanters 951bbd3576 fix(ignore): update zigbee-herdsman-converters to 20.16.3 (#24033) 2024-09-19 20:17:21 +00:00
Koen Kanters 8a54a01583 fix(ignore): update zigbee-herdsman to 1.0.1 (#24032) 2024-09-19 19:58:47 +00:00
Koen Kanters e7a3f0da0a fix(ignore): update zigbee-herdsman-converters to 20.16.2 (#24022) 2024-09-18 20:09:35 +00:00
Koen Kanters 2ae19a7c42 fix(ignore): update zigbee-herdsman-converters to 20.16.1 (#24009) 2024-09-17 20:30:55 +00:00
Koen Kanters da2a6974e0 fix(ignore): update zigbee-herdsman to 1.0.0 (#24008) 2024-09-17 20:24:12 +00:00
Koen Kanters d3de77da13 fix(ignore): update zigbee-herdsman-converters to 20.16.0 (#23990) 2024-09-16 20:49:03 +00:00
Koen Kanters 28650daf83 chore: fix cedc0623f7 2024-09-16 22:46:37 +02:00
cedc0623f7 chore: Fix CI - trigger helm update (#23977)
* Fix CI - trigger helm update

* Update .github/workflows/ci.yml

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

---------

Co-authored-by: jose <jose.pedrosa@reddit.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-09-16 20:23:13 +02:00
Koen Kanters 3efc2b237b fix(ignore): update dependencies (#23973) 2024-09-15 20:46:51 +02:00
Koen Kanters 03b5b0043a fix(ignore): update zigbee-herdsman-converters to 20.15.0 (#23969) 2024-09-14 19:01:07 +00:00
Koen Kanters 70a65c7dcf fix(ignore): update zigbee-herdsman to 0.57.4 (#23968) 2024-09-14 18:46:56 +00:00
Koen Kanters eb8685682f fix(ignore): update zigbee-herdsman-converters to 20.14.1 (#23952) 2024-09-12 21:02:39 +00:00
Koen Kanters e642f7c86a fix(ignore): Sync eslint settings from zhc (#23951)
* fix(ignore): Sync eslint settings from zhc

* u
2024-09-12 19:36:57 +00:00
Tomer c6ca472ca1 fix: Improve performance when logging is disabled (#23893)
* Improve log performance

* Bug fix

* Revert sample configuration

* Align package versions

* Revert dependecies version to allow testing

* Update package-lock.json

* Fix prettier issue

* Coverage to 100%

* Fix CR comments

* remove uneeded changes

* Make the same perf optimization to the MQTT messages

* Fix MQTT tests

* prettier fix

* Allow easy mocking of stub logger callTransports function to receive the actual message

* cleanup leftover

* Fix prettier issues

* Finalize logger mock structure and add few lambda log calls

* fix prettier issue

* Run prettier

* Revert "fix prettier issue"

This reverts commit b409d538e3.

* Update zigbee.ts

Fix prettier issue

* Fix test issues

* Revert await removal
2024-09-12 20:33:05 +02:00
Koen Kanters 0540e4fbb4 fix(ignore): update zigbee-herdsman-converters to 20.13.0 (#23927) 2024-09-09 20:13:08 +00:00
Koen Kanters 9c72a768f2 fix(ignore): Improvement for afd80449b3 2024-09-08 21:33:56 +02:00
Koen Kanters 8c0476612f fix(ignore): update dependencies (#23904) 2024-09-08 14:28:03 +02:00
Koen Kanters afd80449b3 fix(ignore): Migrate to eslint 9 (#23800)
* Eslint 9

* Automatic changes

* Manual changes

* Process feedback

* u
2024-09-08 14:26:18 +02:00
Koen Kanters d989061122 fix(ignore): update dependencies (#23898) 2024-09-08 13:08:03 +02:00
NerivecandKoen Kanters 48d77e4b5e fix: Enforce TS strict type checking (#23601)
* Enforce TS `strict` type checking.

* updates

* updates

* updates

* Updates

* Updates

* pretty

* u

* u

* u

* Updates

* updates

* Updates

* Updates

* `ReadonlyArray`

* scenesChanged

* objectID

* Improve coverage

* u

* u

* process feedback

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-09-07 11:40:50 +00:00
github-actions[bot] 63c3bb4166 chore: promote to dev 2024-09-07 11:33:51 +00:00
Koen Kanters 403d3c090d chore(dev): release 1.40.1 (#23797) 2024-09-07 13:25:22 +02:00
Koen Kanters 9283c4b497 fix(ignore): update zigbee-herdsman-converters to 20.12.1 (#23884) 2024-09-07 11:17:24 +00:00
Koen Kanters 0d46354c48 fix(ignore): update zigbee-herdsman to 0.57.3 (#23877) 2024-09-06 20:26:15 +00:00
Koen Kanters 13a108ddbc fix: Log stack trace when update check fails https://github.com/Koenkk/zigbee2mqtt/discussions/23798 2024-09-06 14:37:20 +02:00
Koen Kanters 5524e881e6 fix(ignore): update zigbee-herdsman to 0.57.2 (#23872) 2024-09-06 12:19:21 +00:00
Koen Kanters a33c29f10a fix(ignore): update zigbee-herdsman-converters to 20.11.0 (#23837) 2024-09-05 12:39:50 +00:00
Koen Kanters fc02071c8f chore: fix e2da2fa8d3 2024-09-05 14:37:30 +02:00
Koen Kanters 06556d44f3 chore: merge master to dev 2024-09-05 13:35:31 +02:00
Koen Kanters 13084d22a7 chore: Update new device support template 2024-09-05 13:35:15 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 14787d3a6a chore(deps): bump peter-evans/create-pull-request from 6 to 7 (#23832)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7)

---
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>
2024-09-03 20:21:00 +02:00
Jose Luis Pedrosaandjose e2da2fa8d3 chore: Invoke chart CI (#23819)
The workflow name on the chart repository is OnZigbee2MQTTRelease, this event means that a new version of zigbe2mqtt is has been released, it should probably trigger too the release, but that is a chart repo concern

Co-authored-by: jose <jose.pedrosa@reddit.com>
2024-09-03 20:20:40 +02:00
Koen Kanters 6dade8b11f fix(ignore): update zigbee-herdsman-converters to 20.9.0 (#23822) 2024-09-02 20:48:09 +00:00
Koen Kanters 557b17ad5b fix: Fix Cannot read properties of undefined (reading 'some') https://github.com/Koenkk/zigbee2mqtt/issues/23821 2024-09-02 22:22:19 +02:00
Koen Kanters 64222d231f Update zigbee-herdsman-converters to 20.8.5 (#23799)
* fix(ignore): update zigbee-herdsman-converters to 20.8.5

* Update utils.ts
2024-09-01 19:23:14 +00:00
Koen Kanters b7fa722109 fix(ignore): update dependencies (#23790) 2024-09-01 20:23:49 +02:00
github-actions[bot] 4a1d983168 chore: promote to dev 2024-09-01 18:22:59 +00:00
Koen Kanters 6fa1dabecb chore(dev): release 1.40.0 (#23473) 2024-09-01 20:12:13 +02:00
Koen Kanters 3b2ba0950e fix(ignore): update zigbee-herdsman-converters to 20.8.4 (#23791) 2024-09-01 18:10:07 +00:00
Koen Kanters 9f40936452 fix(ignore): update zigbee-herdsman-converters to 20.8.1 (#23783) 2024-08-30 18:35:59 +00:00
Koen Kanters b2bae6ba94 fix: Add logging when loading external converters https://github.com/Koenkk/zigbee2mqtt/issues/23703 2024-08-30 20:23:55 +02:00
Koen Kanters 406deaa9da fix(ignore): update zigbee-herdsman to 0.57.1 (#23781) 2024-08-30 18:14:13 +00:00
Koen Kanters 65b1e60a3d fix(ignore): update zigbee-herdsman-converters to 20.8.0 (#23777) 2024-08-29 19:28:15 +00:00
Koen Kanters 6ed9b26657 fix(ignore): update zigbee-herdsman-converters to 20.7.0 (#23759) 2024-08-27 18:57:31 +00:00
Koen Kanters cf3298bf6f fix: Update watchdog delays (#23757) 2024-08-27 20:50:24 +02:00
Koen Kanters 00948c0be1 chore: merge master to dev 2024-08-27 20:48:05 +02:00
Koen Kanters ed9de74f91 chore: Update New device support request template 2024-08-27 20:47:45 +02:00
Koen Kanters 41587211e0 fix(ignore): update zigbee-herdsman to 0.57.0 (#23758) 2024-08-27 18:43:23 +00:00
Ilya Kirov b4176d6fee feat: Add support for zboss adapter (#23747)
https://github.com/Koenkk/zigbee-herdsman/pull/1165
2024-08-27 20:37:00 +02:00
Koen Kanters ccdf6b3c6d fix(ignore): update zigbee-herdsman-converters to 20.6.0 (#23750) 2024-08-26 19:06:20 +00:00
Koen Kanters 8a05077792 fix(ignore): update dependencies (#23731) 2024-08-26 20:12:08 +02:00
Koen Kanters df7772bfe0 fix(ignore): update zigbee-herdsman-converters to 20.5.0 (#23723) 2024-08-24 13:07:53 +00:00
Koen Kanters 6ae9970efd fix(ignore): update zigbee-herdsman-converters to 20.4.0 (#23692) 2024-08-21 19:45:53 +00:00
Koen Kanters b92072fc6a Merge remote-tracking branch 'origin/master' into dev 2024-08-20 21:38:19 +02:00
Koen Kanters 5bdf90df9e chore: Trigger zigbee2mqtt-chart release on release https://github.com/Koenkk/zigbee2mqtt-chart/issues/7 2024-08-20 21:36:01 +02:00
Koen Kanters 22980177b6 fix(ignore): Fix crash when retrieving state (#23679) 2024-08-20 20:57:09 +02:00
Koen Kanters 837cbe5f26 fix(ignore): update zigbee-herdsman-converters to 20.3.0 (#23666) 2024-08-19 19:13:00 +00:00
Koen Kanters e95116c773 fix(ignore): update zigbee-herdsman to 0.56.2 (#23658) 2024-08-18 12:38:04 +00:00
Koen Kanters 27300ba8da fix(ignore): update dependencies (#23653) 2024-08-18 14:03:00 +02:00
Koen Kanters 801ab9066f fix(ignore): update zigbee-herdsman to 0.56.0 (#23643) 2024-08-16 20:57:46 +00:00
Koen Kanters 2a631f3551 chore: merge master to dev 2024-08-16 22:31:18 +02:00
Koen Kanters 66fefaa827 chore: Add fail_pr_to_master.yaml 2024-08-16 22:30:56 +02:00
Koen Kanters c986fd6c66 fix(ignore): update zigbee-herdsman-converters to 20.2.0 (#23628) 2024-08-15 19:32:00 +00:00
Koen Kanters 29f436ab95 fix(ignore): update zigbee-herdsman-converters to 20.1.1 (#23612) 2024-08-13 19:40:53 +00:00
Koen KantersandKoenkk d827d954ed fix(ignore): update zigbee-herdsman-converters to 20.0.0 (#23595)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-12 11:39:30 +00:00
Koen Kanters 8ae2c4d42d chore: merge master to dev 2024-08-12 13:15:23 +02:00
Koen Kanters 9027c41fe8 Revert "Added pictures for _TZE204_ncti2pro (ZSVIOT PN6) (#23552)"
This reverts commit 00b5e983d9.
2024-08-12 13:15:02 +02:00
Andre Klärner a5748d620b fix: Add OCI labels to image (#23578)
this adds Docker Labels according to the OCI Image Format Specification
which means that tools like renovate can automatically find the
changelogs from a docker image, helping the people like me judge the
impact of upgrading their z2m instance from the pull request renovate
generates.
2024-08-11 21:59:44 +02:00
Koen Kanters d1734feb21 chore: merge master to dev 2024-08-09 14:37:08 +02:00
Vitaliy 00b5e983d9 Added pictures for _TZE204_ncti2pro (ZSVIOT PN6) (#23552)
* Added pictures for _TZE204_ncti2pro (ZSVIOT PN6)

* Removed jpg file and renamed TS0601_pn6_way_controller.png to PN6.png
2024-08-09 14:36:52 +02:00
Koen Kanters efd3d3dfb0 fix: Set zigbee-herdsman transmit power through settings on startup (#23566)
* fix: Set zigbee-herdsman transmit power through settings on startup

* use advanced
2024-08-09 12:33:59 +00:00
Koen KantersandKoenkk aeba93d0a3 fix(ignore): update zigbee-herdsman-converters to 19.75.0 (#23565)
* fix(ignore): update zigbee-herdsman-converters to 19.75.0

* fix

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-08 21:14:32 +02:00
Koen KantersandKoenkk 7ca456d20e fix(ignore): update zigbee-herdsman to 0.55.5 (#23564)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-08 19:06:42 +00:00
Nerivec 13ac8a0f53 fix: Improve looping performance (#23541)
* Use herdsman improved looping.

* Fix lint.

* Fix tests.

* Feedback
2024-08-08 20:21:40 +02:00
Koen KantersandKoenkk fe6cacd87e fix(ignore): update zigbee-herdsman-converters to 19.74.0 (#23539)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-06 19:42:34 +00:00
Koen KantersandKoenkk 00923685d2 fix(ignore): update zigbee-herdsman-converters to 19.73.1 (#23527)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-05 21:14:09 +00:00
Koen KantersandKoenkk bd7dec0f11 fix(ignore): update dependencies (#23508)
* fix(ignore): update dependencies

* update

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-05 21:08:40 +00:00
Koen Kanters 9bb21f486c chore: merge master to dev 2024-08-04 22:14:52 +02:00
LaurentandKoen Kanters d0f5733db3 fix: Remove group from Home Assistant when removed (#23492)
* Fire event when group gets removed

* Add a unit test for the new behavior

* Make prettier happy

* Add missing return type

* Merge device and group removal events

* Unbreak bridgeLegacy.ts

* Update homeassistant.ts

* Update bridge.ts

* Update bridge.ts

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-08-04 22:14:36 +02:00
Koen Kanters 5b610afa8f fix: Add min/max for some adapter options (#23503)
* fix: Add min/max for some adapter options

* update
2024-08-04 19:53:29 +00:00
Koen KantersandKoenkk 2cb8a8f5a5 fix(ignore): update zigbee-herdsman-converters to 19.73.0 (#23495)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-03 11:05:54 +00:00
Koen Kanters 3217332e46 fix: Fix expose category ignored by HA discovery (#23494) 2024-08-03 13:00:29 +02:00
Koen KantersandKoenkk a25ef81cf4 fix(ignore): update zigbee-herdsman to 0.55.4 (#23493)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-03 10:31:41 +00:00
Koen Kanters eac11a6439 chore: fix package.json indentation 2024-08-01 21:11:44 +02:00
github-actions[bot] a330830cde chore: promote to dev 2024-08-01 18:57:20 +00:00
Koen Kanters e132316ac4 chore(dev): release 1.39.1 (#23221) 2024-08-01 20:42:11 +02:00
Koen KantersandKoenkk 3dc9a05b0c fix(ignore): update zigbee-herdsman-converters to 19.72.0 (#23472)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-08-01 18:17:31 +00:00
Koen Kanters 7cc5209af3 chore: apply prettier to whole repo 2024-07-29 21:26:33 +02:00
Koen Kanters 02dc8a7b87 chore: apply prettier to whole repo 2024-07-29 21:21:24 +02:00
Koen KantersandKoenkk 3a573da83c fix(ignore): update dependencies (#23440)
* fix(ignore): update dependencies

* fix

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-28 14:55:34 +02:00
Koen Kantersandnurikk 26718eefea fix(ignore): update zigbee2mqtt-frontend to 0.7.4 (#23432)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-07-26 21:09:02 +00:00
Koen KantersandKoenkk 115b187c1a fix(ignore): update zigbee-herdsman to 0.55.3 (#23431)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-26 20:57:00 +00:00
Koen KantersandKoenkk fd65b9a108 fix(ignore): update zigbee-herdsman to 0.55.2 (#23422)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-25 20:59:34 +00:00
Koen Kanters d9ba4524e4 fix(ignore): fix 5fcf295ab8 2024-07-25 21:59:40 +02:00
Koen Kanters 5fcf295ab8 fix: Fix race condition in frontend extension (#23412) 2024-07-24 20:20:24 +00:00
Nerivec b7d9d9e142 fix: Improve looping when retrieving all devices from herdsman (#23405) 2024-07-24 21:58:48 +02:00
Koen KantersandKoenkk 1bf3032b98 fix(ignore): update zigbee-herdsman-converters to 19.71.1 (#23404)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-23 20:26:33 +00:00
Koen Kantersandnurikk 85453091e7 fix(ignore): update zigbee2mqtt-frontend to 0.7.3 (#23394)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-07-22 19:45:24 +00:00
Koen Kanters 26b1613948 fix(ignore): Remove some unecessary string concatenations 2024-07-22 21:33:24 +02:00
Koen KantersandKoenkk c32c411f88 fix(ignore): update zigbee-herdsman to 0.55.1 (#23392)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-22 18:33:07 +00:00
Koen KantersandKoenkk cda8927dc7 fix(ignore): update zigbee-herdsman to 0.55.0 (#23370)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-18 18:42:17 +00:00
Koen KantersandKoenkk 8c71afd93c fix(ignore): update zigbee-herdsman-converters to 19.70.0 (#23359)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-17 20:03:44 +00:00
Koen KantersandKoenkk 712b920abb fix(ignore): update zigbee-herdsman to 0.54.1 (#23358)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-17 19:13:50 +00:00
Koen KantersandKoenkk f795399e10 fix(ignore): update zigbee-herdsman to 0.54.0 (#23351)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-16 20:07:55 +00:00
Koen Kanters 541221bbbd fix(ignore): Update channel change description (#23333)
* fix(ignore): Update channel change description

* Update settings.schema.json
2024-07-16 22:02:41 +02:00
Koen KantersandKoenkk 24c9159e19 fix(ignore): update zigbee-herdsman-converters to 19.69.0 (#23346)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-15 20:45:09 +00:00
Koen KantersandKoenkk 5d05ebbfd2 fix(ignore): update dependencies (#23276)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-15 21:53:56 +02:00
Koen KantersandKoenkk bd78f56d5f fix(ignore): update zigbee-herdsman-converters to 19.68.0 (#23338)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-13 20:05:52 +00:00
Koen Kanters af7467bf62 fix(ignore): Update test cases for https://github.com/Koenkk/zigbee2mqtt/pull/23328 2024-07-13 21:20:33 +02:00
Koen Kanters e3fa0bfb65 fix: Ignore cache when re-interviewing device (#23328)
* fix: Ignore cache when re-interviewing device

* updates
2024-07-12 10:15:33 +00:00
Koen KantersandKoenkk 00661fac4b fix(ignore): update zigbee-herdsman to 0.53.1 (#23331)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-12 12:12:30 +02:00
Koen KantersandKoenkk 2fbb73a009 fix(ignore): update zigbee-herdsman-converters to 19.67.0 (#23327)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-11 19:33:03 +00:00
Koen Kanters 8397f47649 fix: Fix availability blocking other extensions startup (#23316)
* fix: Fix availability blocking frontend startup

* Process feedback

* fix
2024-07-11 21:07:01 +02:00
Koen KantersandKoenkk fe87b5dec7 fix(ignore): update zigbee-herdsman to 0.53.0 (#23317)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-10 19:54:18 +00:00
Koen KantersandKoenkk 2916d7ee52 fix(ignore): update zigbee-herdsman-converters to 19.66.0 (#23306)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-09 19:16:08 +00:00
Koen KantersandKoenkk eee1da620f fix(ignore): update zigbee-herdsman to 0.52.0 (#23299)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-08 20:00:37 +00:00
Koen KantersandKoenkk 5663afb27e fix(ignore): update zigbee-herdsman-converters to 19.65.0 (#23272)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-06 09:59:51 +00:00
Koen Kanters 1bf385c5ff fix(ignore): fix 6b9247c84d 2024-07-06 11:56:16 +02:00
Koen Kanters 6b9247c84d fix: Truncate text of Home Assistant text sensor https://github.com/Koenkk/zigbee2mqtt/issues/23199 2024-07-06 11:22:26 +02:00
Nerivec 6cb62e53cf fix: Fix syslog output (#23269) 2024-07-06 10:57:24 +02:00
Koen Kanters f97ad2e951 fix(ignore): fix tests 2024-07-03 22:37:12 +02:00
Koen KantersandKoenkk e917802f5f fix(ignore): update zigbee-herdsman-converters to 19.64.0 (#23252)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-03 22:23:38 +02:00
Gernot MessowandKoen Kanters 4210edf60e fix: Allow definition to override HA discovery payload (#23075)
* Added function to override payloads for non-conforming devices

* override implementation for Bosch BTH-RA
* unit test including mockup for this device

* Experimental and broken integration of overrideHaConfig

* Not working experiments; mqtt publish debug output

* fixes

* Updates

* fix

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-07-03 21:27:45 +02:00
Koen KantersandKoenkk addc2385b6 fix(ignore): update zigbee-herdsman-converters to 19.63.0 (#23244)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-02 20:05:00 +00:00
Koen KantersandKoenkk 2dbbed14f0 fix(ignore): update zigbee-herdsman to 0.51.0 (#23223)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-07-01 18:52:01 +00:00
github-actions[bot] 5c42883779 chore: promote to dev 2024-07-01 18:32:21 +00:00
Koen Kanters 032692673b chore(dev): release 1.39.0 (#22868) 2024-07-01 20:25:54 +02:00
Koen Kanters 2d2b78e9b5 chore: fix 24c43d9400 2024-06-30 22:14:26 +02:00
Koen Kanters 24c43d9400 chore: fix changelog generator 2024-06-30 22:12:49 +02:00
Koen Kanters 2885eee795 chore: improve changelog generator 2024-06-30 21:50:33 +02:00
Koen KantersandKoenkk 8196460de0 fix(ignore): update zigbee-herdsman-converters to 19.62.0 (#23214)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-30 19:10:26 +00:00
Ikko Eltociear Ashimine 5dde063119 chore: update homeassistant.ts (#23213)
unecessary -> unnecessary
2024-06-30 20:59:53 +02:00
Koen Kanters dc7a06cf56 fix: Don't allow homeassistant.discovery_topic to be equal to mqtt.base_topic https://github.com/Koenkk/zigbee2mqtt/issues/23109 (#23209) 2024-06-30 10:42:24 +00:00
Koen KantersandKoenkk 6ba42c6a12 fix(ignore): update zigbee-herdsman to 0.50.1 (#23208)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-30 10:38:00 +00:00
Koen KantersandKoenkk 940422a9d8 fix(ignore): update dependencies (#23207)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-30 12:32:07 +02:00
Koen KantersandKoenkk fa24d2d2ba fix(ignore): update zigbee-herdsman-converters to 19.61.0 (#23203)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-29 11:55:30 +00:00
Koen KantersandKoenkk c48db3f5b9 fix(ignore): update zigbee-herdsman-converters to 19.60.1 (#23163)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-25 19:14:58 +00:00
Koen Kanters 30227a13ae chore: Implement prettier (#23153)
* chore: Implement prettier

* Run prettier

* fix lint

* process feedback

* process feedback
2024-06-24 18:58:47 +00:00
Koen KantersandKoenkk 8780ab2792 fix(ignore): update dependencies (#23137)
* fix(ignore): update dependencies

* fix

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-24 12:01:44 +02:00
Koen KantersandKoenkk dec9f10133 fix(ignore): update zigbee-herdsman-converters to 19.59.2 (#23152)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-24 09:25:15 +00:00
Koen KantersandKoenkk ee42d68472 fix(ignore): update zigbee-herdsman-converters to 19.59.1 (#23141)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-23 09:32:59 +00:00
Jorge SchrauwenandKoen Kanters ad84374046 fix: Fix OTA overriding custom software version attribute (#23129)
* fix: OTA loses version info for develco/frient

This fix is not perfect, as it will still print that it got updated from null to null.

We just moved the reconfigure later, which will trigger this https://github.com/Koenkk/zigbee-herdsman-converters/blob/000041cba216822dab151151c493b560678da91a/src/devices/develco.ts#L36
for develco/frient devices that have it in their configure. (Which should be all of them)

Of note though, ZCL marks both dateCode and swBuildId as optional attributes, so Develco/Frient returning UNSUPPORTED_ATTRIBUTE here is not outside of spec. Although I'm not sure there is a cleaner way to do this.

* Update otaUpdate.test.js

* Update otaUpdate.ts

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-06-23 11:24:03 +02:00
Nerivec 3d772b5db3 fix: Bind code optimisation/formatting (#23110)
* Bind code optimization/formatting

* Feedback

* Fix coverage
2024-06-23 10:38:00 +02:00
Koen KantersandKoenkk 8877c9e864 fix(ignore): update zigbee-herdsman-converters to 19.59.0 (#23130)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-22 10:54:46 +00:00
Koen KantersandKoenkk 889e41ca4e fix(ignore): update zigbee-herdsman-converters to 19.58.0 (#23114)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-20 11:13:59 +00:00
Nerivec 2b36f7492b fix: Optional watchdog for "adapter disconnected"-type events (non-node-crash) (#23043)
* Watchdog for "adapter disconnected"-type events (non-node-crash)

* Reset watchdog count on successful start.

* Cleanup

* Switch from argv to env.
2024-06-19 20:51:04 +02:00
Koen Kanters bcc36811a9 chore: merge master to dev 2024-06-19 12:53:13 +02:00
dependabot[bot] 6c7d52a3ce chore(deps-dev): bump braces from 3.0.2 to 3.0.3 (#23105) 2024-06-19 12:52:44 +02:00
Koen Kanters 9f1d6f7d94 chore: merge master to dev 2024-06-19 12:48:53 +02:00
dependabot[bot] 89d3acf1d4 chore(deps): bump ws from 8.17.0 to 8.17.1 (#23100) 2024-06-19 12:48:36 +02:00
Koen KantersandKoenkk 70e84ddc90 fix(ignore): update zigbee-herdsman-converters to 19.57.1 (#23099)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-18 19:10:38 +00:00
Nerivec 9c2264e86e fix: MQTT code optimization/formatting (#23072)
* MQTT code optimization/formatting

* Feedback.

* Move `onConnect` logic inside `connect` event.
2024-06-18 13:59:07 +02:00
Koen KantersandKoenkk a85d3e4331 fix(ignore): update zigbee-herdsman-converters to 19.57.0 (#23077)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-16 15:47:07 +00:00
Koen Kanters d45e254f50 fix(ignore): update dependencies (#23073) 2024-06-16 17:42:28 +02:00
Nerivec f57465324a fix: Availability code optimization/formatting (#23065)
* Availability code optimization/formatting

* Fix mock.

* Fix map => some
2024-06-16 17:25:40 +02:00
Koen KantersandKoenkk 83ac5649d3 fix(ignore): update zigbee-herdsman-converters to 19.56.0 (#23068)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-15 13:26:21 +00:00
ghoz d41cf43a7c fix: Exclude homeassistant entries from null cleanup (#22995)
* exclude homeassistant entries from null cleanup

* Add a test

* Don't hardcode the exclude list, add one more test

* implement suggested changes as per review
2024-06-15 10:27:29 +02:00
Rotzbua fcdae971e5 fix: Remove outdated @types/debounce (#23063)
`debounce` now includes types.
https://github.com/sindresorhus/debounce/releases/tag/v2.0.0
2024-06-15 10:26:38 +02:00
ghoz ef2c939482 fix: Don't call onEvent on disabled devices (#23058)
As z2m requires a restart for  enable/disable to be effective,
we don't need to handle changes at runtime (stop events etc)
2024-06-15 10:26:06 +02:00
Koen KantersandKoenkk 85356a2932 fix(ignore): update zigbee-herdsman to 0.50.0 (#23064)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-14 21:29:13 +00:00
Koen KantersandKoenkk a371347ceb fix(ignore): update zigbee-herdsman-converters to 19.55.0 (#23039)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-13 19:48:53 +00:00
Koen Kanters 0754fa8552 fix(ignore): Improve network key hiding https://github.com/Koenkk/zigbee-herdsman/pull/1086 2024-06-13 21:19:09 +02:00
Koen KantersandKoenkk 47bba42357 fix(ignore): update zigbee-herdsman-converters to 19.54.0 (#23027)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-12 19:23:25 +00:00
ghoz eff341bd14 fix: Differentiate the MQTT publish/ received from regular MQTT logs (#23026)
* Differentiate the spammy "MQTT publish"/"received MQTT message" from regular logs

Rather than creating subnamespaces, keep the z2m:mqtt logger namespace only for those 2 , move back to z2m default for the rest.

* fix tests
2024-06-12 21:01:16 +02:00
Nerivec 6064194e52 fix: Groups code optimization/formatting (#23015) 2024-06-11 21:08:01 +02:00
Koen Kanters 69541c6348 chore: merge master to dev 2024-06-10 22:18:32 +02:00
Koen Kanters f18473017e chore: Fix problem_report template debugging link 2024-06-10 22:18:15 +02:00
Koen KantersandKoenkk aa53303c0f fix(ignore): update zigbee-herdsman-converters to 19.53.0 (#23007)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-10 20:14:26 +00:00
Koen Kanters dbcae00316 fix(ignore): update dependencies (#22986) 2024-06-09 15:48:05 +02:00
Koen KantersandKoenkk a4a0c6f3b9 fix(ignore): update zigbee-herdsman-converters to 19.52.0 (#22982)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-08 16:29:39 +00:00
Koen KantersandKoenkk fd3d9a318d fix(ignore): update zigbee-herdsman-converters to 19.51.0 (#22953)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-05 18:45:20 +00:00
Koen KantersandKoenkk 76959067e2 fix(ignore): update zigbee-herdsman to 0.49.3 (#22952)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-05 18:24:11 +00:00
Nerivec 282dfd5b01 fix: Fix floating promises originating from index.js (#22943) 2024-06-05 20:19:53 +02:00
Koen Kantersandnurikk cdfbb17b4a fix(ignore): update zigbee2mqtt-frontend to 0.7.2 (#22938)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-06-04 19:08:43 +00:00
Koen KantersandKoenkk 9d95d61e32 fix(ignore): update zigbee-herdsman-converters to 19.50.1 (#22936)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-04 18:36:16 +00:00
ghozandNerivec 881037c371 feat: Allow hierarchical filtering of logger namespaces (#22859)
* POC: Allow hierarchical filtering of logger namespaces

matches the closes defined namespace log level:

advanced:
  log_namespaced_levels:
    'zh:zstack:unpi': info

would also apply to zh:zstack:unpi:write and zh:zstack:unpi:parse

To limit runtime penalty uses the namespacedLevels as a lazy cache

* Move namespace levels to separate cache

Avoid poluting the namespacedLevel in the config / UI
Add cache reset on config time log level change

* Actually use the cache

* Optimize/Cleanup

* Add some namespace hierarchy filter tests

* More coverage.

* Fix #22935

---------

Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2024-06-04 20:17:35 +02:00
Nerivec 2b8eaa196a fix: Enforce no floating promises (#22880)
* Enforce no floating promises.

* forEach => for/of. Fix tests.
2024-06-04 20:05:39 +02:00
Adam Goode dbf0a3358d fix: Set voc_index to mdi:molecule to match other VOC icons in Home Assistant (#22920) 2024-06-04 20:00:30 +02:00
Koen KantersandKoenkk 6fbf14cced fix(ignore): update zigbee-herdsman-converters to 19.50.0 (#22918)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-03 18:55:10 +00:00
Adam Goode 40ad896114 fix: Support ppb and ppm for VOC reporting in Home Assistant (#22895)
See https://github.com/home-assistant/core/pull/92973.
2024-06-03 20:11:40 +02:00
Koen KantersandKoenkk 6c123c28b3 fix(ignore): update zigbee-herdsman-converters to 19.49.0 (#22902)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-02 19:25:26 +00:00
Adam Goode 519e123b69 fix: Use occupancy device_class instead of motion for occupancy sensors (#22896)
'occupancy' matches the zigbee terminology more closely and is more
consistent with other Home Assistant entities of this type.

https://www.home-assistant.io/integrations/binary_sensor/

In general, it looks like 'motion' is more for alarms, and 'occupancy'
for non-alarm situations. Z2M currently makes no distinction between
these concepts, but they could be separated (with a fair amount of work).
2024-06-02 21:11:00 +02:00
Koen Kanters 8b4f753c5d chore: merge master to dev 2024-06-02 15:59:59 +02:00
Koen Kanters 0e6da85d19 chore: fix problem_report.yaml logging link 2024-06-02 15:59:38 +02:00
Koen KantersandKoenkk 8f195f67e6 fix(ignore): update dependencies (#22883)
* fix(ignore): update dependencies

* Update homeassistant.test.js

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-02 11:33:34 +02:00
Koen Kanters 457ed86ef8 fix(ignore): TuYa -> Tuya https://github.com/Koenkk/zigbee2mqtt/discussions/22876 2024-06-01 16:44:22 +02:00
Koen Kanters 6924db79e8 fix: Add logging for interview https://github.com/nurikk/zigbee2mqtt-frontend/pull/2027 2024-06-01 16:30:38 +02:00
github-actions[bot] 137087d8ac chore: promote to dev 2024-06-01 09:29:38 +00:00
Koen Kanters fe048e6e66 chore(dev): release 1.38.0 (#22867) 2024-06-01 11:18:35 +02:00
Koen Kanters aa439d6687 chore: Move to googleapis/release-please-action@v4 2024-06-01 11:16:56 +02:00
Koen Kanters b5fc1689c4 chore: remove Helm chart, moved to Koenkk/zigbee2mqtt-chart 2024-06-01 11:13:21 +02:00
Koen KantersandKoenkk 33d8223e52 fix(ignore): update zigbee-herdsman-converters to 19.47.1 (#22865)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-01 09:05:00 +00:00
Koen KantersandKoenkk 2cc68fd862 fix(ignore): update zigbee-herdsman to 0.49.2 (#22864)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-06-01 08:49:39 +00:00
Koen KantersandKoenkk a02fd3cee6 fix(ignore): update zigbee-herdsman-converters to 19.47.0 (#22855)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-31 18:50:52 +00:00
Koen Kantersandnurikk a3ea91deb9 fix(ignore): update zigbee2mqtt-frontend to 0.7.1 (#22854)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-05-31 18:45:28 +00:00
Koen KantersandKoenkk 1a12c2f15e fix(ignore): update zigbee-herdsman-converters to 19.46.0 (#22842)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-30 19:24:20 +00:00
Koen KantersandKoenkk f9b0fff185 Update zigbee-herdsman-converters to 19.45.0 (#22814)
* fix(ignore): update zigbee-herdsman-converters to 19.45.0

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-30 19:20:02 +00:00
Mike Ryan b6ad6418a1 feat: Add api for device re-interview (#22788)
* feat: add api for device re-interview

- Adds an API that allows for the re-interview of a device. This can be
  useful a device firmware upgrade adds new device endpoints (as is the case
  when upgrading an Inovelli VZM31-SN to 2.18). Without the ability to
  re-interview, one must remove and re-add the device.

* rename from reinterview to interview

* publish devices after interview.

* only allow device ids or names, not endpoints
2024-05-30 20:48:34 +02:00
Koen KantersandKoenkk 9f5b5d1a76 fix(ignore): update zigbee-herdsman-converters to 19.44.0 (#22801)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-27 20:29:08 +00:00
Koen KantersandKoenkk 0b3f4eb0a7 fix(ignore): update zigbee-herdsman to 0.49.1 (#22800)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-27 20:02:04 +00:00
Koen Kanters ae8a59dd51 fix(ignore): Fix Home Assistant device_automation being cleared due to ef68cc328e 2024-05-27 16:59:56 +02:00
Koen Kantersandnurikk b984a2d092 fix(ignore): update zigbee2mqtt-frontend to 0.7.0 (#22761)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-05-24 08:58:34 +00:00
Koen KantersandKoenkk 9b3a39415f fix(ignore): update zigbee-herdsman to 0.49.0 (#22754)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-23 18:51:17 +00:00
Koen Kanters 3b7392ead2 fix(ignore): Revert "Update dependencies" (#22753)
This reverts commit 5ad7e1541f.
2024-05-23 20:39:30 +02:00
Koen KantersandKoenkk 5ad7e1541f fix(ignore): update dependencies (#22752)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-23 20:34:36 +02:00
Koen Kanters b2d32657f3 fix(ignore): Allow deprecated warn log_level https://github.com/Koenkk/zigbee2mqtt/issues/22706 2024-05-22 20:12:15 +02:00
Koen Kanters ef68cc328e fix: Optimize Home Assistant discovery (#22701)
* fix: Optimize Home Assistant discovery

* update

* u

* u

* u

* fix
2024-05-21 20:53:37 +02:00
Koen KantersandKoenkk 30f6c8e60c fix(ignore): update zigbee-herdsman-converters to 19.43.0 (#22730)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-21 18:22:36 +00:00
Laurent ChardinandKoen Kanters d484405cf0 fix: Expose custom clusters to MQTT (#22583)
* (feat) Expose Custom Clusters in MQTT

- Introducing bridge/definitions
- Updating test case for custom clusters
- Updating new path after ZCL revamp with 0.47.0

This change is needed for nurikk/zigbee2mqtt-frontend#2001

* Fixing test case

* Update lib/model/device.ts

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

* Update test/bridge.test.js

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

* Removing the publishDefinition call from some events. updating tests

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-05-21 19:54:18 +02:00
Koen KantersandKoenkk 75608c79c8 fix(ignore): update zigbee-herdsman-converters to 19.42.0 (#22704)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-20 06:45:16 +00:00
Koen Kanters 63de2c5ccc fix: Fix Entity 'homeassistant/sensor' is unknown https://github.com/Koenkk/zigbee2mqtt/issues/22258 2024-05-19 21:32:09 +02:00
Koen KantersandKoenkk 91887c1877 fix(ignore): update zigbee-herdsman to 0.48.1 (#22699)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-19 14:12:19 +00:00
Koen Kantersandnurikk 8ab3c464dc fix(ignore): update zigbee2mqtt-frontend to 0.6.168 (#22698)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-05-19 14:06:41 +00:00
Koen KantersandNerivec ce6043acb0 fix: Improve settings changing (#22674)
* Add namespaced levels for logger. Add NS to mqtt. Deprecate 'warn'.

* Improve setting validation.

* Fix setting through frontend

* Support reload + frontend improvements

* update description

* remove requiresRestart

* Fix tests.

* Fix namespaced logging at lower levels. Add better tests.

* fix: Fix settings changing

* remove `removeNullPropertiesFromObject` from bridge

* Add extra test

* update

---------

Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2024-05-19 16:01:28 +02:00
NerivecandKoen Kanters 2eec6a4d26 fix: Add namespace-specific levels support to logger (#22619)
* Add namespaced levels for logger. Add NS to mqtt. Deprecate 'warn'.

* Improve setting validation.

* Fix setting through frontend

* Support reload + frontend improvements

* update description

* remove requiresRestart

* Fix tests.

* Fix namespaced logging at lower levels. Add better tests.

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-05-19 15:47:40 +02:00
Koen KantersandKoenkk e9b7a847b0 fix(ignore): update zigbee-herdsman-converters to 19.41.0 (#22690)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-18 19:04:07 +00:00
Koen KantersandKoenkk e01662b3a2 fix(ignore): update zigbee-herdsman to 0.48.0 (#22689)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-18 18:54:35 +00:00
Koen KantersandKoenkk 2963397b60 fix(ignore): update zigbee-herdsman-converters to 19.40.0 (#22655)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-15 20:01:38 +00:00
Koen KantersandKoenkk 2be947b29d fix(ignore): update zigbee-herdsman-converters to 19.39.0 (#22641)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-14 20:20:10 +00:00
Koen KantersandKoenkk 4f9658132b fix(ignore): update zigbee-herdsman to 0.47.2 (#22640)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-14 22:13:00 +02:00
Koen Kanters d39baa6f1d chore: merge master to dev 2024-05-13 22:35:47 +02:00
Koen Kanters cda867a387 fix: Add Node 22 support, remove Node 21 https://github.com/Koenkk/zigbee2mqtt/issues/22595 2024-05-13 22:35:26 +02:00
Koen KantersandKoenkk 0ba02d1f40 fix(ignore): update zigbee-herdsman to 0.47.1 (#22620)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-13 20:02:50 +00:00
Koen KantersandKoenkk 09e51e064b Update zigbee-herdsman to 0.47.0 (#22602)
* fix(ignore): update zigbee-herdsman to 0.47.0

* fix imports

* u

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-12 19:34:16 +00:00
Koen Kanters 04e88baf60 fix: Fix custom cluster not available in frontend https://github.com/Koenkk/zigbee2mqtt/issues/22425 2024-05-10 22:15:38 +02:00
Koen Kanters ecf75ba4ce chore: Set mark_as_latest: false for Helm release 2024-05-10 22:08:09 +02:00
Koen Kanters 14d035007d chore: merge master to dev 2024-05-10 21:46:03 +02:00
Koen Kanters c02c61dd56 chore: Trigger Helm chart release 2024-05-10 21:45:41 +02:00
Koen Kanters 6b60c4ea41 chore: merge master to dev 2024-05-09 22:44:46 +02:00
Koen Kanters d043fd56f9 chore: update problem_report.yaml 2024-05-09 22:44:27 +02:00
Koen Kanters 297e904c2f chore: merge master to dev 2024-05-09 22:38:56 +02:00
Koen Kanters 34f0332564 chore: fix helm_release.yaml branch 2024-05-09 22:38:39 +02:00
github-actions[bot] 9e83b7bba2 chore: promote to dev 2024-05-09 20:33:49 +00:00
Koen Kantersandgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> ea39d86e6b chore: release 1.37.1 (#22569)
* chore: release dev

* chore(release): update helm-chart docs

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-09 22:23:15 +02:00
Koen KantersandKoenkk 06fd8a4eb1 fix(ignore): update zigbee-herdsman-converters to 19.37.2 (#22570)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-09 20:14:10 +00:00
Nerivec d26ccaed65 fix: Fix logger crashing on restart (#22565) 2024-05-09 21:42:28 +02:00
Koen KantersandKoenkk 989f667c81 fix(ignore): update zigbee-herdsman to 0.46.6 (#22558)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-09 06:17:53 +00:00
Koen Kanters cce41c20fb chore: set release-please pull-request-title-pattern 2024-05-08 23:43:55 +02:00
Koen Kanters 350c682e8f chore: make tests faster 2024-05-08 23:32:46 +02:00
Koen KantersandKoenkk 6bcb4b66b1 fix(ignore): update zigbee-herdsman to 0.46.5 (#22552)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-08 21:16:13 +00:00
Koen Kanters 58d37b0f92 chore: improve test speed 2024-05-08 23:09:23 +02:00
Koen Kanters aecdfe638b chore: another attempt to fix release_please 2024-05-08 22:55:46 +02:00
Koen KantersandKoenkk cc9a95c74e fix(ignore): update zigbee-herdsman-converters to 19.37.1 (#22549)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-08 20:49:51 +00:00
Koen Kanters 834626d2e9 chore: fix release_please.yml 2024-05-08 22:44:30 +02:00
Koen KantersandKoenkk 6b886f6268 fix(ignore): update zigbee-herdsman-converters to 19.37.0 (#22531)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-07 20:49:28 +00:00
Koen Kanters a0c9a1769f fix: Fix MQTT ECONNREFUSED error https://github.com/zigbee2mqtt/hassio-zigbee2mqtt/issues/605 2024-05-07 21:25:01 +02:00
Koen KantersandKoenkk 3544d5172e fix(ignore): update zigbee-herdsman-converters to 19.36.1 (#22503)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-06 20:00:06 +00:00
Koen KantersandKoenkk e44608a46d fix(ignore): update zigbee-herdsman to 0.46.4 (#22501)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-06 19:55:04 +00:00
Jose Luis Pedrosaandjose 6a69c16170 chore: Release please no longer overwrites .release-please-manifest.json (#22499)
Co-authored-by: jose <jose.pedrosa@reddit.com>
2024-05-06 21:43:55 +02:00
Jose Luis Pedrosaandjose b2e8b3e00c fix: Add Helm chart (#21852)
* Initial draft for helm chart

* Add CI using helm releaser

Add helm chart docummentation

Add configuration.yaml values

* Set maintainer

set right defaults on values.yaml

* Add ci to update values on chart

* Add chart version on CI

* Remove manual update of the chart

* Trigger update of Chart.yaml with release_please

* Add chart to release-please

* Add version to release-please manifest for chart

* remove the second release-please

* Update appVersion in chart.yaml too

* Add make target to generate helm-docs

* Generate docs and manage versions in release-please

* Add persistent static volumes

* Small fixes, ensure no volume works

* Fixes for static provisioned volume

* Delete yq dependency in CI

* Delete Makefile, update release-please to run doc generation directly

---------

Co-authored-by: jose <jose.pedrosa@reddit.com>
2024-05-06 20:30:09 +02:00
Koen KantersandKoenkk 358e2eee45 fix(ignore): update zigbee-herdsman-converters to 19.36.0 (#22472)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-05 20:45:29 +00:00
Koen KantersandKoenkk 316654b5d3 fix(ignore): update zigbee-herdsman to 0.46.3 (#22471)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-05 20:32:55 +00:00
Nerivec eb49f523bf fix: Add settings for finer control over debug log level (#22426)
* Add settings for finer control over debug log level.

* Updates from feedback.
2024-05-05 21:52:26 +02:00
Koen KantersandKoenkk b754b9d463 fix(ignore): update dependencies (#22456)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-05 21:44:20 +02:00
Koen KantersandKoenkk e4c9bac348 fix(ignore): update zigbee-herdsman to 0.46.2 (#22470)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-05 19:05:33 +00:00
Koen KantersandKoenkk beedfaefac fix(ignore): update zigbee-herdsman-converters to 19.35.0 (#22442)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-04 08:34:32 +00:00
Koen KantersandKoenkk 329d832337 fix(ignore): update zigbee-herdsman to 0.46.1 (#22439)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-04 07:00:44 +00:00
Koen Kantersandnurikk 23e97d0001 fix(ignore): update zigbee2mqtt-frontend to 0.6.167 (#22419)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-05-03 08:44:34 +00:00
Koen KantersandKoenkk 3a61f0acfa Update zigbee-herdsman-converters to 19.34.0 (#22411)
* fix(ignore): update zigbee-herdsman-converters to 19.34.0

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-02 20:20:06 +00:00
Koen Kantersandnurikk 5899436d74 fix(ignore): update zigbee2mqtt-frontend to 0.6.166 (#22410)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-05-02 20:00:06 +00:00
Koen Kanters 97d5c5c97a chore: merge master to dev 2024-05-02 21:18:48 +02:00
rbswift ee5cf5a901 chore: Improve update.sh (#22395)
Pulling without specifying how to reconcile divergent branches is discouraged and produces an ugly "hint" that can be a little disconcerting when running update.sh
2024-05-02 21:18:25 +02:00
Koen KantersandKoenkk 907591c467 fix(ignore): update zigbee-herdsman-converters to 19.33.0 (#22381)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-01 20:28:30 +00:00
Koen KantersandKoenkk 55b5f91a96 fix(ignore): update zigbee-herdsman to 0.46.0 (#22380)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-01 20:20:29 +00:00
github-actions[bot] ce915a6797 chore: promote to dev 2024-05-01 19:45:20 +00:00
Koen Kanters 46f34c8cc7 chore(dev): release 1.37.0 (#22022) 2024-05-01 21:23:10 +02:00
Koen KantersandKoenkk e03013c581 fix(ignore): update zigbee-herdsman-converters to 19.32.0 (#22377)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-05-01 19:17:13 +00:00
Koen Kantersandnurikk 8a288a9119 fix(ignore): update zigbee2mqtt-frontend to 0.6.165 (#22366)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-04-30 19:47:10 +00:00
Koen KantersandKoenkk 8e27ec24f6 fix(ignore): update zigbee-herdsman to 0.45.0 (#22365)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-30 19:38:26 +00:00
Koen KantersandKoenkk 4b291bf012 Update zigbee-herdsman-converters to 19.31.1 (#22341)
* fix(ignore): update zigbee-herdsman-converters to 19.31.1

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-29 20:01:56 +00:00
Koen Kantersandnurikk 10e6ad0e67 fix(ignore): update zigbee2mqtt-frontend to 0.6.164 (#22340)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-04-28 18:23:36 +00:00
Koen KantersandKoenkk 1b089ef6ae fix(ignore): update zigbee-herdsman to 0.44.0 (#22339)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-28 18:11:30 +00:00
Koen KantersandKoenkk d7e2d98a5a fix(ignore): update dependencies (#22331)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-28 08:56:01 +02:00
Koen Kanters eb69319674 chore: merge master to dev 2024-04-28 08:53:39 +02:00
Koen Kanters 8c8a6a3ecf chore: don't autoupdate eslint 2024-04-28 08:53:17 +02:00
Koen Kanters baa1d703d4 chore: don't autoupdate eslint 2024-04-28 08:50:56 +02:00
Koen KantersandKoenkk 37551265a2 fix(ignore): update zigbee-herdsman-converters to 19.29.0 (#22325)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-27 07:19:33 +00:00
Koen KantersandKoenkk 245afb1126 fix(ignore): update zigbee-herdsman-converters to 19.28.0 (#22318)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-25 20:31:11 +00:00
Koen KantersandKoenkk 389779d4e3 fix(ignore): update zigbee-herdsman to 0.43.1 (#22317)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-25 19:50:10 +00:00
Koen KantersandKoenkk df23240683 fix(ignore): update zigbee-herdsman-converters to 19.27.0 (#22301)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-23 20:46:01 +00:00
Koen KantersandKoenkk 75f9743528 fix(ignore): update zigbee-herdsman to 0.43.0 (#22300)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-23 20:25:41 +00:00
Koen Kantersandnurikk e5d632bc91 fix(ignore): update zigbee2mqtt-frontend to 0.6.163 (#22288)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-04-22 11:31:56 +00:00
Koen KantersandKoenkk 4f97a7ad2c fix(ignore): update zigbee-herdsman to 0.42.5 (#22285)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-21 20:47:08 +00:00
Koen KantersandKoenkk 4bf2ffa7dd fix(ignore): update zigbee-herdsman-converters to 19.26.0 (#22284)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-21 19:09:52 +00:00
Koen KantersandKoenkk a8f6bec673 fix(ignore): update zigbee-herdsman to 0.42.4 (#22283)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-21 19:01:53 +00:00
Koen KantersandKoenkk 80504b943d fix(ignore): update zigbee-herdsman-converters to 19.25.0 (#22266)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-20 10:04:27 +00:00
Koen KantersandKoenkk d9628df17b fix(ignore): update zigbee-herdsman-converters to 19.24.0 (#22254)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-18 20:29:12 +00:00
Koen KantersandKoenkk f7abbd5f5a fix(ignore): update zigbee-herdsman to 0.42.3 (#22253)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-18 20:15:59 +00:00
Koen KantersandKoenkk 1544317fce fix(ignore): update zigbee-herdsman-converters to 19.23.0 (#22232)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-16 19:49:38 +00:00
Koen KantersandKoenkk 172aa7461a fix(ignore): update zigbee-herdsman-converters to 19.22.0 (#22221)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-15 20:13:35 +00:00
Koen KantersandKoenkk 76f5425452 fix(ignore): update zigbee-herdsman to 0.42.2 (#22220)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-15 19:55:40 +00:00
Koen KantersandKoenkk 87aa18d14c fix(ignore): update zigbee-herdsman-converters to 19.21.0 (#22199)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-13 08:07:00 +00:00
Koen KantersandKoenkk e10ab5ccb2 fix(ignore): update zigbee-herdsman-converters to 19.20.0 (#22187)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-11 19:46:11 +00:00
f774753213 fix(ignore): update zigbee-herdsman-converters to 19.19.1 (#22171)
* fix(ignore): update zigbee-herdsman-converters to 19.19.1

* fix(configure): pass definition to function call (#22154)

* Pass definition to configure

* fix test

---------

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

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
Co-authored-by: Sergei Salimov <32013287+mrskycriper@users.noreply.github.com>
2024-04-10 22:02:12 +02:00
Koen KantersandKoenkk a210c238f1 fix(ignore): update zigbee-herdsman to 0.42.1 (#22143)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-10 19:56:41 +00:00
Koen KantersandKoenkk cdbbe0d49f fix(ignore): update zigbee-herdsman-converters to 19.19.0 (#22153)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-09 19:21:28 +00:00
Koen Kanters c98e5a74de fix: Remove deprecated Home Assistant color_mode (#22142) 2024-04-08 21:19:17 +02:00
Koen KantersandKoenkk 8b8d660b06 fix(ignore): update zigbee-herdsman-converters to 19.18.0 (#22141)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-08 19:14:41 +00:00
Nerivec 3c1a4afcb7 fix: Convert logger into a class (#22129) 2024-04-07 20:52:55 +02:00
Koen KantersandKoenkk cabc9f5e2f fix(ignore): update zigbee-herdsman-converters to 19.17.0 (#22122)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-07 08:46:33 +00:00
Koen KantersandKoenkk 28b8aff1ae fix(ignore): update zigbee-herdsman to 0.42.0 (#22121)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-07 10:42:45 +02:00
Koen KantersandKoenkk e7487189cf fix(ignore): update dependencies (#22007)
* fix(ignore): update dependencies

* fix

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-06 13:02:02 +02:00
Koen Kanters 92f53235f3 fix: Fix crash when stopping in case MQTT client did not connect yet (#22110) 2024-04-06 10:49:22 +00:00
Koen Kanters 002050f35b fix: Reduce MQTT publishing by excluding debug logging from bridge/logging (#22066)
* fix: Reduce MQTT publishing by excluding debug logging from `bridge/logging`

* fix tests

* fix
2024-04-06 12:44:43 +02:00
Koen KantersandKoenkk 394a3fbd8a fix(ignore): update zigbee-herdsman-converters to 19.16.0 (#22107)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-06 09:46:26 +00:00
Koen Kanters af3a0332f9 chore: Update stale.yml 2024-04-06 08:33:30 +02:00
Koen Kantersandnurikk 0c80129466 fix(ignore): update zigbee2mqtt-frontend to 0.6.162 (#22094)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-04-05 11:51:31 +00:00
Koen KantersandKoenkk ae35aaed5e fix(ignore): update zigbee-herdsman-converters to 19.15.0 (#22089)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-04 20:06:53 +00:00
Koen Kanters 9d251dbad5 fix: Disable automatic reconfigure when configureKey changes (#22088) 2024-04-04 21:47:44 +02:00
Koen KantersandKoenkk a020138f6b fix(ignore): update zigbee-herdsman to 0.41.2 (#22087)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-04 19:36:33 +00:00
Koen KantersandKoenkk 701cdda38c Update zigbee-herdsman-converters to 19.14.0 (#22065)
* fix(ignore): update zigbee-herdsman-converters to 19.14.0

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-04 19:33:39 +00:00
Nerivec 919c4eee73 fix: Cleanup old logger leftovers (#22069) 2024-04-04 20:24:39 +02:00
Koen KantersandKoenkk 3e913495b1 fix(ignore): update zigbee-herdsman to 0.41.1 (#22064)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-03 18:26:22 +00:00
Koen Kanters bbb6f9e042 fix: Change MQTT publish log to debug (#22063) 2024-04-03 18:13:46 +00:00
Koen Kanters cdd6fb0864 fix(ignore): fix 73566df 2024-04-02 22:39:33 +02:00
Koen Kanters 780c065fa1 fix(ignore): fix 73566dff98 2024-04-02 22:38:02 +02:00
Koen KantersandKoenkk db7d9e1120 fix(ignore): update zigbee-herdsman-converters to 19.13.0 (#22046)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-02 22:37:10 +02:00
NerivecandKoen Kanters 73566dff98 feat: Unified logging across z2m/zh/zhc (#21984)
* Unified logging across z2m/zh/zhc

* Updates

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-04-02 22:33:51 +02:00
Koen KantersandKoenkk bd0a41107a fix(ignore): update zigbee-herdsman to 0.41.0 (#22045)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-02 22:33:26 +02:00
Koen KantersandKoenkk 62844ff637 Update zigbee-herdsman-converters to 19.12.1 (#22044)
* fix(ignore): update zigbee-herdsman-converters to 19.12.1

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-02 20:17:43 +00:00
github-actions[bot] 5077b03a02 chore: promote to dev 2024-04-01 18:06:23 +00:00
Koen Kanters ffc2ff1d0e chore(dev): release 1.36.1 (#21638) 2024-04-01 19:52:17 +02:00
Koen KantersandKoenkk 3aadab0925 fix(ignore): update zigbee-herdsman-converters to 19.11.2 (#22021)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-04-01 17:49:24 +00:00
Koen KantersandKoenkk 85c00b8b43 fix(ignore): update zigbee-herdsman-converters to 19.11.1 (#22014)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-31 19:12:44 +00:00
Koen KantersandKoenkk e51792c65a fix(ignore): update zigbee-herdsman-converters to 19.11.0 (#22013)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-31 18:44:03 +00:00
Koen KantersandKoenkk 1e74b2f53f fix(ignore): update zigbee-herdsman to 0.40.3 (#22010)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-31 08:01:06 +00:00
Koen KantersandKoenkk 886b5345d7 fix(ignore): update zigbee-herdsman to 0.40.2 (#22000)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-30 10:14:12 +00:00
Koen KantersandKoenkk 7d8b5374ad fix(ignore): update zigbee-herdsman-converters to 19.10.0 (#21996)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-29 19:48:10 +00:00
Koen KantersandKoenkk 0c7730f4c5 fix(ignore): update zigbee-herdsman to 0.40.1 (#21987)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-28 20:08:42 +00:00
Koen KantersandKoenkk a7aab714ba fix(ignore): update zigbee-herdsman-converters to 19.9.0 (#21970)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-27 19:57:56 +00:00
Koen KantersandKoenkk 032b9a096e fix(ignore): update zigbee-herdsman-converters to 19.8.0 (#21957)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-26 19:47:40 +00:00
Koen KantersandKoenkk 1f3d61dc7f Update zigbee-herdsman-converters to 19.7.0 (#21947)
* fix(ignore): update zigbee-herdsman-converters to 19.7.0

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-25 21:24:00 +00:00
Koen KantersandKoenkk 32b173c653 fix(ignore): update zigbee-herdsman-converters to 19.6.0 (#21927)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-24 13:48:20 +00:00
Koen KantersandKoenkk 8c4cd8c9aa fix(ignore): update dependencies (#21923)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-24 08:06:46 +01:00
Koen KantersandKoenkk 73423ebd60 fix(ignore): update zigbee-herdsman-converters to 19.5.0 (#21902)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-21 19:35:31 +00:00
Koen KantersandKoenkk 334e260b85 fix(ignore): update zigbee-herdsman to 0.40.0 (#21901)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-21 19:32:32 +00:00
Koen Kanters 731957dab9 chore: merge master to dev 2024-03-21 20:10:51 +01:00
wexmanandJens Weiermann 9c505fd75f chore: optimized svg logo (#21874)
* optimized svg image

* preserved original border

---------

Co-authored-by: Jens Weiermann <j.weiermann@wilms.com>
2024-03-21 20:10:33 +01:00
Koen KantersandKoenkk 25cec20f0d fix(ignore): update zigbee-herdsman-converters to 19.4.2 (#21892)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-20 20:40:45 +00:00
Koen KantersandKoenkk f05d501380 fix(ignore): update zigbee-herdsman to 0.39.1 (#21891)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-20 20:27:30 +00:00
Koen KantersandKoenkk b02c34c6e5 fix(ignore): update zigbee-herdsman-converters to 19.4.1 (#21872)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-19 20:57:38 +00:00
Koen KantersandKoenkk 801134b630 fix(ignore): update zigbee-herdsman to 0.39.0 (#21871)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-19 20:50:21 +00:00
Koen KantersandKoenkk 73d43007d4 fix(ignore): update zigbee-herdsman-converters to 19.4.0 (#21870)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-19 20:30:52 +00:00
Koen KantersandKoenkk 1a9e05e6ab fix(ignore): update zigbee-herdsman-converters to 19.3.0 (#21851)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-17 20:21:30 +00:00
Koen KantersandKoenkk 1006725b09 fix(ignore): update dependencies (#21832)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-17 10:18:56 +01:00
Koen KantersandKoenkk a4f9c49ab5 fix(ignore): update zigbee-herdsman-converters to 19.2.1 (#21801)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-14 19:58:50 +00:00
Koen KantersandKoenkk 658622880f fix(ignore): update zigbee-herdsman to 0.38.1 (#21802)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-14 20:55:17 +01:00
Koen KantersandKoenkk 720fc491ce fix(ignore): update zigbee-herdsman-converters to 19.1.1 (#21789)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-13 19:45:48 +00:00
Koen KantersandKoenkk 56feb77f2d fix(ignore): update zigbee-herdsman-converters to 19.1.0 (#21778)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-12 20:42:14 +00:00
Koen KantersandKoenkk 233d263e13 fix(ignore): update zigbee-herdsman to 0.38.0 (#21777)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-12 20:32:24 +00:00
Koen KantersandKoenkk dbd616fbe5 fix(ignore): update zigbee-herdsman-converters to 19.0.0 (#21768)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-11 20:51:22 +00:00
Koen KantersandKoenkk 86c2e90e06 Update zigbee-herdsman-converters to 18.49.0 (#21767)
* fix(ignore): update zigbee-herdsman-converters to 18.49.0

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-11 20:40:55 +00:00
Koen KantersandKoenkk 43610bbff4 fix(ignore): Update dependencies (#21740)
* fix(ignore): update dependencies

* fix

* fix

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-10 19:08:48 +00:00
Koen Kanters 29543bad4b fix(ignore): fix tests 2024-03-10 19:54:35 +01:00
Koen KantersandKoenkk 2a047a469c fix(ignore): update zigbee-herdsman-converters to 18.48.0 (#21753)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-10 19:45:41 +01:00
Koen Kanters 83502f2d30 fix(ignore): Update manufacturer codes (#21729) 2024-03-10 19:45:22 +01:00
Koen KantersandKoenkk b60f679814 fix(ignore): update zigbee-herdsman to 0.37.0 (#21743)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-10 19:41:50 +01:00
Sergei Salimov 14e044093c fix: Home Assistant: remove button name if device_class is present (#21726)
* Remove preset name if button has device class

* fix device_class for consumer_connected

* combine device_class checks
2024-03-08 22:39:15 +01:00
Koen KantersandKoenkk 3188038a7b fix(ignore): update zigbee-herdsman-converters to 18.47.0 (#21728)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-08 21:36:44 +00:00
Koen KantersandKoenkk ebf996acd8 fix(ignore): update zigbee-herdsman to 0.36.2 (#21727)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-08 21:26:15 +00:00
Koen KantersandKoenkk 0e0eccbd13 fix(ignore): update zigbee-herdsman-converters to 18.46.0 (#21714)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-07 21:03:26 +00:00
Koen KantersandKoenkk e834e93deb fix(ignore): update zigbee-herdsman to 0.36.1 (#21713)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-07 20:05:21 +00:00
Koen KantersandKoenkk d37c164993 fix(ignore): update zigbee-herdsman-converters to 18.45.0 (#21699)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-06 19:36:32 +00:00
Koen KantersandKoenkk 6daa7aa61d fix(ignore): update zigbee-herdsman-converters to 18.44.0 (#21690)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-05 21:36:10 +00:00
koenkk 1110d3f8ef fix: Fix incorrect update state when Z2M is restarted during OTA update https://github.com/Koenkk/zigbee2mqtt/issues/12794 2024-03-05 22:33:33 +01:00
Koen KantersandKoenkk 1d73d9d476 fix(ignore): update zigbee-herdsman to 0.36.0 (#21689)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-05 20:51:14 +00:00
Simon RettbergandKoen Kanters 2da80954d6 fix: Fix crash due to race condition during interview (#21680)
Fixes #21118
Mentioned in #21036 and #20688

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-03-05 21:28:35 +01:00
koenkk e2fa66a38c chore: fix ci 2024-03-05 21:23:07 +01:00
koenkk 94382ee418 chore: fix ci 2024-03-05 21:19:56 +01:00
Koen KantersandKoenkk 94c92ef021 Update zigbee-herdsman-converters to 18.43.0 (#21678)
* fix(ignore): update zigbee-herdsman-converters to 18.43.0

* Update configure.test.js

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-04 21:44:31 +00:00
Koen KantersandKoenkk 8c86849bb9 fix(ignore): update dependencies (#21657)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-04 22:32:11 +01:00
Koen KantersandKoenkk 30909d57fb fix(ignore): update zigbee-herdsman to 0.35.3 (#21677)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-04 21:23:13 +00:00
Koen Kantersandnurikk aaed8ab95e fix(ignore): update zigbee2mqtt-frontend to 0.6.161 (#21660)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-03-03 09:39:49 +00:00
Koen Kanters f24b20b133 chore: merge master to dev 2024-03-02 22:19:13 +01:00
koenkk 2a53b8e59b chore: fix release ci 2024-03-02 22:18:48 +01:00
Koen KantersandKoenkk ccaa1281a3 fix(ignore): update zigbee-herdsman to 0.35.2 (#21654)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-02 21:16:37 +00:00
Sergei Salimov 01843fe6c4 fix: Add more Home Assistant entity icons (#21652) 2024-03-02 21:46:46 +01:00
Koen Kanters 66fb672492 fix: Fix set endpoint name conversion (#21653)
* fix: Fix `set` endpoint name conversion https://github.com/Koenkk/zigbee2mqtt/issues/21646

* fix
2024-03-02 21:45:19 +01:00
koenkk 38ed953599 chore: promote to dev 2024-03-01 21:17:51 +01:00
Koen Kanters 86ed71cb1f chore(dev): release 1.36.0 (#21266) 2024-03-01 20:58:11 +01:00
Koen KantersandKoenkk ccc31670ec fix(ignore): update zigbee-herdsman-converters to 18.42.0 (#21637)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-01 19:50:46 +00:00
Koen KantersandKoenkk 0d8f5780dd fix(ignore): update zigbee-herdsman to 0.35.1 (#21636)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-03-01 19:45:01 +00:00
Koen Kantersandnurikk a8a262640f fix(ignore): update zigbee2mqtt-frontend to 0.6.159 (#21623)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-02-29 20:45:12 +00:00
Koen KantersandKoenkk 72333f0888 fix(ignore): update zigbee-herdsman-converters to 18.41.0 (#21622)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-29 20:20:07 +00:00
Koen KantersandKoenkk a84083be40 fix(ignore): update zigbee-herdsman to 0.35.0 (#21621)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-29 16:57:19 +00:00
Sergei Salimov 9b8583e778 fix: Add noise and noise_detected to HA lookup (#21610) 2024-02-29 17:42:48 +01:00
Koen KantersandKoenkk aeaa040920 fix(ignore): update zigbee-herdsman-converters to 18.40.0 (#21603)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-27 20:04:02 +00:00
Koen KantersandKoenkk 222b67b1b0 fix(ignore): update zigbee-herdsman to 0.34.9 (#21602)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-27 19:04:44 +00:00
Koen KantersandKoenkk 1ee1710a96 Update zigbee-herdsman-converters to 18.39.1 (#21590)
* fix(ignore): update zigbee-herdsman-converters to 18.39.1

* fix

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-26 21:32:08 +00:00
Koen Kanters a1e2ee7837 chore: merge master to dev 2024-02-26 21:45:09 +01:00
koenkk f04ade6d76 chore: Update problem_report template 2024-02-26 21:44:00 +01:00
Koen KantersandKoenkk fee0811893 fix(ignore): update zigbee-herdsman to 0.34.8 (#21588)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-26 19:21:45 +00:00
Koen Kanters 956003aa0a fix: Fix endpoint matching for TuYa dummy endpoints (#21576) 2024-02-26 20:02:05 +01:00
Koen KantersandKoenkk 8fb28f9e2f Update zigbee-herdsman-converters to 18.38.0 (#21568)
* fix(ignore): update zigbee-herdsman-converters to 18.38.0

* fix tests

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-25 21:09:35 +00:00
Koen KantersandKoenkk a06a0ea059 fix(ignore): update zigbee-herdsman to 0.34.7 (#21575)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-25 21:04:49 +00:00
Koen KantersandKoenkk 4f71c1e8bf fix(ignore): update zigbee-herdsman to 0.34.6 (#21567)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-25 13:57:37 +00:00
Koen KantersandKoenkk c56470c83f fix(ignore): update dependencies (#21561)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-25 14:12:46 +01:00
Koen KantersandKoenkk cc27032752 fix(ignore): update zigbee-herdsman to 0.34.5 (#21563)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-25 09:03:54 +00:00
Koen KantersandKoenkk 7c97902d4a fix(ignore): update zigbee-herdsman-converters to 18.37.1 (#21549)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-24 08:31:39 +00:00
Koen KantersandKoenkk 23959c5b0e fix(ignore): update zigbee-herdsman to 0.34.4 (#21545)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-23 20:40:51 +00:00
Koen KantersandKoenkk e726d5907e fix(ignore): update zigbee-herdsman to 0.34.3 (#21530)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-22 19:37:33 +00:00
Koen KantersandKoenkk 1337f54e4a fix(ignore): update zigbee-herdsman-converters to 18.37.0 (#21533)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-22 19:32:59 +00:00
Oleksandr Masliuchenko 471902e531 fix: Improve nested group and device name match (#21521) 2024-02-22 19:54:09 +01:00
Koen KantersandKoenkk 22d33360f6 fix(ignore): update zigbee-herdsman-converters to 18.36.0 (#21504)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-20 20:04:54 +00:00
koenkk db1e3b73d1 fix: Only configure routers on startup https://github.com/Koenkk/zigbee2mqtt/issues/21446 2024-02-20 20:24:03 +01:00
Artem Draft fe73db4ff6 fix: trigger_count & power_outage_count adjustments in HA (#21492) 2024-02-20 19:47:45 +01:00
Koen Kanters 9dd6eeee7b chore: merge master to dev 2024-02-20 19:47:25 +01:00
Oleksandr Masliuchenko 3c609658fd fix: Fix parsing entity name that contains slash in the name and endpoint specification (#21493) 2024-02-20 19:47:22 +01:00
Anthony Bourguignon 8b694432aa fix: Check if Zigbee2MQTT is running in update.sh (#21494) 2024-02-20 19:46:44 +01:00
Koen KantersandKoenkk b32bd309ca fix(ignore): update zigbee-herdsman-converters to 18.35.0 (#21487)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-19 21:08:45 +00:00
Koen KantersandKoenkk 78b5e6fd4b fix(ignore): update zigbee-herdsman to 0.34.2 (#21478)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-19 20:46:48 +00:00
Koen Kanters c5c0a8b193 feat: Add experimental Ember adapter @Nerivec https://github.com/Koenkk/zigbee-herdsman/pull/918 (#21459) 2024-02-18 22:11:09 +01:00
Koen KantersandKoenkk b51eef2319 fix(ignore): update zigbee-herdsman to 0.34.1 (#21461)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-18 21:01:03 +00:00
Koen KantersandKoenkk ebed9d31d7 fix(ignore): update zigbee-herdsman-converters to 18.34.0 (#21456)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-18 16:39:07 +00:00
Koen KantersandKoenkk 16b251d3c9 fix(ignore): update dependencies (#21444)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-18 16:23:36 +01:00
Koen KantersandKoenkk 5766bf479b fix(ignore): update zigbee-herdsman-converters to 18.33.2 (#21416)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-15 19:01:26 +00:00
Koen KantersandKoenkk 8a9477f2bb fix(ignore): update zigbee-herdsman-converters to 18.33.1 (#21400)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-14 18:49:57 +00:00
Koen KantersandKoenkk 2aaf878889 fix(ignore): update zigbee-herdsman-converters to 18.33.0 (#21387)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-13 19:28:16 +00:00
Koen KantersandKoenkk dfa499659e fix(ignore): update zigbee-herdsman to 0.33.9 (#21386)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-13 19:05:11 +00:00
Sergei SalimovandKoen Kanters 24b5f9b43a feat: Add support for button entity in Home Assistant (#21372)
* add button entity support

* add enabled_by_default to select

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-02-13 19:43:21 +01:00
Koen KantersandKoenkk 0ab7390a64 fix(ignore): update zigbee-herdsman-converters to 18.32.0 (#21373)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-12 20:31:38 +00:00
Koen KantersandKoenkk c7b820c376 fix(ignore): update zigbee-herdsman-converters to 18.31.0 (#21359)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-11 20:46:36 +00:00
Koen KantersandKoenkk ce41f2f8ba fix(ignore): update dependencies (#21347)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-11 21:07:38 +01:00
Koen Kantersandnurikk 8bc3c46c34 fix(ignore): update zigbee2mqtt-frontend to 0.6.158 (#21356)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-02-11 18:17:21 +00:00
Oleksandr Masliuchenko b4473d6e24 fix: Remove predefined endpoint list, remove limits on device friendly name (#21325)
* Get rid of predefined list of endpoints, correct friend name validations

* Fix minor bug while parsing a topic for an unknown device
2024-02-10 16:50:44 +01:00
Koen KantersandKoenkk 910f3a6860 fix(ignore): update zigbee-herdsman-converters to 18.30.0 (#21315)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-08 19:58:46 +00:00
Oleksandr MasliuchenkoandKoen Kanters 81335e5b4b fix: Remove dependency on predefined list of endpoints (parseEntityID() function) (#21264)
* Add function resolveEntityByID() function as an improved alternative for parseEntityID()

* Port deviceGroupMembership.ts from parseEntityID() to resolveEntityByID()

* Migrade groups.ts from parseEntityID() to resolveEntityByID()

* Improve test coverage on groups functionality

* Migrate from parseEntityID() to resolveEntityByID() for bridge functionality

* Migrate from parseEntityID() to resolveEntityByID() for bind functionality

* Finally get rid of parseEntityID() function

* Fix linter issues

* Move resolveEntityAndEndpoint() function to zigbee.ts

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-02-08 20:54:05 +01:00
Koen KantersandKoenkk 55653c974e fix(ignore): update zigbee-herdsman-converters to 18.29.1 (#21290)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-07 20:32:47 +00:00
Oleksandr MasliuchenkoandKoen Kanters a5a87a79a8 fix: Match endpoint name in the MQTT topic based on endpoints listed in the definition (#21214)
* Add endpoint matching based on the endpoint names list in the device definition

* Cleanup debug prints

* Correct creating endpoint names list, filter out null elements

* Correct test due to slightly changed behavior

* Fix endpoint name lookup when converter does not provide full name-to-id mapping

* Simplify topic name matching

* Remove obsolete branches, improve code coverage

* Make linter happy

* Make linter happy

* Remove dependency on endpoint names while removing device from all groups

* Get rid of predefined list of endpoints when processing group state update

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-02-07 20:12:48 +01:00
Koen Kantersandnurikk a479bf9039 fix(ignore): update zigbee2mqtt-frontend to 0.6.157 (#21269)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-02-06 21:32:15 +00:00
koenkk b7e19cc86a chore: promote to dev 2024-02-06 21:06:28 +01:00
Koen Kanters fe0742a628 chore(dev): release 1.35.3 (#21139) 2024-02-06 20:48:14 +01:00
Koen KantersandKoenkk 29bae3a734 fix(ignore): update zigbee-herdsman-converters to 18.28.3 (#21265)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-06 19:44:44 +00:00
Koen Kanters 6aa6f04059 chore: merge master to dev 2024-02-06 17:46:32 +01:00
Issy Szemeti fc6d74e2b7 chore: Fix spelling/grammar (#21260) 2024-02-06 17:46:13 +01:00
Koen KantersandKoenkk 92e0d7895b Update zigbee-herdsman to 0.33.8 (#21246)
* fix(ignore): update zigbee-herdsman to 0.33.8

* fix

---------

Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-05 19:57:03 +00:00
Koen KantersandKoenkk 250d5bfc24 fix(ignore): update zigbee-herdsman-converters to 18.28.2 (#21245)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-05 19:38:54 +00:00
Koen Kantersandnurikk dfda63138f fix(ignore): update zigbee2mqtt-frontend to 0.6.156 (#21229)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-02-04 22:35:57 +00:00
Koen Kantersandnurikk 8ab6c16f6e fix(ignore): update zigbee2mqtt-frontend to 0.6.155 (#21228)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-02-04 22:20:13 +00:00
Koen KantersandKoenkk a6d51a60a8 fix(ignore): update zigbee-herdsman-converters to 18.28.1 (#21222)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-04 20:05:53 +00:00
Koen KantersandKoenkk 3d37892296 fix(ignore): update zigbee-herdsman-converters to 18.28.0 (#21207)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-04 08:28:03 +00:00
Koen KantersandKoenkk 1df54f071f fix(ignore): update dependencies (#21204)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-04 09:16:48 +01:00
koenkk b3253bacc4 chore: remove @types/rimraf 2024-02-04 09:13:03 +01:00
Koen KantersandKoenkk 4b1721721b fix(ignore): update zigbee-herdsman-converters to 18.27.1 (#21189)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-03 13:57:30 +00:00
Koen Kantersandnurikk 2db07236db fix(ignore): update zigbee2mqtt-frontend to 0.6.154 (#21188)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-02-03 12:14:17 +00:00
Koen Kantersandnurikk 7295955397 fix(ignore): update zigbee2mqtt-frontend to 0.6.153 (#21187)
Co-authored-by: nurikk <1525421+nurikk@users.noreply.github.com>
2024-02-03 12:10:41 +00:00
koenkk f83709d96c fix(ignore): set zhc logger 2024-02-03 12:27:04 +01:00
Koen KantersandKoenkk a678a1c652 fix(ignore): update zigbee-herdsman-converters to 18.27.0 (#21184)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-03 11:24:11 +00:00
Koen KantersandKoenkk 3d1336dc6c fix(ignore): update zigbee-herdsman to 0.33.7 (#21178)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-02 20:58:09 +00:00
koenkk da1d0d81c2 chore: fix release-please 2024-02-01 22:03:28 +01:00
koenkk 335e18fe98 chore: fix release-please 2024-02-01 22:02:01 +01:00
Koen Kanters 6e2539af09 chore: merge master to dev 2024-02-01 21:58:18 +01:00
koenkk fdc9f7d4d4 chore: fix dev -> master on release 2024-02-01 21:57:56 +01:00
Koen KantersandKoenkk 08c03e48cc fix(ignore): update zigbee-herdsman-converters to 18.26.0 (#21145)
Co-authored-by: Koenkk <2892853+Koenkk@users.noreply.github.com>
2024-02-01 20:35:49 +00:00
koenkk 6621f6fd91 chore: promote to dev 2024-02-01 17:45:05 +01:00
Koen Kanters 06b3395cff chore(dev): release 1.35.2 (#20636) 2024-02-01 17:20:18 +01:00
Koen KantersandKoenkk 0b59a51c3c fix(ignore): update zigbee-herdsman-converters to 18.25.1 (#21129)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-31 19:36:04 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1cee011ca0 chore: Bump peter-evans/create-pull-request from 5 to 6 (#21122)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 5 to 6.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v5...v6)

---
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>
2024-01-31 19:59:19 +01:00
Koen Kanters 630b4ec560 chore: merge master to dev 2024-01-30 21:03:09 +01:00
koenkk 4b6f50daf3 chore: fix update dep auto merge 2024-01-30 21:02:50 +01:00
Koen KantersandKoenkk 0361b13b66 Update zigbee-herdsman-converters to 18.25.0 (#21114)
* fix(ignore): update zigbee-herdsman-converters to 18.25.0

* fix

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-30 20:58:29 +01:00
Koen KantersandKoenkk 49dea233b6 fix(ignore): update zigbee-herdsman to 0.33.5 (#21113)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-30 20:24:23 +01:00
koenkk c16abdd832 fix: Use Alpine 3.18.4 as base https://github.com/Koenkk/zigbee2mqtt/issues/20868 2024-01-30 20:13:08 +01:00
koenkk c37f3e834e chore: remove onlythis 2024-01-30 20:12:52 +01:00
Koen KantersandKoenkk f3e79df4b4 fix(ignore): update zigbee-herdsman-converters to 18.24.0 (#21106)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-29 20:13:02 +00:00
Koen KantersandKoenkk 03ba745e46 fix(ignore): update zigbee-herdsman to 0.33.4 (#21104)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-29 19:02:40 +00:00
Koen KantersandKoenkk 7e7d3c6328 fix(ignore): update zigbee-herdsman-converters to 18.23.1 (#21093)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-28 20:03:43 +00:00
Koen KantersandKoenkk 59001babca fix(ignore): update zigbee-herdsman to 0.33.3 (#21094)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-28 19:52:46 +00:00
Koen KantersandKoenkk 0d716288bf Update zigbee-herdsman-converters to 18.22.0 (#21040)
* fix(ignore): update zigbee-herdsman-converters to 18.22.0

* Update homeassistant.test.js

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-27 08:55:15 +00:00
Koen KantersandKoenkk a879382cbd fix(ignore): update zigbee-herdsman-converters to 18.21.0 (#21012)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-25 19:23:45 +00:00
Koen Kantersandnurikk a858798054 fix(ignore): update zigbee2mqtt-frontend to 0.6.152 (#21003)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2024-01-25 11:15:44 +00:00
Koen KantersandKoenkk 416e561b32 fix(ignore): update zigbee-herdsman-converters to 18.20.0 (#20996)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-24 21:07:36 +00:00
Koen KantersandKoenkk a240487bb4 fix(ignore): update zigbee-herdsman to 0.33.2 (#20994)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-24 22:04:17 +01:00
Koen KantersandKoenkk 518972882c fix(ignore): update zigbee-herdsman-converters to 18.19.0 (#20966)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-23 19:23:56 +00:00
Koen KantersandKoenkk ed639ec9d7 fix(ignore): update zigbee-herdsman to 0.33.1 (#20965)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-23 18:56:19 +00:00
Der Mundschenk & Compagnie 6372d84259 fix: Republish correctly on scene changes to Home Assistant (#20952) 2024-01-23 19:25:18 +01:00
Koen KantersandKoenkk 45579dcf10 Update zigbee-herdsman-converters to 18.18.0 (#20933)
* fix(ignore): update zigbee-herdsman-converters to 18.18.0

* Update bridge.test.js

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-22 20:02:57 +00:00
chrthiandKoen Kanters 97eac16aaf fix: Notify systemd for start, stop, watchdog (#20482)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2024-01-21 21:49:17 +01:00
Koen KantersandKoenkk 032557b1db fix(ignore): update zigbee-herdsman to 0.33.0 (#20932)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-21 20:35:49 +00:00
Koen KantersandKoenkk de7bc7cf60 fix(ignore): update zigbee-herdsman-converters to 18.17.0 (#20907)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-20 08:56:11 +00:00
Sergei Salimov 92588fefe3 fix: Home Assistant: additional icons for Aqara TRV entities and various others (#20903)
* Add schedule and schedule_settings icons

* Add various missing entities
2024-01-20 08:36:18 +01:00
Koen KantersandKoenkk 8de20e2cf1 fix(ignore): update zigbee-herdsman-converters to 18.16.0 (#20901)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-19 19:49:09 +00:00
Koen KantersandKoenkk a1360d8e90 fix(ignore): update zigbee-herdsman to 0.32.7 (#20900)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-19 19:26:05 +00:00
Koen KantersandKoenkk 9f3b10235c fix(ignore): update zigbee-herdsman-converters to 18.15.1 (#20876)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-18 21:15:15 +00:00
dependabot[bot] 1d6d386a47 chore: Bump actions/dependency-review-action from 3 to 4 (#20869) 2024-01-18 18:34:20 +01:00
dependabot[bot] 76ea57b732 chore: Bump actions/cache from 3 to 4 (#20870) 2024-01-18 18:34:04 +01:00
slugzero e76f568f0f fix: Remove deprecated sendWhen (#20861) 2024-01-18 18:28:52 +01:00
Koen KantersandKoenkk 080f3b6b50 fix(ignore): update zigbee-herdsman-converters to 18.15.0 (#20860)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-17 20:04:51 +00:00
koenkk a741b2f598 fix: Revert "Enable incremental TSC builds (#19293)" https://github.com/Koenkk/zigbee2mqtt/discussions/20805 2024-01-17 19:55:55 +01:00
mrskycriper ac74fac1db fix: Added icon and device_class for some Aqara device entities (#20746)
* Update homeassistant.ts

* Update homeassistant.ts

* Update homeassistant.ts

* Update homeassistant.ts

* Remove entity_category and add some icons and device classes

* Remove device_class for calibrated because of inverted state mapping
2024-01-17 19:44:52 +01:00
Koen KantersandKoenkk ab9124a517 fix(ignore): update zigbee-herdsman-converters to 18.14.0 (#20847)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-16 21:37:36 +00:00
Koen KantersandKoenkk 4e997c7b83 fix(ignore): update zigbee-herdsman to 0.32.6 (#20846)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-16 20:12:52 +00:00
slugzero 0007db42b0 fix(ignore): Ability to trigger events from converters on incoming Zigbee messages (#20776)
* emit event on converter triggers

* use meta.deviceExposesChanged() instead

* add ExposesChanged event for homeassistant discovery

* add tests for receive.ts and homeassistant.ts
2024-01-15 21:08:08 +01:00
Koen KantersandKoenkk 6ccbb11fc2 fix(ignore): update zigbee-herdsman-converters to 18.13.0 (#20828)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-15 21:07:35 +01:00
Koen KantersandKoenkk 4fe4e2ec2f fix(ignore): update zigbee-herdsman to 0.32.5 (#20826)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-15 19:09:15 +00:00
Koen KantersandKoenkk 2bd7df84eb Update zigbee-herdsman-converters to 18.12.0 (#20775)
* fix(ignore): update zigbee-herdsman-converters to 18.12.0

* Update configure.test.js

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-13 20:06:27 +00:00
Koen KantersandKoenkk ec3e37350d fix(ignore): update zigbee-herdsman to 0.32.4 (#20772)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-13 19:34:18 +00:00
Koen KantersandKoenkk 769bc14163 fix(ignore): update zigbee-herdsman to 0.32.3 (#20744)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-11 19:45:05 +00:00
Koen KantersandKoenkk a38b88455f fix(ignore): update zigbee-herdsman-converters to 18.11.0 (#20742)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-11 19:39:54 +00:00
Koen KantersandKoenkk 9a9bcd503e fix(ignore): update zigbee-herdsman to 0.32.2 (#20726)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-10 20:34:35 +00:00
Koen Kanters f58ee7ad58 chore: merge master to dev 2024-01-10 08:18:53 +01:00
Nerivec 3c9620428e chore: Add 'setup' field to problem report template (#20708)
Allow to discern patterns of behaviors emerging from specific setups (virtualization, low resource devices, etc).
2024-01-10 08:18:34 +01:00
Koen KantersandKoenkk 2bf244c2de fix(ignore): update zigbee-herdsman to 0.32.1 (#20706)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-09 20:26:46 +00:00
Koen KantersandKoenkk 5c8f95ae01 fix(ignore): update zigbee-herdsman-converters to 18.10.1 (#20705)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-09 20:23:01 +00:00
slugzero 363792aee8 fix: Set Home Assistant entity categories config and diagnostic according to expose categories (#20663)
* Add exposes starting with 'config' or 'diagnostic' to respective Home Assistant categories

* alt proposal: default category based on new Expose property

* Use single category 'system' instead of 'config' and 'diagnostic'

* Revert "Use single category 'system' instead of 'config' and 'diagnostic'"

This reverts commit 416dd9c568.

* lint
2024-01-09 19:54:49 +01:00
Koen KantersandKoenkk ce7d022fda Update zigbee-herdsman-converters to 18.10.0 (#20682)
* fix(ignore): update zigbee-herdsman-converters to 18.10.0

* Update bridge.test.js

* Update bridge.test.js

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-08 20:14:03 +00:00
Koen KantersandKoenkk 52f25eb101 fix(ignore): update zigbee-herdsman to 0.32.0 (#20681)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-08 19:27:08 +00:00
Koen KantersandKoenkk ef84ff86fe fix(ignore): update zigbee-herdsman to 0.31.0 (#20639)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-07 13:19:05 +00:00
koenkk dfbe93ef9d chore: fix release ci 2024-01-07 14:14:14 +01:00
koenkk 6bbe8d1844 chore: promote to dev 2024-01-07 14:08:07 +01:00
Koen Kanters 9eaaa0f9d1 chore(dev): release 1.35.1 (#20580) 2024-01-07 13:50:18 +01:00
koenkk 0807049b6c chore: fix changelog generator 2024-01-07 13:47:56 +01:00
Koen KantersandKoenkk 759c235923 fix(ignore): update zigbee-herdsman-converters to 18.9.0 (#20635)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-07 12:36:55 +00:00
Koen KantersandKoenkk 14ac9a8f68 fix(ignore): update zigbee-herdsman-converters to 18.8.0 (#20613)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-06 12:33:39 +00:00
Koen KantersandKoenkk d942db9a92 Update zigbee-herdsman-converters to 18.7.0 (#20607)
* fix(ignore): update zigbee-herdsman-converters to 18.7.0

* Update bridge.test.js

* Update frontend.test.js

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-06 10:32:21 +00:00
koenkk 212c70913c fix: Prevent extension errors from crashing Zigbee2MQTT https://github.com/Koenkk/zigbee2mqtt/issues/20477 2024-01-06 10:19:56 +01:00
Koen KantersandKoenkk b17c22bb50 fix(ignore): update zigbee-herdsman-converters to 18.6.0 (#20585)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-05 10:01:40 +00:00
koenkk c8c5ea2de3 chore: fix release-please 2024-01-04 22:34:33 +01:00
koenkk 00230baedf chore: fix release-please 2024-01-04 22:32:02 +01:00
koenkk 540969be21 chore: fix release-please 2024-01-04 22:30:28 +01:00
Koen KantersandKoenkk 804182e30c Update zigbee-herdsman-converters to 18.5.0 (#20573)
* fix(ignore): update zigbee-herdsman-converters to 18.5.0

* Update receive.ts

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-04 16:35:17 +01:00
Der Mundschenk & Compagnie 99a57bfd08 fix: Prevent warnings for bridge entities with legacy_entity_attributes enabled (#20548) 2024-01-04 14:54:39 +01:00
Der Mundschenk & Compagnie b8edf273c6 fix: Support legacy_availability_payload for Home Assistant connection_state (#20547) 2024-01-04 14:38:29 +01:00
koenkk e9aee4c6dc fix: Fix configure on startup crashing coordinator https://github.com/Koenkk/zigbee2mqtt/issues/20526 2024-01-03 10:51:05 +01:00
koenkk e6bcf91e17 fix: Fix crash due to unhandled Promise exception https://github.com/Koenkk/zigbee2mqtt/issues/20473 2024-01-03 09:09:18 +01:00
Koen KantersandKoenkk 7e37365a5f fix(ignore): update zigbee-herdsman-converters to 18.4.0 (#20523)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-02 22:00:24 +00:00
koenkk 32b1d5b073 fix: Fix Home Assistant program not truncated when too large https://github.com/Koenkk/zigbee2mqtt/issues/19788 2024-01-02 21:42:49 +01:00
Koen Kantersandnurikk cb7b773e0d fix(ignore): update zigbee2mqtt-frontend to 0.6.151 (#20509)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2024-01-02 14:36:20 +00:00
Koen Kanters 84387b27a4 chore: merge master to dev 2024-01-01 13:22:32 +01:00
koenkk 3d8f0fc3a8 chore: fix release-please 2024-01-01 13:22:03 +01:00
koenkk 02a587f745 chore: promote to dev 2024-01-01 12:33:54 +01:00
Koen Kanters b5a90e921b chore(dev): release 1.35.0 (#20467) 2024-01-01 12:29:01 +01:00
koenkk 32b0ded17c fix(ignore): fix dee8781e51 2024-01-01 12:18:25 +01:00
koenkk dee8781e51 fix(ignore): fix homeassistant.test.js 2024-01-01 11:42:48 +01:00
koenkk 64fd21c9c2 chore: fix release please 2024-01-01 11:14:28 +01:00
Koen KantersandKoenkk f9e0c2058f fix(ignore): update zigbee-herdsman-converters to 18.3.0 (#20465)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-01 09:57:41 +00:00
Koen KantersandKoenkk 16730b14a1 fix(ignore): update dependencies (#20452)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-01 10:53:14 +01:00
Koen KantersandKoenkk 05c65d31ed fix(ignore): update zigbee-herdsman to 0.30.0 (#20464)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2024-01-01 09:47:11 +00:00
Koen KantersandKoenkk a8c791b030 fix(ignore): update zigbee-herdsman-converters to 18.2.0 (#20451)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-30 21:59:55 +00:00
Koen KantersandKoenkk a3a87f4580 fix(ignore): update zigbee-herdsman-converters to 18.1.0 (#20426)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-12-29 07:47:48 +00:00
Koen KantersandKoenkk ffa43915f0 Update zigbee-herdsman-converters to 18.0.2 (#20404)
* fix(ignore): update zigbee-herdsman-converters to 18.0.2

* Update bridge.test.js

* Update networkMap.ts

---------

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

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

* Update otaUpdate.ts

---------

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

* updates

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

* Update publish.ts

* Update publish.ts

---------

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

Fixes #20283

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

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

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

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

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

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

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

* Add test

* Clarify comments

* Simplify test

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

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

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

* Add support for discovering bridge entities

* Add automatic discovery for bridge

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

* Link devices & groups to bridge

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

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

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

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

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

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

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

* fix

---------

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

* fix

---------

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

* Move Scene and getScenes to utils.ts

* Add auto-discovery for group scenes

* Update scene auto-discovery on change

* Add support for single endpoint scenes

* Add test for onScenesChanged

* Move Scene to types.d.ts

* Allow optional object_id_postfix key in payload

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

* Change: Allow frontend to listen on port without host

* Change: Unit test

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

Idle is mapped to idle state in HA

* Update homeassistant.test.js
2023-11-03 20:14:25 +01:00
Laxilef 1f2bd841c4 fix: Fix in_progress attribute for Home Assistant update sensor (#19542) 2023-11-03 17:16:57 +01:00
koenkk 1e905598f2 chore: fix release-please 2023-11-01 20:33:51 +01:00
koenkk e76b0ff282 chore: promote to dev 2023-11-01 20:23:03 +01:00
Koen Kanters 9996c931e2 chore(dev): release 1.33.2 (#19158) 2023-11-01 20:15:16 +01:00
Koen KantersandKoenkk 35386903f2 fix(ignore): update zigbee-herdsman-converters to 15.106.0 (#19511)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-11-01 18:53:54 +00:00
Koen KantersandKoenkk d79235e27b fix(ignore): update zigbee-herdsman-converters to 15.105.1 (#19503)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-31 20:25:11 +00:00
Franck Nijhof 69f6891db5 fix: Fix entity category of Home Assistant exposed enum sensors (#19474)
* Fix entity category of Home Assistant exposed enum sensors

* Process review comment
2023-10-31 16:31:14 +01:00
Koen KantersandKoenkk fe25924780 fix(ignore): update zigbee-herdsman-converters to 15.105.0 (#19492)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-30 20:28:33 +00:00
Koen KantersandKoenkk 0b3776916b fix(ignore): update dependencies (#19466)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-30 20:40:35 +01:00
Artem Sorokin 98c7a2ab56 fix: Add device class for soil_moisture sensor in HA (#19486) 2023-10-30 20:40:05 +01:00
Koen KantersandKoenkk e2fccc219b fix(ignore): update zigbee-herdsman-converters to 15.104.0 (#19479)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-29 18:51:46 +00:00
Koen Kantersandnurikk 6c7be58a3f fix(ignore): update zigbee2mqtt-frontend to 0.6.142 (#19447)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-10-27 21:43:51 +02:00
Koen Kantersandnurikk 2541feac25 fix(ignore): update zigbee2mqtt-frontend to 0.6.141 (#19446)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-10-27 19:39:04 +00:00
Koen KantersandKoenkk 7e82a491f6 fix(ignore): update zigbee-herdsman-converters to 15.103.0 (#19443)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-27 18:51:54 +00:00
Franck Nijhof e7749e25e9 fix: Fix entity category of Home Assistant exposed sensor entities (#19434)
* Fix entity category of Home Assistant exposed sensor entities

* ok, testing helps :)
2023-10-27 19:59:47 +02:00
Franck Nijhof d0a4f5f628 fix: Adjust default entity category of updates in Home Assistant (#19435) 2023-10-27 19:59:08 +02:00
Koen KantersandKoenkk 1fa10f6f28 fix(ignore): update zigbee-herdsman-converters to 15.102.0 (#19422)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-25 20:40:07 +02:00
Koen KantersandKoenkk f73cca0e39 fix(ignore): update zigbee-herdsman to 0.21.0 (#19421)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-25 18:30:39 +00:00
Koen Kantersandnurikk 0e55b382ba fix(ignore): update zigbee2mqtt-frontend to 0.6.140 (#19413)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-10-24 19:53:24 +00:00
dependabot[bot] 4d8c6b2856 chore: Bump actions/setup-node from 3 to 4 (#19411) 2023-10-24 19:14:34 +02:00
Koen KantersandKoenkk 52a545d86e fix(ignore): update zigbee-herdsman-converters to 15.101.0 (#19397)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-23 18:06:40 +00:00
Koen Kantersandnurikk 08a5f32ea9 fix(ignore): update zigbee2mqtt-frontend to 0.6.139 (#19378)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-10-22 11:54:16 +00:00
Koen KantersandKoenkk 71dd0ab772 fix(ignore): update zigbee-herdsman-converters to 15.100.0 (#19376)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-22 06:54:03 +00:00
Koen KantersandKoenkk 6d387b31c1 fix(ignore): update dependencies (#19375)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-22 08:30:22 +02:00
Koen Kantersandnurikk 17ad362fa7 fix(ignore): update zigbee2mqtt-frontend to 0.6.138 (#19365)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-10-22 08:29:53 +02:00
Koen KantersandKoenkk 0cd99487cc fix(ignore): update zigbee-herdsman-converters to 15.99.0 (#19366)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-21 09:04:47 +00:00
Koen Kanters e36983b4b5 fix: Publish zigbee-herdsman/zigbee-herdsman-converters version to bridge/info (#19364) 2023-10-21 10:56:31 +02:00
Koen Kanters 62f5dc875a chore: merge master to dev 2023-10-21 09:33:38 +02:00
gizmocuzandKoen Kanters 51f15bd306 Updated Domoticz implementation method (#19355)
* Updated Domoticz implementation method

* Update README.md

* Update README.md

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2023-10-21 09:33:18 +02:00
Koen KantersandKoenkk 8a28154661 fix(ignore): update zigbee-herdsman-converters to 15.98.0 (#19358)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-20 18:31:59 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ff85c63f29 chore: Bump @babel/traverse from 7.23.0 to 7.23.2 (#19349)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.23.0 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-20 16:42:45 +02:00
Koen KantersandKoenkk a86f3d8765 fix(ignore): update zigbee-herdsman-converters to 15.97.0 (#19337)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-18 18:49:42 +00:00
Koen KantersandKoenkk 5d1bd256f9 fix(ignore): update zigbee-herdsman-converters to 15.96.0 (#19328)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-17 18:49:00 +00:00
Koen KantersandKoenkk 3461f11869 fix(ignore): update zigbee-herdsman-converters to 15.95.0 (#19314)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-16 18:20:13 +00:00
Koen KantersandKoenkk 43a5c7b2f0 fix(ignore): update zigbee-herdsman to 0.20.0 (#19313)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-16 17:51:16 +00:00
Koen KantersandKoenkk 81a8df9855 fix(ignore): update zigbee-herdsman-converters to 15.94.2 (#19304)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-15 18:31:26 +00:00
Koen KantersandKoenkk 31541bd6ba fix(ignore): update dependencies (#19301)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-15 08:32:24 +02:00
Koen KantersandKoenkk 70b0fed2de fix(ignore): update zigbee-herdsman-converters to 15.94.1 (#19295)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-14 08:05:14 +00:00
FabianMangold cc86e76a7a fix: Enable incremental TSC builds (#19293) 2023-10-14 08:22:35 +02:00
Koen KantersandKoenkk 29247e19ac fix(ignore): update zigbee-herdsman-converters to 15.94.0 (#19290)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-13 18:23:37 +00:00
Koen KantersandKoenkk afb83fcbd0 fix(ignore): update zigbee-herdsman-converters to 15.93.0 (#19278)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-12 18:26:04 +00:00
Koen KantersandKoenkk a0c13c1c1e fix(ignore): update zigbee-herdsman to 0.19.2 (#19277)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-12 18:00:46 +00:00
Koen KantersandKoenkk d027b85200 fix(ignore): update zigbee-herdsman-converters to 15.92.1 (#19234)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-10 20:25:25 +00:00
kds69andKoen Kanters bd57f45929 chore: Fix cosmetic in homeassistant.ts (#19239)
* Update homeassistant.ts

* Update homeassistant.ts - remove double bracket in condition

No idea if this is what is causing code to be ignored in certain circumstances, but anyway this is cleansing code

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2023-10-10 22:05:19 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 9cff19b322 chore: Bump stefanzweifel/git-auto-commit-action from 4 to 5 (#19231)
Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 4 to 5.
- [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases)
- [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: stefanzweifel/git-auto-commit-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 20:07:25 +02:00
Koen Kanters a9e320e029 chore: merge master to dev 2023-10-08 17:12:50 +02:00
kds69 95d22c0822 fix: Enable Home Assistant force entity by default (#19212) 2023-10-08 17:12:28 +02:00
Koen KantersandKoenkk 2aae1bd54a fix(ignore): update zigbee-herdsman-converters to 15.90.0 (#19209)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-08 07:12:37 +00:00
Koen KantersandKoenkk 9615faac2f fix(ignore): update dependencies (#19208)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-08 09:04:37 +02:00
Laxilef cdce6b11d2 fix: Add in_progress attribute for Home Assistant update sensor (#19207) 2023-10-08 08:54:56 +02:00
Koen KantersandKoenkk de7736a370 fix(ignore): update zigbee-herdsman-converters to 15.89.1 (#19195)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-07 07:10:22 +00:00
Koen KantersandKoenkk fcb0af00d3 fix(ignore): update zigbee-herdsman to 0.19.1 (#19194)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-07 07:00:42 +00:00
koenkk daaade45d9 fix: Fix device keeps requesting OTA due to incorrect transaction sequence number. https://github.com/Koenkk/zigbee2mqtt/issues/19129 2023-10-07 08:55:16 +02:00
Koen KantersandKoenkk b085fac50f fix(ignore): update zigbee-herdsman-converters to 15.89.0 (#19190)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-06 15:03:40 +00:00
Koen KantersandKoenkk 0b60cd1100 fix(ignore): update zigbee-herdsman-converters to 15.88.0 (#19185)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-05 18:46:31 +00:00
dupondje 32c1bdfccb fix: Bump semver version (#19179) 2023-10-05 20:11:28 +02:00
Koen Kantersandnurikk b261f2c6c1 fix(ignore): update zigbee2mqtt-frontend to 0.6.137 (#19165)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-10-04 12:47:48 +00:00
koenkk 8cda915637 chore: fix release-please 2023-10-03 20:54:35 +02:00
Koen KantersandKoenkk de0f060378 fix(ignore): update zigbee-herdsman-converters to 15.87.0 (#19156)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-03 18:15:28 +00:00
koenkk 0edc0cda94 chore: promote to dev 2023-10-03 15:41:31 +02:00
Koen Kanters 7e63039144 chore(dev): release 1.33.1 (#19104) 2023-10-03 11:09:57 +02:00
Koen KantersandKoenkk b990ed9a60 fix(ignore): update zigbee-herdsman-converters to 15.86.1 (#19150)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-03 11:01:54 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ccc0f7ce53 chore: Bump alpine from 3.18.3 to 3.18.4 in /docker (#19116)
Bumps alpine from 3.18.3 to 3.18.4.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 10:58:37 +02:00
Rotzbua 83e331323c fix: Correct typos (#19143)
Credit to `codespell`.
2023-10-03 10:54:13 +02:00
Koen KantersandKoenkk 45ce494951 fix(ignore): update zigbee-herdsman-converters to 15.86.0 (#19149)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-03 08:45:18 +00:00
Koen KantersandKoenkk 1c7bb8fc24 fix(ignore): update dependencies (#19127)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-10-03 10:38:48 +02:00
Koen KantersandKoenkk 90763fd83f fix(ignore): update zigbee-herdsman-converters to 15.85.0 (#19102)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-28 10:42:33 +00:00
Koen KantersandKoenkk 7ccb4bcafe fix(ignore): update zigbee-herdsman-converters to 15.84.0 (#19096)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-27 18:59:28 +00:00
Koen KantersandKoenkk a7bb3fc9e8 fix(ignore): update zigbee-herdsman-converters to 15.83.0 (#19088)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-26 18:07:53 +00:00
Koen KantersandKoenkk 1190a342e9 fix(ignore): update zigbee-herdsman-converters to 15.82.0 (#19067)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-24 18:19:07 +00:00
Koen Kanters 31e4e8c552 chore: merge master to dev 2023-09-24 19:49:20 +02:00
koenkk 4d53ac4c84 chore: fix dev branch changelog link https://github.com/Koenkk/zigbee2mqtt/issues/19062 2023-09-24 19:48:55 +02:00
Koen KantersandKoenkk d99da63b89 fix(ignore): update dependencies (#19061)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-24 07:42:03 +02:00
Koen KantersandKoenkk 9a7aeaf62b fix(ignore): update zigbee-herdsman-converters to 15.81.1 (#19041)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-21 18:42:24 +00:00
Koen Kantersandnurikk 9b66ab1367 fix(ignore): update zigbee2mqtt-frontend to 0.6.136 (#19040)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-09-21 18:32:32 +00:00
Koen KantersandKoenkk e3d1c63463 Update zigbee-herdsman-converters to 15.81.0 (#19032)
* fix(ignore): update zigbee-herdsman-converters to 15.81.0

* fix tests

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-20 18:27:44 +00:00
Koen KantersandKoenkk af209b6f53 fix(ignore): update zigbee-herdsman-converters to 15.80.0 (#19023)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-19 18:14:44 +00:00
Koen KantersandKoenkk 7b558fef5f Update zigbee-herdsman-converters to 15.79.0 (#19003)
* fix(ignore): update zigbee-herdsman-converters to 15.79.0

* fix tests

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-18 17:35:36 +00:00
Koen KantersandKoenkk eac9240e06 fix(ignore): update zigbee-herdsman-converters to 15.78.0 (#18999)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-17 07:17:19 +00:00
Koen KantersandKoenkk 84f9950556 fix(ignore): update zigbee-herdsman to 0.19.0 (#18998)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-17 07:00:37 +00:00
Koen KantersandKoenkk 5477163727 fix(ignore): update dependencies (#18995)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-17 08:46:52 +02:00
Koen KantersandKoenkk 0a69bb54c8 fix(ignore): update zigbee-herdsman-converters to 15.77.0 (#18984)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-16 07:21:58 +00:00
Koen KantersandKoenkk 4040840d8e fix(ignore): update zigbee-herdsman-converters to 15.76.0 (#18978)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-14 19:16:23 +00:00
Koen KantersandKoenkk cc95c85969 fix(ignore): update zigbee-herdsman-converters to 15.75.0 (#18972)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-13 17:58:34 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3b64ea9d92 chore: Bump docker/setup-qemu-action from 2 to 3 (#18956)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 19:44:07 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 08a1468dda chore: Bump docker/setup-buildx-action from 2 to 3 (#18955)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 19:43:43 +02:00
Koen KantersandKoenkk 6de7662ca8 fix(ignore): update zigbee-herdsman-converters to 15.74.0 (#18949)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-11 18:18:48 +00:00
Koen KantersandKoenkk 4daddfc5f7 Update dependencies (#18923)
* fix(ignore): update dependencies

* fix types/node

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-10 09:28:14 +02:00
Koen KantersandKoenkk d6ed0f45da fix(ignore): update zigbee-herdsman-converters to 15.73.0 (#18917)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-09 06:47:11 +00:00
Koen KantersandKoenkk ea8de2ba1e fix(ignore): update zigbee-herdsman-converters to 15.72.0 (#18901)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-07 19:00:33 +00:00
Koen KantersandKoenkk f509410189 fix(ignore): update zigbee-herdsman-converters to 15.71.0 (#18887)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-06 18:39:23 +00:00
Koen KantersandKoenkk 91a9e32a83 fix(ignore): update zigbee-herdsman-converters to 15.70.0 (#18866)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-04 19:00:31 +00:00
Koen KantersandKoenkk 914b4c6757 fix(ignore): update zigbee-herdsman to 0.18.7 (#18865)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-04 18:43:08 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 03f381fdc2 chore: Bump actions/checkout from 3 to 4 (#18863)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 20:09:44 +02:00
Koen KantersandKoenkk 3285850efa fix(ignore): update zigbee-herdsman-converters to 15.69.0 (#18835)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-03 06:54:30 +00:00
Koen KantersandKoenkk 6654c7f1a3 fix(ignore): update zigbee-herdsman to 0.18.6 (#18838)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-02 07:17:04 +00:00
Koen Kanters d280e5ef2b chore: merge master to dev 2023-09-02 09:08:31 +02:00
rbswift f224ef7b7c chore: Improve update script in case of local changes (#18837)
* Stash local changes to prevent update failing

Call git stash to stash any local changes that would prevent the update.sh script from failing

* Update update.sh

changed to git checkout package-lock.json
2023-09-02 09:08:09 +02:00
Koen Kanters 0543ad4b19 fix: Fix configuration.example.yaml missing from container (#18836) 2023-09-02 08:51:50 +02:00
koenkk 9165bbb7b4 chore: bump release-please bootstrap-sha 2023-09-02 08:50:33 +02:00
koenkk f4ef421d27 chore: promote to dev 2023-09-01 16:05:15 +02:00
Koen Kanters 7ee207fc0e chore(dev): release 1.33.0 (#18490) 2023-09-01 15:57:57 +02:00
Koen KantersandKoenkk e78aa3589b fix(ignore): update zigbee-herdsman-converters to 15.67.1 (#18817)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-01 15:50:16 +02:00
Koen KantersandKoenkk 68169f0e50 fix(ignore): update zigbee-herdsman to 0.18.5 (#18816)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-01 13:45:06 +00:00
Koen KantersandKoenkk 97f6a840e4 fix(ignore): update zigbee-herdsman-converters to 15.67.0 (#18815)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-09-01 13:35:18 +00:00
Koen Kantersandnurikk 9a6445a8a0 fix(ignore): update zigbee2mqtt-frontend to 0.6.135 (#18814)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-09-01 15:27:52 +02:00
Koen KantersandKoenkk 0ec68a4b71 fix(ignore): update zigbee-herdsman-converters to 15.66.1 (#18790)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-31 07:35:19 +00:00
Koen KantersandKoenkk 5084b1293e fix(ignore): update zigbee-herdsman-converters to 15.64.0 (#18784)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-29 06:46:11 +00:00
Koen Kantersandnurikk 4949a54d66 fix(ignore): update zigbee2mqtt-frontend to 0.6.134 (#18761)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-08-27 10:59:06 +00:00
Koen KantersandKoenkk b193d89042 fix(ignore): update zigbee-herdsman-converters to 15.63.0 (#18758)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-27 06:42:34 +00:00
Koen KantersandKoenkk bc17e6406b fix(ignore): update dependencies (#18755)
* fix(ignore): update dependencies

* lock version

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-27 08:36:28 +02:00
Yip Rui Fung d1e50ce534 fix: Use QOS1 for rarely sent discovery and availability messages (#18756)
* Use QOS1 for rarely sent messages

Specifically discovery and availability depending on the set up, it's possible for these messages to be silently discarded by the broker.

Sending them as QOS1 means the mqtt library used will resend them until it gets an ACK.
Ensuring delivery is particularly important for messages that are rarely sent like discovery or availability as they are only sent when there are changes, and nondelivery or dropped messages for them impede proper functioning in home assistant.

* Fix tests for discovery and availability using QOS1
2023-08-27 06:26:07 +00:00
koenkk 677db105e2 chore: remove duplicate object_id warning for function expose 2023-08-26 08:43:47 +02:00
Koen KantersandKoenkk 514d9f38b2 fix(ignore): update zigbee-herdsman-converters to 15.62.0 (#18742)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-26 06:02:23 +00:00
Koen Kanters 704ecf366e feat: Add Home Assistant origin to discovery (#18741) 2023-08-26 07:56:31 +02:00
Koen KantersandKoenkk 1439f4c6ac fix(ignore): update zigbee-herdsman to 0.18.4 (#18727)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-25 07:46:49 +00:00
Koen KantersandKoenkk 6a1b6a1403 fix(ignore): update zigbee-herdsman to 0.18.3 (#18714)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-24 08:27:14 +00:00
Koen KantersandKoenkk 239c8fc70c Update zigbee-herdsman-converters to 15.61.0 (#18713)
* fix(ignore): update zigbee-herdsman-converters to 15.61.0

* u

* u

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-24 08:20:28 +00:00
Artem Draft e33e335c0c feat: Use labels in Home Assistant entity naming (#18678)
* feat: Use labels in Home Assistant entity naming

* tweak comment
2023-08-22 08:07:23 +02:00
Koen KantersandKoenkk 71c4211d13 fix(ignore): update zigbee-herdsman-converters to 15.60.0 (#18672)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-20 15:58:16 +00:00
4b3a2c9f5a feat: Support scene renaming (#18667)
Co-authored-by: Frank van Viegen <frank@vanviegen.net>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2023-08-20 17:50:10 +02:00
Koen KantersandKoenkk 7185d9e501 fix(ignore): update zigbee-herdsman-converters to 15.59.0 (#18661)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-19 07:17:20 +00:00
Koen KantersandKoenkk dcbf4e5b0e fix(ignore): update zigbee-herdsman-converters to 15.58.1 (#18658)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-18 18:08:05 +00:00
Koen KantersandKoenkk db812cd729 fix(ignore): update zigbee-herdsman to 0.18.2 (#18648)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-17 19:59:06 +00:00
Koen KantersandKoenkk c214f83298 Update zigbee-herdsman-converters to 15.58.0 (#18647)
* fix(ignore): update zigbee-herdsman-converters to 15.58.0

* fix tests

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-17 19:24:20 +00:00
Koen KantersandKoenkk 3736b5901b fix(ignore): update zigbee-herdsman to 0.18.1 (#18627)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-15 18:38:11 +00:00
Koen Kanters 3af130a2c0 feat: Support coordinator check (#18599)
* feat: Support coordinator check

* add tests
2023-08-14 20:35:15 +02:00
Koen KantersandKoenkk 145bc05dc3 fix(ignore): update zigbee-herdsman-converters to 15.57.0 (#18616)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-14 17:02:24 +00:00
Koen KantersandKoenkk cc0536c5ab fix(ignore): update zigbee-herdsman to 0.18.0 (#18600)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-13 07:13:00 +00:00
Koen KantersandKoenkk 0d00a144f5 fix(ignore): update dependencies (#18598)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-13 08:45:32 +02:00
Koen Kanters aa021d988d feat: Support generating extended_pan_id (#18596)
* feat: Support generating extended_pan_id

* Update configuration.example.yaml
2023-08-12 15:07:30 +02:00
Koen KantersandKoenkk ea147900ab Update zigbee-herdsman-converters to 15.56.0 (#18593)
* fix(ignore): update zigbee-herdsman-converters to 15.56.0

* Update bridge.test.js

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-12 12:39:00 +00:00
Koen KantersandKoenkk b1796e3e1a fix(ignore): update zigbee-herdsman-converters to 15.55.3 (#18578)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-10 16:23:27 +00:00
koenkk b55cc0a037 fix(ignore): chore: Update dependencies (#18459) 2023-08-09 20:04:20 +02:00
Koen KantersandKoenkk 8675b1c9ed fix(ignore): update zigbee-herdsman-converters to 15.55.2 (#18566)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-09 18:00:54 +00:00
koenkk b09b8ec3d6 fix(ignore): Revert "chore: Update dependencies (#18459)"
This reverts commit 53cb42103c.
2023-08-09 07:33:27 +02:00
Koen KantersandKoenkk 7f05f31c72 fix(ignore): update zigbee-herdsman-converters to 15.55.1 (#18557)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-08 18:10:21 +00:00
Koen KantersandKoenkk 53cb42103c chore: Update dependencies (#18459)
* fix(ignore): update dependencies

* Make compatible with MQTT 5

* updates

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-08 20:04:22 +02:00
Koen KantersandKoenkk a892a1b898 fix(ignore): update zigbee-herdsman to 0.17.3 (#18556)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-08 17:49:39 +00:00
Koen Kanters 1f52faee39 feat: Drop Node.js 14 support (#18555) 2023-08-08 19:40:22 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e0038380c7 chore: Bump alpine from 3.18.2 to 3.18.3 in /docker (#18554)
Bumps alpine from 3.18.2 to 3.18.3.

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 19:37:05 +02:00
Koen KantersandKoenkk 6050428566 fix(ignore): update zigbee-herdsman-converters to 15.55.0 (#18547)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-07 17:03:41 +00:00
Koen KantersandKoenkk b5e9ed436c fix(ignore): update zigbee-herdsman-converters to 15.54.1 (#18537)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-06 07:14:43 +00:00
Koen Kanters cf313c503b fix: Generate pan_id for new installations (#18530) 2023-08-05 13:22:31 +02:00
Koen KantersandKoenkk abba8b3ba8 fix(ignore): update zigbee-herdsman-converters to 15.54.0 (#18527)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-05 06:40:44 +00:00
Koen KantersandKoenkk f08cd81ac0 fix(ignore): update zigbee-herdsman-converters to 15.53.0 (#18524)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-04 17:37:59 +00:00
Koen Kanters 68ec507e30 fix: Add secure default config (#18357)
* fix: Add secure default config. https://github.com/Koenkk/zigbee2mqtt/issues/16188

* Enable frontend by default
2023-08-03 20:48:23 +02:00
Koen KantersandKoenkk 0718ca162d fix(ignore): update zigbee-herdsman-converters to 15.52.0 (#18514)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-03 18:39:15 +00:00
Koen KantersandKoenkk 2d917a7d0d fix(ignore): update zigbee-herdsman-converters to 15.51.0 (#18501)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-02 18:09:54 +00:00
koenkk ed22ecedbf fix: Fix Home Assistant TypeError: object of type 'NoneType' has no len() error. https://github.com/Koenkk/zigbee2mqtt/issues/17861 2023-08-02 19:34:39 +02:00
Koen Kanters f6196107a7 fix: Fix Home Assistant `MQTT device name is equal to entity name in your config (#18491)
* fix: Fix Home Assistant `MQTT device name is equal to entity name in your config` error. https://github.com/Koenkk/zigbee2mqtt/issues/18445

* remove onlythis
2023-08-01 20:39:02 +02:00
koenkk 49920ce093 chore: use .md extension for changelog 2023-08-01 20:15:01 +02:00
koenkk 33543c8588 chore: use GH_TOKEN for gist edit 2023-08-01 20:07:05 +02:00
koenkk c1413afa04 chore: publish changelog in gist 2023-08-01 20:04:14 +02:00
Koen Kanters df93e4c8f9 feat: Let Home Assistant generate entity name when device_class is present (#18208) 2023-08-01 19:46:24 +02:00
koenkk 7709af8a5e chore: promote to dev 2023-08-01 19:25:37 +02:00
Koen Kanters 1ec1e57244 chore(dev): release 1.32.2 (#18486) 2023-08-01 19:18:26 +02:00
koenkk 758b8144a1 fix(ignore): fix commit-user-lookup cache 2023-08-01 18:29:22 +02:00
koenkk 590564c158 fix(ignore): Attempt 3 to fix commit-user-lookup 2023-08-01 18:23:24 +02:00
koenkk ea412cfe82 fix(ignore): Attempt 2 to fix cache saving 2023-08-01 18:17:11 +02:00
koenkk fa02bb5b74 fix(ignore): Attempt to fix cache not saving 2023-08-01 18:11:36 +02:00
Koen KantersandKoenkk 683d29f9f7 fix(ignore): update zigbee-herdsman-converters to 15.50.1 (#18487)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-01 18:00:02 +02:00
Koen KantersandKoenkk 3478e40017 fix(ignore): update zigbee-herdsman-converters to 15.50.0 (#18475)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-08-01 15:30:00 +00:00
koenkk ba0cc61965 fix(ignore): Fix failing test 2023-07-31 18:05:00 +02:00
Koen KantersandKoenkk 8139d7f1f4 fix(ignore): update zigbee-herdsman-converters to 15.49.0 (#18443)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-27 17:59:55 +00:00
Koen KantersandKoenkk d487f37a7a fix(ignore): update zigbee-herdsman-converters to 15.48.0 (#18438)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-26 19:42:36 +00:00
Koen KantersandKoenkk 087b85cb9f fix(ignore): update zigbee-herdsman-converters to 15.47.0 (#18430)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-25 18:10:45 +00:00
Ilya KirovandKoen Kanters 4bd281ce6c fix(ignore): Add ep l till 24 (#18422)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2023-07-25 17:53:31 +00:00
Koen KantersandKoenkk ced3d43dad fix(ignore): update zigbee-herdsman-converters to 15.46.0 (#18421)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-24 17:50:49 +00:00
Jorge Schrauwen 91f22f1230 fix(ignore): Add th1-th10 endpoints for Ubisys H10 (#18405)
See koenkk/zigbee2mqtt#18121
2023-07-23 18:37:56 +02:00
Koen KantersandKoenkk 57d031d54a fix(ignore): update zigbee-herdsman-converters to 15.45.0 (#18404)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-23 07:49:55 +00:00
Koen KantersandKoenkk 8e946623a9 fix(ignore): update dependencies (#18403)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-23 09:15:07 +02:00
Koen Kantersandnurikk e1d52ae43a fix(ignore): update zigbee2mqtt-frontend to 0.6.133 (#18390)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-07-21 15:53:43 +00:00
Koen Kanters f64cd7e8f3 fix: Fix socket error crashing Zigbee2MQTT (#18388) 2023-07-21 14:51:39 +00:00
Bruno Bierbaumer df8e1687b2 fix: Fix insecure Zigbee network encryption key generation (#18372) 2023-07-20 19:26:29 +02:00
Koen KantersandKoenkk 3408e3c05a fix(ignore): update zigbee-herdsman-converters to 15.44.0 (#18365)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-19 17:46:37 +00:00
Koen KantersandKoenkk 7a22cee639 fix(ignore): update zigbee-herdsman-converters to 15.43.0 (#18356)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-18 18:32:43 +00:00
koenkk dec91919bc fix: Fix 'dict object' has no attribute 'ir_code_to_send' error. https://github.com/Koenkk/zigbee2mqtt/issues/18180 2023-07-18 19:53:48 +02:00
Koen KantersandKoenkk 313865b0a3 fix(ignore): update zigbee-herdsman-converters to 15.42.0 (#18343)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-17 18:07:42 +00:00
Koen KantersandKoenkk ee1a5f0400 fix(ignore): update dependencies (#18260)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-16 09:05:30 +02:00
Koen Kanters 7f2803b020 chore: merge master to dev 2023-07-16 08:44:34 +02:00
koenkk 83205e43da chore: fix d22d45a697 2023-07-16 08:41:41 +02:00
Koen Kanters 45e127e22f chore: merge master to dev 2023-07-16 08:39:51 +02:00
koenkk d22d45a697 chore: don't update @types/ws package 2023-07-16 08:39:23 +02:00
Koen KantersandKoenkk 330a913e76 fix(ignore): update zigbee-herdsman-converters to 15.41.0 (#18328)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-15 07:18:43 +00:00
Koen KantersandKoenkk af0521b54c fix(ignore): update zigbee-herdsman-converters to 15.40.0 (#18314)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-13 18:20:52 +00:00
Koen Kanters 8cf8cc0231 chore: merge master to dev 2023-07-13 19:59:31 +02:00
arteck f5f5b037b8 chore: Add iobroker intergration (#18308)
* Update README.md

* Update README.md
2023-07-13 19:56:26 +02:00
Brandon Rothweiler 1252f10d39 fix: Use HA sensor device class atmospheric_pressure instead of pressure (#18306)
* Update homeassistant.ts

* Update tests
2023-07-13 19:55:19 +02:00
Koen KantersandKoenkk a3c8e1c3c9 fix(ignore): update zigbee-herdsman-converters to 15.39.1 (#18292)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-11 19:46:51 +02:00
Koen KantersandKoenkk 073989c7a4 fix(ignore): update zigbee-herdsman to 0.17.2 (#18291)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-11 19:45:58 +02:00
Koen Kantersandnurikk 05eff957eb fix(ignore): update zigbee2mqtt-frontend to 0.6.132 (#18289)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-07-11 12:56:33 +00:00
Koen KantersandKoenkk 3c855d2042 fix(ignore): update zigbee-herdsman-converters to 15.39.0 (#18282)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-10 20:06:48 +02:00
koenkk 0bdf663ca1 fix: Fix not all cluster unbound/bound when specified. https://github.com/Koenkk/zigbee2mqtt/issues/10740 2023-07-10 20:00:57 +02:00
Koen KantersandKoenkk 813dd49a41 fix(ignore): update zigbee-herdsman-converters to 15.38.0 (#18262)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-09 12:12:40 +00:00
koenkk dab7e6a1ae chore: Merge branch 'master' into dev 2023-07-08 09:36:24 +02:00
koenkk b73c3e3dfb Revert "fix(ignore): update zigbee-herdsman-converters to 15.33.1-hotfix.0 (#18247)"
This reverts commit 1046f0e74a.
2023-07-08 09:34:55 +02:00
koenkk 4ccfdfc6f4 chore: release 1.32.1 2023-07-08 09:32:42 +02:00
koenkk 15601d6339 fix(ignore): Update zigbee-herdsman-converters to 15.33.1-hotfix.0 2023-07-08 09:21:42 +02:00
Koen KantersandKoenkk 1046f0e74a fix(ignore): update zigbee-herdsman-converters to 15.33.1-hotfix.0 (#18247)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-08 07:18:57 +00:00
Koen KantersandKoenkk 032e5238b8 fix(ignore): update zigbee-herdsman-converters to 15.37.0 (#18244)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-07 18:52:01 +00:00
Koen KantersandKoenkk 5b3fdf7b9f fix(ignore): update zigbee-herdsman to 0.17.1 (#18234)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-06 20:00:03 +02:00
Koen KantersandKoenkk 0489e75e1e fix(ignore): update zigbee-herdsman to 0.17.0 (#18232)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-06 14:31:34 +00:00
Koen KantersandKoenkk eb655f17db fix(ignore): update zigbee-herdsman-converters to 15.36.0 (#18227)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-05 17:27:06 +00:00
Koen KantersandKoenkk d8c7c00ca2 fix(ignore): update zigbee-herdsman-converters to 15.35.1 (#18216)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-04 19:07:31 +00:00
Koen KantersandKoenkk 9aa7e1ec86 fix(ignore): update zigbee-herdsman-converters to 15.35.0 (#18215)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-04 17:24:31 +00:00
Koen Kantersandnurikk caa652b0ad fix(ignore): update zigbee2mqtt-frontend to 0.6.131 (#18194)
Co-authored-by: nurikk <nurikk@users.noreply.github.com>
2023-07-03 10:08:04 +00:00
Koen Kanters 08e82e4980 chore: Add changelog to release PR (#18183) 2023-07-02 09:49:51 +02:00
Koen KantersandKoenkk 5067721fdf fix(ignore): update zigbee-herdsman-converters to 15.34.0 (#18181)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-02 09:19:51 +02:00
koenkk b014857a13 chore: promote to dev 2023-07-01 13:08:01 +02:00
koenkk eb7945248b chore: disable latest-dev version publishing 2023-07-01 13:07:46 +02:00
Koen Kanters 69638d7956 chore(dev): release 1.32.0 (#18163) 2023-07-01 12:50:40 +02:00
koenkk 953f3a232b chore: Fix release please 2023-07-01 12:25:11 +02:00
Koen KantersandKoenkk d1824f169b fix(ignore): update zigbee-herdsman-converters to 15.33.1 (#18161)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-07-01 06:37:59 +00:00
Koen KantersandKoenkk 81973c3a2a fix(ignore): update zigbee-herdsman-converters to 15.33.0 (#18156)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-30 17:05:35 +00:00
Koen Kanters 213e2b50d6 chore: merge master to dev 2023-06-30 18:52:54 +02:00
gruijter 0a63ae5376 chore: Added Homey Integration to README.md (#18152) 2023-06-30 18:52:33 +02:00
Koen KantersandKoenkk 61dd1dd96d fix(ignore): update zigbee-herdsman-converters to 15.32.0 (#18132)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-27 19:05:48 +00:00
Koen KantersandKoenkk ed85367e53 fix(ignore): update zigbee-herdsman-converters to 15.31.0 (#18125)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-26 17:47:42 +00:00
Koen KantersandKoenkk 38c2ea5bf7 fix(ignore): update zigbee-herdsman to 0.16.0 (#18124)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-26 17:40:00 +00:00
Koen KantersandKoenkk 2d62027e24 fix(ignore): update zigbee-herdsman to 0.15.3 (#18116)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-25 10:00:07 +02:00
Koen KantersandKoenkk a56719d19c fix(ignore): update zigbee-herdsman-converters to 15.30.0 (#18115)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-25 09:59:01 +02:00
Koen Kanters 94744525ba feat: Support settable Home Assistant text (#18114) 2023-06-25 09:23:13 +02:00
koenkk ea193f1938 Revert "fix(ignore): update dependencies (#18112)"
This reverts commit 57566c76ed.
2023-06-25 09:19:27 +02:00
koenkk 88339473ce fix(ignore): Fix DeviceMessage.meta type 2023-06-25 09:16:36 +02:00
github-actions[bot]andKoenkk 57566c76ed fix(ignore): update dependencies (#18112)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-25 08:12:19 +02:00
koenkk a09342836b chore: Improve changelog generator 2023-06-23 18:24:12 +02:00
Koen Kanters b3fbf78bec chore: Fix changelog generator cache (#18100)
* chore: Fix changelog

* u

* u

* u

* u

* u
2023-06-23 18:14:35 +02:00
Koen KantersandKoenkk a744838e9c fix(ignore): update zigbee-herdsman-converters to 15.29.0 (#18099)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-23 16:06:47 +00:00
koenkk 712c7bdbaf chore: Cache commit users in changelog generator 2023-06-22 21:10:10 +02:00
Koen KantersandKoenkk 6d3389b409 fix(ignore): update zigbee-herdsman-converters to 15.28.0 (#18092)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-22 18:46:31 +00:00
Koen KantersandKoenkk 03005b38c8 fix(ignore): update zigbee-herdsman to 0.15.2 (#18091)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-22 18:37:39 +00:00
Koen KantersandKoenkk 17cff2a26f fix(ignore): update zigbee-herdsman to 0.15.1 (#18080)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-21 17:32:24 +00:00
Koen KantersandKoenkk 5777d76e55 fix(ignore): update zigbee-herdsman-converters to 15.27.0 (#18075)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-20 17:18:43 +00:00
Koen KantersandKoenkk 91d67a0ef5 fix(ignore): update zigbee-herdsman-converters to 15.26.0 (#18067)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-19 19:09:38 +00:00
Grégoire SeuxandKoen Kanters e083a6bff7 feat: Support Home Assistant water device_class (#18066)
This should be sufficient for https://www.zigbee2mqtt.io/devices/ZVG1.html to be water_consumed sensor to be usable in Energy dashboard (water section).

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2023-06-19 19:21:43 +02:00
Koen KantersandKoenkk 57aaa5a85a fix(ignore): update zigbee-herdsman-converters to 15.25.0 (#18056)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-18 10:03:48 +00:00
koenkk 34bad92ec6 chore: fix update_deps 2023-06-18 11:00:53 +02:00
Koen Kanters b6a4acda9b Revert "fix(ignore): update dependencies (#18052)" (#18053)
This reverts commit 1dc0b24e9a.
2023-06-18 10:56:59 +02:00
github-actions[bot]andKoenkk 1dc0b24e9a fix(ignore): update dependencies (#18052)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-18 10:40:32 +02:00
dependabot[bot] 38de19bb2c chore: Bump alpine from 3.18.0 to 3.18.2 in /docker (#18030) 2023-06-15 18:56:06 +02:00
Koen KantersandKoenkk c47f5df021 fix(ignore): update zigbee-herdsman-converters to 15.24.0 (#18025)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-14 18:31:21 +00:00
Koen KantersandKoenkk 4ad838c114 fix(ignore): update zigbee-herdsman-converters to 15.23.0 (#18015)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-13 19:41:02 +00:00
Koen KantersandKoenkk b0b3aee648 fix(ignore): update zigbee-herdsman-converters to 15.22.0 (#18004)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-12 16:26:26 +00:00
koenkk 446dbbf1fc fix(ignore): Fix e10dd893ab 2023-06-12 18:22:15 +02:00
koenkk 11f0be55d8 fix: Fix Home Assistant truncate error when program is null. https://github.com/Koenkk/zigbee2mqtt/issues/16460 2023-06-12 18:10:45 +02:00
github-actions[bot]andKoenkk e10dd893ab fix(ignore): update dependencies (#17991)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-12 17:22:42 +02:00
Koen KantersandKoenkk 0dd899679d Update zigbee-herdsman-converters to 15.21.2 (#17967)
* fix(ignore): update zigbee-herdsman-converters to 15.21.2

* fix

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-08 21:42:29 +00:00
Koen KantersandKoenkk 05cab61e1f fix(ignore): update zigbee-herdsman to 0.15.0 (#17966)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-08 23:10:44 +02:00
Koen KantersandKoenkk 0c7e7e5089 fix(ignore): update zigbee-herdsman-converters to 15.20.0 (#17954)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-08 07:25:05 +00:00
koenkk 5f71776434 chore: improve changelog generator 2023-06-08 09:20:42 +02:00
Koen Kanters a7e02a7be8 fix(ignore): Better fix for #17891 (#17951)
* fix(ignore): Better fix for #17891

* remove onlythis
2023-06-08 09:04:14 +02:00
koenkk 7df6b2bab5 chore: Merge branch 'master' into dev 2023-06-07 20:24:57 +02:00
koenkk a1bea8c5ab chore: Fix changelog generator 2023-06-07 16:48:26 +02:00
Koen KantersandKoenkk c23d244405 Update zigbee-herdsman-converters to 15.19.1 (#17941)
* fix(ignore): update zigbee-herdsman-converters to 15.19.1

* fix

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-07 14:30:11 +00:00
koenkk 86898e5ceb chore: Merge branch 'master' into dev 2023-06-07 14:57:48 +02:00
Sean Kelly c12bedb3e5 fix: Fix HA entities unavailable after broker restart when broker does not persist retained messages (#17891)
* Fix race condition with bridge status message cancelling reinitialize after a reconnect to mqtt

* Fix tests
2023-06-07 09:16:11 +02:00
Koen KantersandKoenkk fd1622ba80 fix(ignore): update zigbee-herdsman-converters to 15.18.0 (#17917)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-05 08:44:21 +00:00
Koen KantersandKoenkk 7564019ecd fix(ignore): update zigbee-herdsman-converters to 15.17.0 (#17909)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-04 12:18:21 +00:00
koenkk 6d381da075 chore: fix ci.yml 2023-06-04 14:01:49 +02:00
github-actions[bot]andKoenkk b10aa83fc8 fix(ignore): update dependencies (#17903)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-04 13:47:46 +02:00
Koen KantersandKoenkk a7997a3cd4 fix(ignore): update zigbee-herdsman-converters to 15.16.0 (#17900)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-03 16:07:49 +00:00
Koen KantersandKoenkk 73c19bff71 fix(ignore): update zigbee-herdsman-converters to 15.15.1 (#17884)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-02 17:58:16 +00:00
Koen KantersandKoenkk 80aa21123c fix(ignore): update zigbee-herdsman-converters to 15.15.0 (#17872)
Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-02 07:52:26 +00:00
koenkk afd1760b6f chore: Fix changelog generator 2023-06-02 09:47:58 +02:00
Koen KantersandKoenkk ddcab827ad Update zigbee-herdsman-converters to 15.14.1 (#17859)
* fix(ignore): update zigbee-herdsman-converters to 15.14.1

* fix tests

---------

Co-authored-by: Koenkk <Koenkk@users.noreply.github.com>
2023-06-01 14:29:42 +00:00
koenkk d0a41548fd chore: Exclude node_modules from TS compiler 2023-06-01 16:10:05 +02:00
koenkk ff8e6b9c66 chore: fix release_please.yml 2023-06-01 15:25:20 +02:00
koenkk 25f30a94f5 Revert "chore: promote to dev"
This reverts commit efe451df79.
2023-06-01 15:15:24 +02:00
Koenkk efe451df79 chore: promote to dev 2023-06-01 13:13:42 +00:00
koenkk b97a09c75b chore: improve release_please.yml 2023-06-01 14:39:25 +02:00
koenkk f3b8c689a9 chore: improve release_please.yml 2023-06-01 14:36:39 +02:00
koenkk 4d1a8ee873 chore: promote to dev 2023-06-01 11:30:16 +02:00
185 changed files with 44745 additions and 36808 deletions
-2
View File
@@ -1,2 +0,0 @@
node_modules/*
test/
-46
View File
@@ -1,46 +0,0 @@
module.exports = {
'env': {
'jest/globals': true,
'es6': true,
'node': true,
},
'extends': ['eslint:recommended', 'google', 'plugin:jest/recommended', 'plugin:jest/style'],
'parserOptions': {
'ecmaVersion': 2018,
'sourceType': 'module',
},
'rules': {
'require-jsdoc': 'off',
'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',
],
'overrides': [{
files: ['*.ts'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['plugin:@typescript-eslint/recommended'],
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/semi': ['error'],
'array-bracket-spacing': ['error', 'never'],
'indent': ['error', 4],
'max-len': ['error', {'code': 120}],
'no-return-await': 'error',
'object-curly-spacing': ['error', 'never'],
},
}],
};
+2 -2
View File
@@ -1,4 +1,4 @@
github: [koenkk]
custom:
- https://www.paypal.me/koenkk
- https://www.buymeacoffee.com/koenkk
- https://www.paypal.me/koenkk
- https://www.buymeacoffee.com/koenkk
+24 -18
View File
@@ -1,20 +1,26 @@
blank_issues_enabled: false
contact_links:
- name: "IMPORTANT: Check development branch changelog first!!"
url: https://github.com/Koenkk/zigbee2mqtt/releases/tag/latest-dev
about: "Before submitting an issue, check if the issue hasn't already been solved in the development branch. Click 'Open' to see the release notes of the development branch. In case it is, you can read here how to switch to the development branch: https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html"
- name: Questions/discussion
url: https://github.com/Koenkk/zigbee2mqtt/discussions/new
about: Ask questions, discuss about devices or show things you made
- name: Frontend issues
url: https://github.com/nurikk/z2m-frontend/issues
about: Issues related to the frontend.
- name: Home Assistant addon issues
url: https://github.com/zigbee2mqtt/hassio-zigbee2mqtt/issues
about: Issues related to the Home Assistant addon.
- name: FAQ
url: https://www.zigbee2mqtt.io/guide/faq
about: Frequently asked questions.
- name: Support Chat
url: https://discord.gg/NyseBeK
about: Chat for feedback, questions and troubleshooting.
- name: 'IMPORTANT: Check development branch changelog first!!'
url: https://gist.github.com/Koenkk/bfd4c3d1725a2cccacc11d6ba51008ba
about: Before submitting an issue, check that it has not already been solved in the development branch. Click here to see the release notes of the development branch.
- name: Switch to the development branch
url: https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html
about: If the development branch solves an issue you are facing, click here to see the instructions to switch to the development branch.
- name: Questions/discussions
url: https://github.com/Koenkk/zigbee2mqtt/discussions/new
about: Ask questions, discuss devices, show things you made...
- name: Original frontend issues
url: https://github.com/nurikk/zigbee2mqtt-frontend/issues
about: Issues with the frontend package zigbee2mqtt-frontend
- name: WindFront frontend issues
url: https://github.com/Nerivec/zigbee2mqtt-windfront/issues
about: Issues with the frontend package zigbee2mqtt-windfront
- name: Home Assistant addon issues
url: https://github.com/zigbee2mqtt/hassio-zigbee2mqtt/issues
about: Issues with the Home Assistant addon
- name: FAQ
url: https://www.zigbee2mqtt.io/guide/faq
about: Frequently asked questions
- name: Support Chat
url: https://discord.gg/NyseBeK
about: Chat for feedback, questions and troubleshooting
@@ -0,0 +1,55 @@
name: External converter
description: Submit an external converter
title: '[External Converter]: '
labels: [external converter]
body:
- type: markdown
attributes:
value: |
**IMPORTANT:** In case the external converter fully works, please consider submitting a [pull request](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html#_4-create-a-pull-request) instead.
- 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: 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: textarea
id: external_definition
attributes:
label: External converter
description: Code of the external converter, to retrieve in the Z2M frontend go to Settings -> Dev Console -> External Converters
render: JavaScript
validations:
required: true
- type: textarea
attributes:
label: What does/doesn't work with the external definition?
description: Indicate what works and what doesn't work with the external definition.
validations:
required: true
- type: textarea
id: notes
attributes:
label: Notes
description: Any additional information or context that might be helpful
placeholder: Add any relevant notes or context here
validations:
required: false
+34 -34
View File
@@ -1,38 +1,38 @@
name: Feature request
description: Suggest an idea for this project
title: "[Feature request]: "
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
- type: markdown
attributes:
value: |
**IMPORTANT:** Before submitting:
- Is your feature request related to the frontend? 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. Eg. 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
+55 -51
View File
@@ -1,55 +1,59 @@
name: New device support request
description: Request support for a new device
title: "[New device support]: "
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)
- Make sure there is no existing issue or PR for this device already, search for your device here: https://github.com/Koenkk/zigbee2mqtt/issues
- Follow this [guide](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html)
- If you are using the Home Assistant addon and are still on 1.18.1, check the first point of [this](https://github.com/Koenkk/zigbee2mqtt/releases/tag/1.19.0)
- 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}'
- type: markdown
attributes:
value: |
**IMPORTANT:** Before submitting:
- Make sure this device is not already supported in the dev branch by checking the [dev branch changelog](https://gist.github.com/Koenkk/bfd4c3d1725a2cccacc11d6ba51008ba#new-supported-devices)
- Make sure there is no existing issue or PR for this device already, search for your device here: https://github.com/Koenkk/zigbee2mqtt/issues
- Follow this [guide](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html)
- If you are using the Home Assistant addon and are still on 1.18.1, check the first point of [this](https://github.com/Koenkk/zigbee2mqtt/releases/tag/1.19.0)
- 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: 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: textarea
id: external_definition
attributes:
label: External definition
description: See [Creating the external definition](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html#_2-creating-the-external-definition)
render: JavaScript
validations:
required: true
- type: textarea
attributes:
label: What does/doesn't work with the external definition?
description: Indicate what works and what doesn't work with the external definition.
validations:
required: true
- type: textarea
id: notes
attributes:
label: Notes
description: Any additional information or context that might be helpful
placeholder: Add any relevant notes or context here
validations:
required: false
+89 -63
View File
@@ -2,66 +2,92 @@ 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
- type: markdown
attributes:
value: |
**IMPORTANT:** Before submitting:
- Zigbee2MQTT fails to start? Read [this](https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start_crashes-runtime.html)
- Raspberry Pi users? Make sure to check [this](https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start_crashes-runtime.html#raspberry-pi-users-use-a-good-power-supply)
- You read the [FAQ](https://www.zigbee2mqtt.io/guide/faq/)
- Are you using an EZSP adapter (e.g. Dongle-E/SkyConnect)? Try the [new driver](https://github.com/Koenkk/zigbee2mqtt/discussions/21462)
- 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 frontend? 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, then 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. In case of EZSP, try the [new `ember` driver](https://github.com/Koenkk/zigbee2mqtt/discussions/21462) first.
placeholder: Electrolama zig-a-zig-ah! (zzh!), Slaesh's CC2652RB stick, ...
validations:
required: true
- type: textarea
id: setup
attributes:
label: Setup
description: |-
How do you run Z2M (plain, add-on...) and on what machine (Pi, x86-64, containerized...)?
When running on Linux also include output of: `uname -a && cat /etc/issue.net`
placeholder: Add-on on Home Assistant OS on Intel NUC, Plain on Docker container, ...
validations:
required: true
- type: input
id: device
attributes:
label: Device `database.db` entry
description: If the problem is related to a specific device, paste the `database.db` entry here
placeholder: '{"id":117,"type":"EndDevice","ieeeAddr":"0x00158d0001d8e1d2","nwkAddr":25887 ...'
- type: textarea
id: log
attributes:
label: Debug log
description: After enabling [debug logging](https://www.zigbee2mqtt.io/guide/configuration/logging.html#debugging) 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
- type: textarea
id: notes
attributes:
label: Notes
description: Any additional information or context that might be helpful
placeholder: Add any relevant notes or context here
validations:
required: false
+63 -60
View File
@@ -1,64 +1,67 @@
name: Wrong device picture/vendor/model/description
description: Use if device is detected as supported and is fully functional but has a wrong picture, vendor, model or description
title: "[Wrong device]: "
title: '[Wrong device]: '
labels: [wrong device]
body:
- type: markdown
attributes:
value: |
Only use this if the device is detected as **supported** and is **fully functional** but has a wrong picture, vendor, model or description.
- 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: model
attributes:
label: Model
description: Expected model, model that is printed on the device, for TuYa device this is NOT something like TS0601 or _TZE200_cf1sl3tj
placeholder: RTCGQ01LM
validations:
required: true
- type: input
id: description
attributes:
label: Description
description: Expected description
placeholder: Motion sensor
validations:
required: true
- type: input
id: vendor
attributes:
label: Vendor
description: Expected vendor
placeholder: Xiaomi
validations:
required: true
- type: input
id: picture
attributes:
label: Picture (link)
description: Expected picture
placeholder: https://www.linktomydevice.org/RTCGQ01LM.jpg
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: Notes
placeholder: Some additional notes...
validations:
required: false
- type: markdown
attributes:
value: |
Submit this issue only if your device is detected as **supported** and works **fully**, but displays an incorrect picture, vendor, model, or description.
**IMPORTANT**: Before opening this issue, please review the [guide](https://www.zigbee2mqtt.io/advanced/support-new-devices/02_support_new_tuya_devices.html#fixing-tuya-device-detection) for instructions on fixing the device detection yourself.
This usually only takes 5 minutes.
- 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: model
attributes:
label: Model
description: Expected model, model that is printed on the device, for Tuya device this is NOT something like TS0601 or _TZE200_cf1sl3tj
placeholder: RTCGQ01LM
validations:
required: true
- type: input
id: description
attributes:
label: Description
description: Expected description
placeholder: Motion sensor
validations:
required: true
- type: input
id: vendor
attributes:
label: Vendor
description: Expected vendor
placeholder: Xiaomi
validations:
required: true
- type: input
id: picture
attributes:
label: Picture (link)
description: Expected picture
placeholder: https://www.linktomydevice.org/RTCGQ01LM.jpg
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: Notes
placeholder: Some additional notes...
validations:
required: false
+459
View File
@@ -0,0 +1,459 @@
# GitHub Copilot Instructions
## Priority Guidelines
When generating code for this repository:
1. **Version Compatibility**: Always detect and respect the exact versions of languages, frameworks, and libraries used in this project
2. **Context Files**: Prioritize patterns and standards defined in the .github/copilot directory
3. **Codebase Patterns**: When context files don't provide specific guidance, scan the codebase for established patterns
4. **Architectural Consistency**: Maintain our layered architectural style with clear separation between controller, extensions, models, and utilities
5. **Code Quality**: Prioritize maintainability, performance, security, and testability in all generated code
## Technology Stack
### Core Technologies
- **Language**: TypeScript 5.9.3 with target `esnext` and module `NodeNext`
- **Runtime**: Node.js ^20 || ^22 || ^24
- **Package Manager**: pnpm 10.12.1
- **Testing**: Vitest 3.1.1 with @vitest/coverage-v8
- **Linting/Formatting**: Biome 2.2.5 (configured with 4-space indents, 150 line width, no bracket spacing)
### Key Dependencies
- **zigbee-herdsman**: 6.2.0 (exact version - critical for Zigbee protocol compatibility)
- **zigbee-herdsman-converters**: 25.42.0 (exact version - device definitions)
- **MQTT**: mqtt 5.14.1
- **Logging**: winston 3.18.3
- **YAML**: js-yaml 4.1.0
- **Decorators**: bind-decorator 1.0.11
- **WebSocket**: ws 8.18.1
### TypeScript Configuration
- **Strict Mode**: Enabled with `noImplicitAny` and `noImplicitThis`
- **Module System**: NodeNext with ESM interop
- **Decorators**: Experimental decorators enabled
- **Composite**: True (for project references)
- **Source Maps**: Inline source maps enabled
- **Output**: Compiled to `dist/` directory
## Project Architecture
### Directory Structure
```
lib/ # Source TypeScript files
├── controller.ts # Main controller orchestrating all components
├── mqtt.ts # MQTT client management
├── zigbee.ts # Zigbee network management
├── state.ts # State management
├── eventBus.ts # Event-driven communication
├── extension/ # Extension system (plugins)
│ ├── extension.ts # Abstract base class
│ ├── availability.ts
│ ├── bind.ts
│ ├── bridge.ts
│ ├── configure.ts
│ └── ...
├── model/ # Domain models
│ ├── device.ts
│ └── group.ts
├── util/ # Utility functions
│ ├── logger.ts
│ ├── settings.ts
│ ├── utils.ts
│ └── ...
└── types/ # TypeScript type definitions
└── api.ts
test/ # Vitest test files
data/ # Runtime configuration and data
```
### Architectural Patterns
#### Extension Pattern
All extensions inherit from the abstract `Extension` base class:
```typescript
abstract class Extension {
protected zigbee: Zigbee;
protected mqtt: Mqtt;
protected state: State;
protected publishEntityState: PublishEntityState;
protected eventBus: EventBus;
async start(): Promise<void> {}
async stop(): Promise<void> {}
}
```
#### Event-Driven Architecture
Use the `EventBus` for component communication. Events are strongly typed:
```typescript
interface EventBusMap {
deviceMessage: [data: eventdata.DeviceMessage];
mqttMessage: [data: eventdata.MQTTMessage];
publishEntityState: [data: eventdata.PublishEntityState];
// ... other events
}
```
#### Dependency Injection
The `Controller` class instantiates and injects dependencies into extensions. Follow this pattern when creating new extensions.
## Code Style and Conventions
### Naming Conventions
- **Classes**: PascalCase (e.g., `Extension`, `Device`, `EventBus`)
- **Interfaces/Types**: PascalCase (e.g., `MqttPublishOptions`, `DeviceOptions`)
- **Functions/Methods**: camelCase (e.g., `publishEntityState`, `enableDisableExtension`)
- **Constants**: SCREAMING_SNAKE_CASE for top-level constants (e.g., `CURRENT_VERSION`, `LOG_LEVELS`)
- **Private members**: Prefix with underscore for private class fields only when needed to distinguish from public properties (e.g., `_definitionModelID`)
- **Files**: camelCase for TypeScript files (e.g., `eventBus.ts`, `externalJS.ts`)
### Import Organization
Follow this import order (separated by blank lines):
1. Node.js built-in modules (use `node:` prefix: `import fs from "node:fs"`)
2. Third-party libraries (e.g., `bind-decorator`, `mqtt`)
3. Type-only imports from external packages (using `type` keyword)
4. Internal absolute imports from project root
5. Type-only imports from internal modules
Example:
```typescript
import fs from "node:fs";
import bind from "bind-decorator";
import type {IClientOptions} from "mqtt";
import {connectAsync} from "mqtt";
import type {Zigbee2MQTTAPI} from "./types/api";
import logger from "./util/logger";
import * as settings from "./util/settings";
```
### Type Annotations
- Use `type` imports for TypeScript types: `import type * as zhc from "zigbee-herdsman-converters"`
- Explicitly type function parameters and return types
- Use `KeyValue` type for generic object payloads: `type KeyValue = Record<string, any>`
- Prefer interfaces for object shapes, type aliases for unions/intersections
- Use namespace exports for related types: `export type * as ZSpec from "zigbee-herdsman/dist/zspec"`
### Async/Await Patterns
- Always use `async/await` for asynchronous operations
- Return types should be explicitly `Promise<Type>`
- Methods that don't return values should be `Promise<void>`
- Use `Awaited<ReturnType<typeof fn>>` for inferring async function return types
### Decorators
Use `@bind` decorator from `bind-decorator` for methods that need `this` binding:
```typescript
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
// Implementation
}
```
### Error Handling
- Use `throw new Error("message")` for explicit errors
- Include descriptive error messages
- Log errors using the logger: `logger.error("message")`
- For Zigbee-herdsman errors, log the stack trace: `logger.error((error as Error).stack!)`
- Catch and handle errors at appropriate boundaries (controller level)
### Logging
Use the centralized logger (winston-based):
```typescript
import logger from "./util/logger";
logger.info("message");
logger.warning("message");
logger.error("message");
logger.debug("message");
```
- Use namespaced loggers for specific modules (created internally by logger)
- Log levels: `error`, `warning`, `info`, `debug` (from most to least critical)
- Include relevant context in log messages (device names, IEEE addresses, etc.)
## Code Quality Standards
### Maintainability
- Write self-documenting code with clear, descriptive names
- Keep methods focused on single responsibilities
- Abstract classes should define clear contracts with protected members for subclasses
- Use constructor dependency injection for required dependencies
- Limit function complexity - methods should be concise and focused
- Use TypeScript's strict mode features (`noImplicitAny`, `noImplicitThis`)
### Performance
- Use `rimrafSync` for synchronous file deletion when appropriate
- Leverage async/await for I/O operations to avoid blocking
- Use JSON stable stringify for consistent object serialization: `json-stable-stringify-without-jsonify`
- Cache computed values when appropriate (see device model patterns)
- Use getter methods for computed properties that should be cached
### Security
- Validate input using Ajv JSON schema validation (see `settings.ts` pattern)
- Sanitize file paths using `path.join` from Node.js
- Use YAML safe loading: `yaml.safeLoad()`
- Handle sensitive data (credentials, tokens) through settings with proper defaults
- Never log sensitive information (passwords, tokens)
### Testability
- Write tests using Vitest with describe/it/expect patterns
- Mock external dependencies using Vitest's `vi.mock()`
- Use `beforeEach`, `afterEach`, `beforeAll`, `afterAll` for test setup/teardown
- Place test files in `test/` directory with `.test.ts` extension
- Mock constructors and modules in the pattern shown in `test/controller.test.ts`
- Use `flushPromises()` utility for async test synchronization
- Target 100% code coverage (configured in vitest.config.mts)
## Testing Standards
### Unit Testing Structure
```typescript
import {afterAll, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
describe("ComponentName", () => {
beforeEach(() => {
// Setup
});
it("Should do something specific", async () => {
// Arrange
const input = {};
// Act
const result = await someFunction(input);
// Assert
expect(result).toBe(expected);
});
});
```
### Mocking Patterns
- Create mock modules in `test/mocks/` directory
- Use `vi.fn()` for function mocks
- Use `vi.mock()` for module mocks
- Clear mocks in `afterEach` or between tests
- Mock external libraries like `mqtt`, `zigbee-herdsman` consistently
### Test Coverage
- All code in `lib/**` should be covered
- Use coverage reports: `pnpm test:coverage`
- Thresholds set to 100% (can be adjusted per project needs)
- Tests should cover both success and failure paths
## Documentation Standards
### JSDoc Comments
Use JSDoc-style comments for classes and public methods:
```typescript
/**
* Besides initializing variables, the constructor should do nothing!
*
* @param {Zigbee} zigbee Zigbee controller
* @param {Mqtt} mqtt MQTT controller
* @param {State} state State controller
* @param {Function} publishEntityState Method to publish device state to MQTT.
* @param {EventBus} eventBus The event bus
*/
constructor(zigbee: Zigbee, mqtt: Mqtt, state: State, ...) {
```
### Comment Style
- Use single-line comments (`//`) for implementation notes
- Use JSDoc (`/** */`) for public APIs and class/method documentation
- Include context for non-obvious logic
- Document parameters with their types and purposes
- Use `@param` tags with TypeScript types in braces
- Use biome-ignore comments when necessary: `// biome-ignore lint/rule: reason`
### Code Documentation
- Document complex algorithms or business logic
- Explain "why" not just "what" when logic is non-trivial
- Include links to relevant issues or documentation when applicable
- Document deprecations and breaking changes
## TypeScript-Specific Guidelines
### Module System
- Use ES modules with `import`/`export` syntax
- Default exports for main classes: `export default class Device {}`
- Named exports for utilities and types: `export const LOG_LEVELS = ...`
- Namespace exports for related types: `export type * as ZSpec from ...`
- Use `.js` extension in imports for local modules when using dynamic imports: `await import("./extension/frontend.js")`
### Type Safety
- Enable all strict type checking options
- Use type guards and assertions when necessary: `asserts expose is zhc.Numeric`
- Prefer `unknown` over `any` when type is truly unknown
- Use `// biome-ignore lint/suspicious/noExplicitAny: API` when `any` is necessary
- Define proper interfaces for external module types (e.g., `unix-dgram.d.ts`)
### Generic Types
- Use generics for reusable, type-safe abstractions
- Example: `abstract class ExternalJSExtension<M> extends Extension`
- Constrain generics when appropriate
- Document generic type parameters
### Utility Types
- Use built-in utility types: `Partial`, `Required`, `Pick`, `Omit`, `Record`
- Use `Awaited<ReturnType<typeof fn>>` for async function return types
- Define custom utility types when patterns emerge
- Use `type` for aliases, `interface` for object shapes
## Version Control and Releases
### Versioning Strategy
- Follow Semantic Versioning (MAJOR.MINOR.PATCH)
- Current version managed in `package.json`
- Use `-dev` suffix for development versions (e.g., `2.6.2-dev`)
- Configuration version tracked separately: `CURRENT_VERSION = 4`
### Changelog
- Maintain CHANGELOG.md with all changes
- Group changes by type: Bug Fixes, Features, Breaking Changes
- Include issue/PR references: `([#28583](url))`
- Include commit references: `([09f33b3](url))`
- Use conventional commits format
### Git Workflow
- Development on `dev` branch
- Production releases from `master` branch
- Use meaningful commit messages
- Reference issues in commits
## Project-Specific Patterns
### Settings Management
- All configuration loaded through `util/settings.ts`
- Validate settings using Ajv with JSON schema
- Schema defined in `settings.schema.json`
- Support runtime setting changes with restart detection
- Use `settings.get()` to access current configuration
- Use `settings.getDevice(ieeeAddr)` for device-specific config
### Device and Group Models
- Devices and groups are domain models wrapping `zigbee-herdsman` entities
- Access underlying entity via `.zh` property
- Expose computed properties as getters
- Include definition from `zigbee-herdsman-converters`
- Handle coordinator devices specially (type checking)
### MQTT Integration
- MQTT client wrapped in `Mqtt` class
- Publish options: `retain`, `qos` properties
- Topics follow pattern: `{base_topic}/{device}/{attribute}`
- Event-based message handling via EventBus
- Clean disconnect handling with retry logic
### Extension System
- Extensions are loosely coupled plugins
- Lifecycle: constructor → start() → stop()
- Constructor should only assign properties (no side effects)
- Use EventBus for inter-extension communication
- Extensions can be enabled/disabled at runtime
- External extensions loaded from `data/external_extensions/`
### State Management
- State persisted to `state.json`
- Cached in memory for performance
- Device states include all exposed attributes
- State changes trigger events via EventBus
## Best Practices Specific to This Project
1. **Never use language features beyond TypeScript 5.9.3 or ES2024**
2. **Always respect exact versions of zigbee-herdsman and zigbee-herdsman-converters** - these are critical for device compatibility
3. **Use the EventBus for all component communication** - avoid direct coupling
4. **Follow the Extension pattern for new features** - don't add logic directly to Controller
5. **Log appropriately** - info for user-relevant events, debug for developer info, error for failures
6. **Test with real Zigbee scenarios** - many edge cases exist with different device types
7. **Handle coordinator specially** - coordinator is a device but with unique behavior
8. **Validate all external input** - MQTT messages, configuration files, device data
9. **Use the bind decorator** for event handlers to preserve `this` context
10. **Match the exact code formatting** - Biome enforces 4 spaces, 150 line width, no bracket spacing
## Common Patterns to Follow
### Creating a New Extension
1. Extend `Extension` abstract class
2. Accept all dependencies in constructor
3. Implement `start()` method for initialization
4. Subscribe to EventBus events in `start()`
5. Implement `stop()` method for cleanup
6. Export as default: `export default class MyExtension extends Extension`
### Accessing Device Information
```typescript
const device: Device; // Our wrapper
device.ieeeAddr; // IEEE address
device.name; // Friendly name
device.zh; // Underlying zigbee-herdsman device
device.definition; // zigbee-herdsman-converters definition
device.options; // User configuration
```
### Publishing MQTT Messages
```typescript
await this.mqtt.publish(topic, message, {retain: true, qos: 0});
```
### Emitting Events
```typescript
this.eventBus.emit('deviceMessage', {device, message});
```
### Listening to Events
```typescript
this.eventBus.on('deviceMessage', this.onDeviceMessage, this);
```
## Integration Points
### Zigbee-Herdsman Integration
- Start controller: `await this.zigbee.start()`
- Access coordinator: `this.zigbee.coordinator()`
- Device operations through `zigbee-herdsman` API
- Event handling through EventBus wrappers
### MQTT Integration
- Connect: `await this.mqtt.connect()`
- Subscribe: `await this.mqtt.subscribe(topic)`
- Publish: `await this.mqtt.publish(topic, message, options)`
- Handle messages via EventBus `mqttMessage` event
### Frontend Integration
- Optional extension loaded dynamically
- Serves static files with compression
- WebSocket support for real-time updates
- Configurable port and base URL
### Home Assistant Integration
- Optional extension for discovery
- Publishes discovery messages to MQTT
- Supports entities, sensors, and devices
- Configurable discovery topic
## Critical Compatibility Notes
1. **Node.js**: Only versions 20, 22, and 24 are supported
2. **TypeScript**: Features must be compatible with 5.9.3
3. **Zigbee Libraries**: Exact versions are critical - do not suggest upgrades without testing
4. **MQTT Protocol**: Uses MQTT 3.1.1 and 5.0 features
5. **ES Modules**: Project uses ESM with NodeNext resolution
6. **Experimental Decorators**: Required for `@bind` decorator support
## When in Doubt
1. **Search for similar patterns** in the existing codebase
2. **Check existing extensions** for implementation examples
3. **Follow the controller and extension architecture** - don't bypass it
4. **Consult the test files** for usage examples
5. **Match the exact style** - run `pnpm check` to verify
6. **Prioritize consistency** over external best practices
7. **Test thoroughly** - this project controls real hardware
## Resources
- Repository: https://github.com/Koenkk/zigbee2mqtt
- Documentation: https://koenkk.github.io/zigbee2mqtt
- License: GPL-3.0
- Issue Tracker: https://github.com/Koenkk/zigbee2mqtt/issues
+24 -11
View File
@@ -1,13 +1,26 @@
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
- package-ecosystem: npm
versioning-strategy: increase
directory: /
schedule:
interval: weekly
target-branch: dev
commit-message:
prefix: fix(ignore)
groups:
minor-patch:
applies-to: version-updates
update-types:
- minor
- patch
- package-ecosystem: docker
directory: /docker
schedule:
interval: weekly
target-branch: dev
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
target-branch: dev
@@ -0,0 +1,294 @@
---
description: 'Technology-agnostic blueprint generator for creating comprehensive copilot-instructions.md files that guide GitHub Copilot to produce code consistent with project standards, architecture patterns, and exact technology versions by analyzing existing codebase patterns and avoiding assumptions.'
mode: 'agent'
---
# Copilot Instructions Blueprint Generator
## Configuration Variables
${PROJECT_TYPE="Auto-detect|.NET|Java|JavaScript|TypeScript|React|Angular|Python|Multiple|Other"} <!-- Primary technology -->
${ARCHITECTURE_STYLE="Layered|Microservices|Monolithic|Domain-Driven|Event-Driven|Serverless|Mixed"} <!-- Architectural approach -->
${CODE_QUALITY_FOCUS="Maintainability|Performance|Security|Accessibility|Testability|All"} <!-- Quality priorities -->
${DOCUMENTATION_LEVEL="Minimal|Standard|Comprehensive"} <!-- Documentation requirements -->
${TESTING_REQUIREMENTS="Unit|Integration|E2E|TDD|BDD|All"} <!-- Testing approach -->
${VERSIONING="Semantic|CalVer|Custom"} <!-- Versioning approach -->
## Generated Prompt
"Generate a comprehensive copilot-instructions.md file that will guide GitHub Copilot to produce code consistent with our project's standards, architecture, and technology versions. The instructions must be strictly based on actual code patterns in our codebase and avoid making any assumptions. Follow this approach:
### 1. Core Instruction Structure
```markdown
# GitHub Copilot Instructions
## Priority Guidelines
When generating code for this repository:
1. **Version Compatibility**: Always detect and respect the exact versions of languages, frameworks, and libraries used in this project
2. **Context Files**: Prioritize patterns and standards defined in the .github/copilot directory
3. **Codebase Patterns**: When context files don't provide specific guidance, scan the codebase for established patterns
4. **Architectural Consistency**: Maintain our ${ARCHITECTURE_STYLE} architectural style and established boundaries
5. **Code Quality**: Prioritize ${CODE_QUALITY_FOCUS == "All" ? "maintainability, performance, security, accessibility, and testability" : CODE_QUALITY_FOCUS} in all generated code
## Technology Version Detection
Before generating code, scan the codebase to identify:
1. **Language Versions**: Detect the exact versions of programming languages in use
- Examine project files, configuration files, and package managers
- Look for language-specific version indicators (e.g., <LangVersion> in .NET projects)
- Never use language features beyond the detected version
2. **Framework Versions**: Identify the exact versions of all frameworks
- Check package.json, .csproj, pom.xml, requirements.txt, etc.
- Respect version constraints when generating code
- Never suggest features not available in the detected framework versions
3. **Library Versions**: Note the exact versions of key libraries and dependencies
- Generate code compatible with these specific versions
- Never use APIs or features not available in the detected versions
## Context Files
Prioritize the following files in .github/copilot directory (if they exist):
- **architecture.md**: System architecture guidelines
- **tech-stack.md**: Technology versions and framework details
- **coding-standards.md**: Code style and formatting standards
- **folder-structure.md**: Project organization guidelines
- **exemplars.md**: Exemplary code patterns to follow
## Codebase Scanning Instructions
When context files don't provide specific guidance:
1. Identify similar files to the one being modified or created
2. Analyze patterns for:
- Naming conventions
- Code organization
- Error handling
- Logging approaches
- Documentation style
- Testing patterns
3. Follow the most consistent patterns found in the codebase
4. When conflicting patterns exist, prioritize patterns in newer files or files with higher test coverage
5. Never introduce patterns not found in the existing codebase
## Code Quality Standards
${CODE_QUALITY_FOCUS.includes("Maintainability") || CODE_QUALITY_FOCUS == "All" ? `### Maintainability
- Write self-documenting code with clear naming
- Follow the naming and organization conventions evident in the codebase
- Follow established patterns for consistency
- Keep functions focused on single responsibilities
- Limit function complexity and length to match existing patterns` : ""}
${CODE_QUALITY_FOCUS.includes("Performance") || CODE_QUALITY_FOCUS == "All" ? `### Performance
- Follow existing patterns for memory and resource management
- Match existing patterns for handling computationally expensive operations
- Follow established patterns for asynchronous operations
- Apply caching consistently with existing patterns
- Optimize according to patterns evident in the codebase` : ""}
${CODE_QUALITY_FOCUS.includes("Security") || CODE_QUALITY_FOCUS == "All" ? `### Security
- Follow existing patterns for input validation
- Apply the same sanitization techniques used in the codebase
- Use parameterized queries matching existing patterns
- Follow established authentication and authorization patterns
- Handle sensitive data according to existing patterns` : ""}
${CODE_QUALITY_FOCUS.includes("Accessibility") || CODE_QUALITY_FOCUS == "All" ? `### Accessibility
- Follow existing accessibility patterns in the codebase
- Match ARIA attribute usage with existing components
- Maintain keyboard navigation support consistent with existing code
- Follow established patterns for color and contrast
- Apply text alternative patterns consistent with the codebase` : ""}
${CODE_QUALITY_FOCUS.includes("Testability") || CODE_QUALITY_FOCUS == "All" ? `### Testability
- Follow established patterns for testable code
- Match dependency injection approaches used in the codebase
- Apply the same patterns for managing dependencies
- Follow established mocking and test double patterns
- Match the testing style used in existing tests` : ""}
## Documentation Requirements
${DOCUMENTATION_LEVEL == "Minimal" ?
`- Match the level and style of comments found in existing code
- Document according to patterns observed in the codebase
- Follow existing patterns for documenting non-obvious behavior
- Use the same format for parameter descriptions as existing code` : ""}
${DOCUMENTATION_LEVEL == "Standard" ?
`- Follow the exact documentation format found in the codebase
- Match the XML/JSDoc style and completeness of existing comments
- Document parameters, returns, and exceptions in the same style
- Follow existing patterns for usage examples
- Match class-level documentation style and content` : ""}
${DOCUMENTATION_LEVEL == "Comprehensive" ?
`- Follow the most detailed documentation patterns found in the codebase
- Match the style and completeness of the best-documented code
- Document exactly as the most thoroughly documented files do
- Follow existing patterns for linking documentation
- Match the level of detail in explanations of design decisions` : ""}
## Testing Approach
${TESTING_REQUIREMENTS.includes("Unit") || TESTING_REQUIREMENTS == "All" ?
`### Unit Testing
- Match the exact structure and style of existing unit tests
- Follow the same naming conventions for test classes and methods
- Use the same assertion patterns found in existing tests
- Apply the same mocking approach used in the codebase
- Follow existing patterns for test isolation` : ""}
${TESTING_REQUIREMENTS.includes("Integration") || TESTING_REQUIREMENTS == "All" ?
`### Integration Testing
- Follow the same integration test patterns found in the codebase
- Match existing patterns for test data setup and teardown
- Use the same approach for testing component interactions
- Follow existing patterns for verifying system behavior` : ""}
${TESTING_REQUIREMENTS.includes("E2E") || TESTING_REQUIREMENTS == "All" ?
`### End-to-End Testing
- Match the existing E2E test structure and patterns
- Follow established patterns for UI testing
- Apply the same approach for verifying user journeys` : ""}
${TESTING_REQUIREMENTS.includes("TDD") || TESTING_REQUIREMENTS == "All" ?
`### Test-Driven Development
- Follow TDD patterns evident in the codebase
- Match the progression of test cases seen in existing code
- Apply the same refactoring patterns after tests pass` : ""}
${TESTING_REQUIREMENTS.includes("BDD") || TESTING_REQUIREMENTS == "All" ?
`### Behavior-Driven Development
- Match the existing Given-When-Then structure in tests
- Follow the same patterns for behavior descriptions
- Apply the same level of business focus in test cases` : ""}
## Technology-Specific Guidelines
${PROJECT_TYPE == ".NET" || PROJECT_TYPE == "Auto-detect" || PROJECT_TYPE == "Multiple" ? `### .NET Guidelines
- Detect and strictly adhere to the specific .NET version in use
- Use only C# language features compatible with the detected version
- Follow LINQ usage patterns exactly as they appear in the codebase
- Match async/await usage patterns from existing code
- Apply the same dependency injection approach used in the codebase
- Use the same collection types and patterns found in existing code` : ""}
${PROJECT_TYPE == "Java" || PROJECT_TYPE == "Auto-detect" || PROJECT_TYPE == "Multiple" ? `### Java Guidelines
- Detect and adhere to the specific Java version in use
- Follow the exact same design patterns found in the codebase
- Match exception handling patterns from existing code
- Use the same collection types and approaches found in the codebase
- Apply the dependency injection patterns evident in existing code` : ""}
${PROJECT_TYPE == "JavaScript" || PROJECT_TYPE == "TypeScript" || PROJECT_TYPE == "Auto-detect" || PROJECT_TYPE == "Multiple" ? `### JavaScript/TypeScript Guidelines
- Detect and adhere to the specific ECMAScript/TypeScript version in use
- Follow the same module import/export patterns found in the codebase
- Match TypeScript type definitions with existing patterns
- Use the same async patterns (promises, async/await) as existing code
- Follow error handling patterns from similar files` : ""}
${PROJECT_TYPE == "React" || PROJECT_TYPE == "Auto-detect" || PROJECT_TYPE == "Multiple" ? `### React Guidelines
- Detect and adhere to the specific React version in use
- Match component structure patterns from existing components
- Follow the same hooks and lifecycle patterns found in the codebase
- Apply the same state management approach used in existing components
- Match prop typing and validation patterns from existing code` : ""}
${PROJECT_TYPE == "Angular" || PROJECT_TYPE == "Auto-detect" || PROJECT_TYPE == "Multiple" ? `### Angular Guidelines
- Detect and adhere to the specific Angular version in use
- Follow the same component and module patterns found in the codebase
- Match decorator usage exactly as seen in existing code
- Apply the same RxJS patterns found in the codebase
- Follow existing patterns for component communication` : ""}
${PROJECT_TYPE == "Python" || PROJECT_TYPE == "Auto-detect" || PROJECT_TYPE == "Multiple" ? `### Python Guidelines
- Detect and adhere to the specific Python version in use
- Follow the same import organization found in existing modules
- Match type hinting approaches if used in the codebase
- Apply the same error handling patterns found in existing code
- Follow the same module organization patterns` : ""}
## Version Control Guidelines
${VERSIONING == "Semantic" ?
`- Follow Semantic Versioning patterns as applied in the codebase
- Match existing patterns for documenting breaking changes
- Follow the same approach for deprecation notices` : ""}
${VERSIONING == "CalVer" ?
`- Follow Calendar Versioning patterns as applied in the codebase
- Match existing patterns for documenting changes
- Follow the same approach for highlighting significant changes` : ""}
${VERSIONING == "Custom" ?
`- Match the exact versioning pattern observed in the codebase
- Follow the same changelog format used in existing documentation
- Apply the same tagging conventions used in the project` : ""}
## General Best Practices
- Follow naming conventions exactly as they appear in existing code
- Match code organization patterns from similar files
- Apply error handling consistent with existing patterns
- Follow the same approach to testing as seen in the codebase
- Match logging patterns from existing code
- Use the same approach to configuration as seen in the codebase
## Project-Specific Guidance
- Scan the codebase thoroughly before generating any code
- Respect existing architectural boundaries without exception
- Match the style and patterns of surrounding code
- When in doubt, prioritize consistency with existing code over external best practices
```
### 2. Codebase Analysis Instructions
To create the copilot-instructions.md file, first analyze the codebase to:
1. **Identify Exact Technology Versions**:
- ${PROJECT_TYPE == "Auto-detect" ? "Detect all programming languages, frameworks, and libraries by scanning file extensions and configuration files" : `Focus on ${PROJECT_TYPE} technologies`}
- Extract precise version information from project files, package.json, .csproj, etc.
- Document version constraints and compatibility requirements
2. **Understand Architecture**:
- Analyze folder structure and module organization
- Identify clear layer boundaries and component relationships
- Document communication patterns between components
3. **Document Code Patterns**:
- Catalog naming conventions for different code elements
- Note documentation styles and completeness
- Document error handling patterns
- Map testing approaches and coverage
4. **Note Quality Standards**:
- Identify performance optimization techniques actually used
- Document security practices implemented in the code
- Note accessibility features present (if applicable)
- Document code quality patterns evident in the codebase
### 3. Implementation Notes
The final copilot-instructions.md should:
- Be placed in the .github directory
- Reference only patterns and standards that exist in the codebase
- Include explicit version compatibility requirements
- Avoid prescribing any practices not evident in the code
- Provide concrete examples from the codebase
- Be comprehensive yet concise enough for Copilot to effectively use
Important: Only include guidance based on patterns actually observed in the codebase. Explicitly instruct Copilot to prioritize consistency with existing code over external best practices or newer language features.
"
## Expected Output
A comprehensive copilot-instructions.md file that will guide GitHub Copilot to produce code that is perfectly compatible with your existing technology versions and follows your established patterns and architecture.
+249
View File
@@ -0,0 +1,249 @@
---
description: "Prompt for generating an AGENTS.md file for a repository"
mode: "agent"
---
# Create highquality AGENTS.md file
You are a code agent. Your task is to create a complete, accurate AGENTS.md at the root of this repository that follows the public guidance at https://agents.md/.
AGENTS.md is an open format designed to provide coding agents with the context and instructions they need to work effectively on a project.
## What is AGENTS.md?
AGENTS.md is a Markdown file that serves as a "README for agents" - a dedicated, predictable place to provide context and instructions to help AI coding agents work on your project. It complements README.md by containing detailed technical context that coding agents need but might clutter a human-focused README.
## Key Principles
- **Agent-focused**: Contains detailed technical instructions for automated tools
- **Complements README.md**: Doesn't replace human documentation but adds agent-specific context
- **Standardized location**: Placed at repository root (or subproject roots for monorepos)
- **Open format**: Uses standard Markdown with flexible structure
- **Ecosystem compatibility**: Works across 20+ different AI coding tools and agents
## File Structure and Content Guidelines
### 1. Required Setup
- Create the file as `AGENTS.md` in the repository root
- Use standard Markdown formatting
- No required fields - flexible structure based on project needs
### 2. Essential Sections to Include
#### Project Overview
- Brief description of what the project does
- Architecture overview if complex
- Key technologies and frameworks used
#### Setup Commands
- Installation instructions
- Environment setup steps
- Dependency management commands
- Database setup if applicable
#### Development Workflow
- How to start development server
- Build commands
- Watch/hot-reload setup
- Package manager specifics (npm, pnpm, yarn, etc.)
#### Testing Instructions
- How to run tests (unit, integration, e2e)
- Test file locations and naming conventions
- Coverage requirements
- Specific test patterns or frameworks used
- How to run subset of tests or focus on specific areas
#### Code Style Guidelines
- Language-specific conventions
- Linting and formatting rules
- File organization patterns
- Naming conventions
- Import/export patterns
#### Build and Deployment
- Build commands and outputs
- Environment configurations
- Deployment steps and requirements
- CI/CD pipeline information
### 3. Optional but Recommended Sections
#### Security Considerations
- Security testing requirements
- Secrets management
- Authentication patterns
- Permission models
#### Monorepo Instructions (if applicable)
- How to work with multiple packages
- Cross-package dependencies
- Selective building/testing
- Package-specific commands
#### Pull Request Guidelines
- Title format requirements
- Required checks before submission
- Review process
- Commit message conventions
#### Debugging and Troubleshooting
- Common issues and solutions
- Logging patterns
- Debug configuration
- Performance considerations
## Example Template
Use this as a starting template and customize based on the specific project:
```markdown
# AGENTS.md
## Project Overview
[Brief description of the project, its purpose, and key technologies]
## Setup Commands
- Install dependencies: `[package manager] install`
- Start development server: `[command]`
- Build for production: `[command]`
## Development Workflow
- [Development server startup instructions]
- [Hot reload/watch mode information]
- [Environment variable setup]
## Testing Instructions
- Run all tests: `[command]`
- Run unit tests: `[command]`
- Run integration tests: `[command]`
- Test coverage: `[command]`
- [Specific testing patterns or requirements]
## Code Style
- [Language and framework conventions]
- [Linting rules and commands]
- [Formatting requirements]
- [File organization patterns]
## Build and Deployment
- [Build process details]
- [Output directories]
- [Environment-specific builds]
- [Deployment commands]
## Pull Request Guidelines
- Title format: [component] Brief description
- Required checks: `[lint command]`, `[test command]`
- [Review requirements]
## Additional Notes
- [Any project-specific context]
- [Common gotchas or troubleshooting tips]
- [Performance considerations]
```
## Working Example from agents.md
Here's a real example from the agents.md website:
```markdown
# Sample AGENTS.md file
## Dev environment tips
- Use `pnpm dlx turbo run where <project_name>` to jump to a package instead of scanning with `ls`.
- Run `pnpm install --filter <project_name>` to add the package to your workspace so Vite, ESLint, and TypeScript can see it.
- Use `pnpm create vite@latest <project_name> -- --template react-ts` to spin up a new React + Vite package with TypeScript checks ready.
- Check the name field inside each package's package.json to confirm the right name—skip the top-level one.
## Testing instructions
- Find the CI plan in the .github/workflows folder.
- Run `pnpm turbo run test --filter <project_name>` to run every check defined for that package.
- From the package root you can just call `pnpm test`. The commit should pass all tests before you merge.
- To focus on one step, add the Vitest pattern: `pnpm vitest run -t "<test name>"`.
- Fix any test or type errors until the whole suite is green.
- After moving files or changing imports, run `pnpm lint --filter <project_name>` to be sure ESLint and TypeScript rules still pass.
- Add or update tests for the code you change, even if nobody asked.
## PR instructions
- Title format: [<project_name>] <Title>
- Always run `pnpm lint` and `pnpm test` before committing.
```
## Implementation Steps
1. **Analyze the project structure** to understand:
- Programming languages and frameworks used
- Package managers and build tools
- Testing frameworks
- Project architecture (monorepo, single package, etc.)
2. **Identify key workflows** by examining:
- package.json scripts
- Makefile or other build files
- CI/CD configuration files
- Documentation files
3. **Create comprehensive sections** covering:
- All essential setup and development commands
- Testing strategies and commands
- Code style and conventions
- Build and deployment processes
4. **Include specific, actionable commands** that agents can execute directly
5. **Test the instructions** by ensuring all commands work as documented
6. **Keep it focused** on what agents need to know, not general project information
## Best Practices
- **Be specific**: Include exact commands, not vague descriptions
- **Use code blocks**: Wrap commands in backticks for clarity
- **Include context**: Explain why certain steps are needed
- **Stay current**: Update as the project evolves
- **Test commands**: Ensure all listed commands actually work
- **Consider nested files**: For monorepos, create AGENTS.md files in subprojects as needed
## Monorepo Considerations
For large monorepos:
- Place a main AGENTS.md at the repository root
- Create additional AGENTS.md files in subproject directories
- The closest AGENTS.md file takes precedence for any given location
- Include navigation tips between packages/projects
## Final Notes
- AGENTS.md works with 20+ AI coding tools including Cursor, Aider, Gemini CLI, and many others
- The format is intentionally flexible - adapt it to your project's needs
- Focus on actionable instructions that help agents understand and work with your codebase
- This is living documentation - update it as your project evolves
When creating the AGENTS.md file, prioritize clarity, completeness, and actionability. The goal is to give any coding agent enough context to effectively contribute to the project without requiring additional human guidance.
@@ -0,0 +1,127 @@
---
mode: 'agent'
description: 'Create a new specification file for the solution, optimized for Generative AI consumption.'
tools: ['changes', 'codebase', 'editFiles', 'extensions', 'fetch', 'githubRepo', 'openSimpleBrowser', 'problems', 'runTasks', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure', 'usages', 'vscodeAPI']
---
# Create Specification
Your goal is to create a new specification file for `${input:SpecPurpose}`.
The specification file must define the requirements, constraints, and interfaces for the solution components in a manner that is clear, unambiguous, and structured for effective use by Generative AIs. Follow established documentation standards and ensure the content is machine-readable and self-contained.
## Best Practices for AI-Ready Specifications
- Use precise, explicit, and unambiguous language.
- Clearly distinguish between requirements, constraints, and recommendations.
- Use structured formatting (headings, lists, tables) for easy parsing.
- Avoid idioms, metaphors, or context-dependent references.
- Define all acronyms and domain-specific terms.
- Include examples and edge cases where applicable.
- Ensure the document is self-contained and does not rely on external context.
The specification should be saved in the [/spec/](/spec/) directory and named according to the following convention: `spec-[a-z0-9-]+.md`, where the name should be descriptive of the specification's content and starting with the highlevel purpose, which is one of [schema, tool, data, infrastructure, process, architecture, or design].
The specification file must be formatted in well formed Markdown.
Specification files must follow the template below, ensuring that all sections are filled out appropriately. The front matter for the markdown should be structured correctly as per the example following:
```md
---
title: [Concise Title Describing the Specification's Focus]
version: [Optional: e.g., 1.0, Date]
date_created: [YYYY-MM-DD]
last_updated: [Optional: YYYY-MM-DD]
owner: [Optional: Team/Individual responsible for this spec]
tags: [Optional: List of relevant tags or categories, e.g., `infrastructure`, `process`, `design`, `app` etc]
---
# Introduction
[A short concise introduction to the specification and the goal it is intended to achieve.]
## 1. Purpose & Scope
[Provide a clear, concise description of the specification's purpose and the scope of its application. State the intended audience and any assumptions.]
## 2. Definitions
[List and define all acronyms, abbreviations, and domain-specific terms used in this specification.]
## 3. Requirements, Constraints & Guidelines
[Explicitly list all requirements, constraints, rules, and guidelines. Use bullet points or tables for clarity.]
- **REQ-001**: Requirement 1
- **SEC-001**: Security Requirement 1
- **[3 LETTERS]-001**: Other Requirement 1
- **CON-001**: Constraint 1
- **GUD-001**: Guideline 1
- **PAT-001**: Pattern to follow 1
## 4. Interfaces & Data Contracts
[Describe the interfaces, APIs, data contracts, or integration points. Use tables or code blocks for schemas and examples.]
## 5. Acceptance Criteria
[Define clear, testable acceptance criteria for each requirement using Given-When-Then format where appropriate.]
- **AC-001**: Given [context], When [action], Then [expected outcome]
- **AC-002**: The system shall [specific behavior] when [condition]
- **AC-003**: [Additional acceptance criteria as needed]
## 6. Test Automation Strategy
[Define the testing approach, frameworks, and automation requirements.]
- **Test Levels**: Unit, Integration, End-to-End
- **Frameworks**: MSTest, FluentAssertions, Moq (for .NET applications)
- **Test Data Management**: [approach for test data creation and cleanup]
- **CI/CD Integration**: [automated testing in GitHub Actions pipelines]
- **Coverage Requirements**: [minimum code coverage thresholds]
- **Performance Testing**: [approach for load and performance testing]
## 7. Rationale & Context
[Explain the reasoning behind the requirements, constraints, and guidelines. Provide context for design decisions.]
## 8. Dependencies & External Integrations
[Define the external systems, services, and architectural dependencies required for this specification. Focus on **what** is needed rather than **how** it's implemented. Avoid specific package or library versions unless they represent architectural constraints.]
### External Systems
- **EXT-001**: [External system name] - [Purpose and integration type]
### Third-Party Services
- **SVC-001**: [Service name] - [Required capabilities and SLA requirements]
### Infrastructure Dependencies
- **INF-001**: [Infrastructure component] - [Requirements and constraints]
### Data Dependencies
- **DAT-001**: [External data source] - [Format, frequency, and access requirements]
### Technology Platform Dependencies
- **PLT-001**: [Platform/runtime requirement] - [Version constraints and rationale]
### Compliance Dependencies
- **COM-001**: [Regulatory or compliance requirement] - [Impact on implementation]
**Note**: This section should focus on architectural and business dependencies, not specific package implementations. For example, specify "OAuth 2.0 authentication library" rather than "Microsoft.AspNetCore.Authentication.JwtBearer v6.0.1".
## 9. Examples & Edge Cases
```code
// Code snippet or data example demonstrating the correct application of the guidelines, including edge cases
```
## 10. Validation Criteria
[List the criteria or tests that must be satisfied for compliance with this specification.]
## 11. Related Specifications / Further Reading
[Link to related spec 1]
[Link to relevant external documentation]
```
@@ -0,0 +1,15 @@
---
mode: 'agent'
description: 'Review and refactor code in your project according to defined instructions'
---
## Role
You're a senior expert software engineer with extensive experience in maintaining projects over a long time and ensuring clean code and best practices.
## Task
1. Take a deep breath, and review all coding guidelines instructions in `.github/instructions/*.md` and `.github/copilot-instructions.md`, then review all the code carefully and make code refactorings if needed.
2. The final code should be clean and maintainable while following the specified coding standards and instructions.
3. Do not split up the code, keep the existing files intact.
4. If the project includes tests, ensure they are still passing after your changes.
@@ -0,0 +1,127 @@
---
mode: 'agent'
description: 'Update an existing specification file for the solution, optimized for Generative AI consumption based on new requirements or updates to any existing code.'
tools: ['changes', 'codebase', 'editFiles', 'extensions', 'fetch', 'githubRepo', 'openSimpleBrowser', 'problems', 'runTasks', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure', 'usages', 'vscodeAPI']
---
# Update Specification
Your goal is to update the existing specification file `${file}` based on new requirements or updates to any existing code.
The specification file must define the requirements, constraints, and interfaces for the solution components in a manner that is clear, unambiguous, and structured for effective use by Generative AIs. Follow established documentation standards and ensure the content is machine-readable and self-contained.
## Best Practices for AI-Ready Specifications
- Use precise, explicit, and unambiguous language.
- Clearly distinguish between requirements, constraints, and recommendations.
- Use structured formatting (headings, lists, tables) for easy parsing.
- Avoid idioms, metaphors, or context-dependent references.
- Define all acronyms and domain-specific terms.
- Include examples and edge cases where applicable.
- Ensure the document is self-contained and does not rely on external context.
The specification should be saved in the [/spec/](/spec/) directory and named according to the following convention: `[a-z0-9-]+.md`, where the name should be descriptive of the specification's content and starting with the highlevel purpose, which is one of [schema, tool, data, infrastructure, process, architecture, or design].
The specification file must be formatted in well formed Markdown.
Specification files must follow the template below, ensuring that all sections are filled out appropriately. The front matter for the markdown should be structured correctly as per the example following:
```md
---
title: [Concise Title Describing the Specification's Focus]
version: [Optional: e.g., 1.0, Date]
date_created: [YYYY-MM-DD]
last_updated: [Optional: YYYY-MM-DD]
owner: [Optional: Team/Individual responsible for this spec]
tags: [Optional: List of relevant tags or categories, e.g., `infrastructure`, `process`, `design`, `app` etc]
---
# Introduction
[A short concise introduction to the specification and the goal it is intended to achieve.]
## 1. Purpose & Scope
[Provide a clear, concise description of the specification's purpose and the scope of its application. State the intended audience and any assumptions.]
## 2. Definitions
[List and define all acronyms, abbreviations, and domain-specific terms used in this specification.]
## 3. Requirements, Constraints & Guidelines
[Explicitly list all requirements, constraints, rules, and guidelines. Use bullet points or tables for clarity.]
- **REQ-001**: Requirement 1
- **SEC-001**: Security Requirement 1
- **[3 LETTERS]-001**: Other Requirement 1
- **CON-001**: Constraint 1
- **GUD-001**: Guideline 1
- **PAT-001**: Pattern to follow 1
## 4. Interfaces & Data Contracts
[Describe the interfaces, APIs, data contracts, or integration points. Use tables or code blocks for schemas and examples.]
## 5. Acceptance Criteria
[Define clear, testable acceptance criteria for each requirement using Given-When-Then format where appropriate.]
- **AC-001**: Given [context], When [action], Then [expected outcome]
- **AC-002**: The system shall [specific behavior] when [condition]
- **AC-003**: [Additional acceptance criteria as needed]
## 6. Test Automation Strategy
[Define the testing approach, frameworks, and automation requirements.]
- **Test Levels**: Unit, Integration, End-to-End
- **Frameworks**: MSTest, FluentAssertions, Moq (for .NET applications)
- **Test Data Management**: [approach for test data creation and cleanup]
- **CI/CD Integration**: [automated testing in GitHub Actions pipelines]
- **Coverage Requirements**: [minimum code coverage thresholds]
- **Performance Testing**: [approach for load and performance testing]
## 7. Rationale & Context
[Explain the reasoning behind the requirements, constraints, and guidelines. Provide context for design decisions.]
## 8. Dependencies & External Integrations
[Define the external systems, services, and architectural dependencies required for this specification. Focus on **what** is needed rather than **how** it's implemented. Avoid specific package or library versions unless they represent architectural constraints.]
### External Systems
- **EXT-001**: [External system name] - [Purpose and integration type]
### Third-Party Services
- **SVC-001**: [Service name] - [Required capabilities and SLA requirements]
### Infrastructure Dependencies
- **INF-001**: [Infrastructure component] - [Requirements and constraints]
### Data Dependencies
- **DAT-001**: [External data source] - [Format, frequency, and access requirements]
### Technology Platform Dependencies
- **PLT-001**: [Platform/runtime requirement] - [Version constraints and rationale]
### Compliance Dependencies
- **COM-001**: [Regulatory or compliance requirement] - [Impact on implementation]
**Note**: This section should focus on architectural and business dependencies, not specific package implementations. For example, specify "OAuth 2.0 authentication library" rather than "Microsoft.AspNetCore.Authentication.JwtBearer v6.0.1".
## 9. Examples & Edge Cases
```code
// Code snippet or data example demonstrating the correct application of the guidelines, including edge cases
```
## 10. Validation Criteria
[List the criteria or tests that must be satisfied for compliance with this specification.]
## 11. Related Specifications / Further Reading
[Link to related spec 1]
[Link to relevant external documentation]
```
+232 -100
View File
@@ -1,108 +1,240 @@
name: ci
name: CI
on:
pull_request:
push:
branches-ignore:
- deps/*
tags:
- '**'
tags:
- '[0-9]+.[0-9]+.[0-9]+'
branches:
- dev
- master
pull_request:
permissions:
contents: write
pull-requests: write
contents: write
pull-requests: write
id-token: write
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
registry-url: https://registry.npmjs.org/
cache: npm
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Test
run: npm run test-with-coverage
- name: Lint
run: npm run eslint
- name: Docker login
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
run: echo ${{ secrets.DOCKER_KEY }} | docker login -u koenkk --password-stdin
- name: Docker login ghcr.io
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
run: echo ${{ secrets.GH_TOKEN }} | 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@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@v2
with:
version: latest
- name: Docker build dev
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
docker buildx build \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--platform linux/arm64/v8,linux/386,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--provenance=false \
--push \
-t koenkk/zigbee2mqtt:latest-dev -t ghcr.io/koenkk/zigbee2mqtt:latest-dev \
.
- name: Docker build release
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: |
TAG="$(git describe --tags)"
docker buildx build \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--platform linux/arm64/v8,linux/386,linux/amd64,linux/arm/v6,linux/arm/v7 \
-f docker/Dockerfile \
--provenance=false \
--push \
-t koenkk/zigbee2mqtt:latest -t "koenkk/zigbee2mqtt:$TAG" -t ghcr.io/koenkk/zigbee2mqtt:latest -t "ghcr.io/koenkk/zigbee2mqtt:$TAG" \
.
- name: Publish to npm
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN }}
- name: Trigger zigbee2mqtt/hassio-zigbee2mqtt build
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
curl \
-X POST \
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/zigbee2mqtt/hassio-zigbee2mqtt/actions/workflows/ci.yml/dispatches \
-d '{"ref":"master","inputs":{}}'
ci:
runs-on: ubuntu-latest
permissions:
id-token: write # for npm publish
actions: read # for Nerivec/action-ci-bench
pull-requests: write # for Nerivec/action-ci-bench
steps:
- uses: actions/checkout@v6
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
with:
# Required for `release: merge dev -> master and promote dev`
token: ${{ secrets.GH_TOKEN }}
tests:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [16, 18, 20]
runs-on: ${{ matrix.os }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
registry-url: https://registry.npmjs.org/
cache: 'npm'
- name: Install dependencies
# --ignore-scripts prevents the serialport build which often fails on Windows
run: npm ci --ignore-scripts
- name: Build
run: npm run build
- name: Test
run: npm run test-with-coverage
- uses: actions/checkout@v6
if: ((github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push') == false
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: 24
registry-url: https://registry.npmjs.org/
cache: pnpm
- name: Install dependencies
run: pnpm i --frozen-lockfile
- name: Check
run: pnpm run check
- name: Build
run: pnpm run build
- name: Test
run: pnpm run test:coverage
- name: Bench
if: github.ref == 'refs/heads/dev' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-please-'))
uses: Nerivec/action-ci-bench@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
compare-against: dev
base-cmd: npx vitest bench --run --config ./test/vitest.config.mts --outputJson bench.json
compare-cmd: npx vitest bench --run --config ./test/vitest.config.mts --compare bench.json
base-result: bench.json
- name: Log in to the Docker container registry
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
uses: docker/login-action@v4
with:
username: koenkk
password: ${{ secrets.DOCKER_KEY }}
- name: Log in to the GitHub container registry
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: koenkk
password: ${{ secrets.GH_TOKEN }}
- name: Docker setup - QEMU
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
uses: docker/setup-qemu-action@v4
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@v4
with:
version: latest
- name: dev - Docker build and push
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile
platforms: linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/riscv64,linux/386
tags: koenkk/zigbee2mqtt:latest-dev,ghcr.io/koenkk/zigbee2mqtt:latest-dev
push: true
build-args: |
COMMIT=${{ github.sha }}
VERSION=dev
DATE=${{ github.event.repository.updated_at }}
- name: dev - Publish types
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
pnpm run build:types
temp_dir=$(mktemp -d)
cp -R package.json LICENSE dist $temp_dir
git fetch --unshallow
git checkout dev-types
rm -rf *
cp -R $temp_dir/* .
# remove unnecessary dependencies
KEEP_DEPS='["zigbee-herdsman","zigbee-herdsman-converters"]'
tmp_pkg=$(mktemp)
jq --indent 4 --argjson keep "$KEEP_DEPS" 'def filterDeps: (. // {}) | with_entries(select(.key as $k | $keep | index($k))); .dependencies |= filterDeps | .devDependencies |= filterDeps | .optionalDependencies |= filterDeps' package.json > "$tmp_pkg"
mv "$tmp_pkg" package.json
git add -A
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "$GITHUB_SHA" || echo 'Nothing to commit'
git push --force --set-upstream origin dev-types
- name: release - Docker meta
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
uses: docker/metadata-action@v6
id: meta
with:
images: |
koenkk/zigbee2mqtt
ghcr.io/koenkk/zigbee2mqtt
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: release - Docker build and push
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile
platforms: linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/riscv64,linux/386
tags: ${{ steps.meta.outputs.tags }}
push: true
build-args: |
COMMIT=${{ github.sha }}
VERSION=${{ github.ref_name }}
DATE=${{ github.event.repository.updated_at }}
- name: 'release: Publish to npm'
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: pnpm publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true
- name: 'dev: Trigger zigbee2mqtt/hassio-zigbee2mqtt build'
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
run: |
curl \
-X POST \
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/zigbee2mqtt/hassio-zigbee2mqtt/actions/workflows/ci.yml/dispatches \
-d '{"ref":"master","inputs":{}}'
- name: 'release: Trigger zigbee2mqtt/hassio-zigbee2mqtt build'
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: |
TAG=${GITHUB_REF#refs/*/}
echo "Triggering with tag '$TAG'"
curl \
-X POST \
-H "Authorization: token ${{ secrets.GH_TOKEN }}" \
-H "Accept: application/vnd.github.everest-preview+json" \
-H "Content-Type: application/json" \
https://api.github.com/repos/zigbee2mqtt/hassio-zigbee2mqtt/dispatches \
--data "{\"event_type\": \"release\", \"client_payload\": { \"version\": \"$TAG-1\"}}"
- name: 'release: Trigger zigbee2mqtt-chart image update'
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: |
TAG=${GITHUB_REF#refs/*/}
echo "Triggering with tag '$TAG'"
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/Koenkk/zigbee2mqtt-chart/actions/workflows/on_zigbee2mqtt_release.yaml/dispatches \
--data "{\"ref\": \"main\", \"inputs\": { \"zigbee2mqtt_version\": \"$TAG\"}}"
- name: 'release: merge dev -> master and promote dev'
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
run: |
TAG=${GITHUB_REF#refs/*/}
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git fetch --unshallow
git fetch origin
git checkout master
git merge --ff-only origin/dev
git push origin master
git checkout dev
jq --indent 4 ".version = \"$TAG-dev\"" package.json > package.json.tmp
mv package.json.tmp package.json
git add -A
git commit -m "chore: promote to dev"
git push origin dev
tests:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [20, 22, 24]
runs-on: ${{ matrix.os }}
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Install dependencies
# --ignore-scripts prevents build on Windows (only for unix-dgram, so doesn't matter, others have pre-builds)
run: pnpm i --frozen-lockfile ${{ matrix.os == 'windows-latest' && '--ignore-scripts' || '' }}
- name: Build
run: pnpm run build
- name: Test
run: pnpm run test:coverage
-23
View File
@@ -1,23 +0,0 @@
name: codeql
on:
push:
branches:
- dev
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+15
View File
@@ -0,0 +1,15 @@
name: Dependency review
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout repository'
uses: actions/checkout@v6
- name: 'Dependency review'
uses: actions/dependency-review-action@v5
-14
View File
@@ -1,14 +0,0 @@
name: deps-review
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout repository'
uses: actions/checkout@v3
- name: 'Dependency review'
uses: actions/dependency-review-action@v3
+19
View File
@@ -0,0 +1,19 @@
name: Fail PR to master
on:
pull_request:
branches:
- master
jobs:
fail-pr-to-master:
runs-on: ubuntu-latest
steps:
- name: Fail PR to master
run: |
if [[ "${{ github.event.pull_request.title }}" == "chore(dev): release"* ]]; then
echo "PR title starts with 'chore(dev): release', allowing PR"
else
echo "Pull requests to the master branch are not allowed, target dev branch"
exit 1
fi
+34
View File
@@ -0,0 +1,34 @@
on:
workflow_dispatch:
name: GHCR cleanup
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Delete untagged images
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GH_TOKEN }}
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
-34
View File
@@ -1,34 +0,0 @@
on:
workflow_dispatch:
name: ghcr-cleanup
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Delete untagged images
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GH_TOKEN }}
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
+38
View File
@@ -0,0 +1,38 @@
name: Issue Bot
on:
issues:
types: [opened, edited]
permissions:
issues: write
contents: read
jobs:
comment:
runs-on: ubuntu-latest
if: |
contains(github.event.issue.labels.*.name, 'new device support') ||
contains(github.event.issue.labels.*.name, 'external converter') ||
startsWith(github.event.issue.title, '[New device support]') ||
startsWith(github.event.issue.title, '[External Converter]')
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: |
scripts
path: z2m
- uses: actions/checkout@v6
with:
repository: Koenkk/zigbee-herdsman-converters
ref: master
fetch-depth: 1
path: zhc
- name: Comment on new device support/external converter issue
uses: actions/github-script@v9
with:
script: |
const {newDeviceSupport} = await import("${{ github.workspace }}/z2m/scripts/issueBot.mjs")
await newDeviceSupport(github, core, context, "${{ github.workspace }}/zhc")
+19
View File
@@ -0,0 +1,19 @@
name: Merge master to dev
on:
push:
branches:
- master
jobs:
merge-master-to-dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: devmasx/merge-branch@master
with:
type: now
head_to_merge: master
target_branch: dev
message: 'chore: merge master to dev'
github_token: ${{ secrets.GH_TOKEN }}
-19
View File
@@ -1,19 +0,0 @@
name: merge-master-to-dev
on:
push:
branches:
- master
jobs:
merge_master_to_dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: devmasx/merge-branch@master
with:
type: now
head_to_merge: master
target_branch: dev
message: "chore: merge master to dev"
github_token: ${{ secrets.GH_TOKEN }}
+74
View File
@@ -0,0 +1,74 @@
on:
push:
branches:
- dev
permissions:
contents: write
pull-requests: write
name: Release Please
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
version: '${{steps.release.outputs.major}}.${{steps.release.outputs.minor}}.${{steps.release.outputs.patch}}'
steps:
- uses: pnpm/action-setup@v6
with:
version: 9
- uses: actions/setup-node@v6
with:
node-version: 24
- uses: googleapis/release-please-action@v5
id: release
with:
target-branch: dev
token: ${{secrets.GH_TOKEN}}
# Checkout repos
- uses: actions/checkout@v6
with:
repository: koenkk/zigbee2mqtt
path: ./z2m
- uses: actions/checkout@v6
with:
repository: koenkk/zigbee2mqtt
path: ./z2m-master
ref: master
- name: Restore cache commit-user-lookup.json
uses: actions/cache/restore@v5
with:
path: z2m/scripts/commit-user-lookup.json
key: commit-user-lookup-dummy
restore-keys: |
commit-user-lookup-
- name: Generate changelog
run: |
MASTER_Z2M_VERSION=$(cat z2m-master/package.json | jq -r '.version')
MASTER_ZHC_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee-herdsman-converters"')
MASTER_ZH_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee-herdsman"')
MASTER_FRONTEND_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee2mqtt-frontend"')
MASTER_WINDFRONT_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee2mqtt-windfront"')
wget -q -O - https://raw.githubusercontent.com/Koenkk/zigbee2mqtt/release-please--branches--dev--components--zigbee2mqtt/CHANGELOG.md > z2m/CHANGELOG.md
cd z2m
pnpm i --frozen-lockfile
node scripts/generateChangelog.mjs $MASTER_Z2M_VERSION $MASTER_ZHC_VERSION $MASTER_ZH_VERSION $MASTER_FRONTEND_VERSION $MASTER_WINDFRONT_VERSION >> ../changelog.md
env:
GH_TOKEN: ${{secrets.GH_TOKEN}}
- name: Update changelog gist
run: |
gh gist edit bfd4c3d1725a2cccacc11d6ba51008ba -a changelog.md
env:
GH_TOKEN: ${{secrets.GH_TOKEN}}
- name: Save cache commit-user-lookup.json
uses: actions/cache/save@v5
if: always()
with:
path: z2m/scripts/commit-user-lookup.json
key: commit-user-lookup-${{ hashFiles('z2m/scripts/commit-user-lookup.json') }}
-112
View File
@@ -1,112 +0,0 @@
on:
push:
branches:
- dev
permissions:
contents: write
pull-requests: write
name: release-please
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
version: "${{steps.release.outputs.major}}.${{steps.release.outputs.minor}}.${{steps.release.outputs.patch}}"
steps:
- uses: actions/setup-node@v3
with:
node-version: 20
- uses: google-github-actions/release-please-action@v3
id: release
with:
release-type: node
package-name: release-please-action
default-branch: dev
token: ${{secrets.GH_TOKEN}}
# Checkout repos
- uses: actions/checkout@v3
with:
repository: koenkk/zigbee2mqtt
path: ./z2m
- uses: actions/checkout@v3
with:
repository: koenkk/zigbee2mqtt
path: ./z2m-master
ref: master
- uses: actions/checkout@v3
with:
repository: koenkk/zigbee2mqtt
path: ./z2m-changelog
ref: release-please--branches--dev--components--release-please-action
- name: Update latest-dev tag
run: |
cd z2m
git push --delete origin latest-dev
git tag latest-dev
git push origin latest-dev
- name: Update latest-dev release changelog
run: |
PR=$(echo '${{ steps.release.outputs.pr }}' | jq -r '.number')
MASTER_Z2M_VERSION=$(cat z2m-master/package.json | jq -r '.version')
MASTER_ZHC_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee-herdsman-converters"')
MASTER_ZH_VERSION=$(cat z2m-master/package.json | jq -r '.dependencies."zigbee-herdsman"')
cp z2m-changelog/CHANGELOG.md z2m
cd z2m
npm ci
node scripts/generateChangelog.js $MASTER_Z2M_VERSION $MASTER_ZHC_VERSION $MASTER_ZH_VERSION >> ../changelog.txt
env:
GH_TOKEN: ${{secrets.GH_TOKEN}}
- uses: ncipollo/release-action@v1
with:
bodyFile: changelog.txt
prerelease: true
name: latest-dev
allowUpdates: true
tag: latest-dev
- name: Upload changelog
uses: actions/upload-artifact@v3
with:
name: changelog
path: changelog.txt
create_release:
runs-on: ubuntu-latest
needs: [release-please]
if: ${{ needs.release-please.outputs.release_created }}
steps:
- uses: actions/checkout@v3
with:
ref: dev
token: ${{secrets.GH_TOKEN}}
- name: Download changelog
uses: actions/download-artifact@v3
with:
name: changelog
path: ../changelog.txt
- uses: ncipollo/release-action@v1
name: Create release
with:
bodyFile: ../changelog.txt
draft: true
name: ${{ needs.release-please.outputs.version }}
tag: ${{ needs.release-please.outputs.version }}
- name: Merge dev -> master
uses: devmasx/merge-branch@master
with:
type: now
from_branch: dev
target_branch: master
github_token: ${{secrets.GH_TOKEN}}
- name: Promote to dev
run: |
jq '.version = "${{ needs.release-please.outputs.version }}-dev"' package.json > package.json
jq '.version = "${{ needs.release-please.outputs.version }}-dev"' package-lock.json > package-lock.json
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Promote to dev
+16 -16
View File
@@ -1,20 +1,20 @@
name: stale
name: Stale
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days'
stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days'
days-before-stale: 30
days-before-close: 7
exempt-issue-labels: dont-stale
operations-per-run: 500
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
stale-pr-message: 'This pull request is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
days-before-stale: 60
days-before-close: 7
exempt-issue-labels: dont-stale,feature-request
operations-per-run: 500
+39
View File
@@ -0,0 +1,39 @@
on:
repository_dispatch:
types: update_dep
name: Update dependency
permissions: {}
jobs:
update-dependency:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: dev
token: ${{ secrets.GH_TOKEN }}
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: |
pnpm install ${{ github.event.client_payload.package }}@${{ github.event.client_payload.version }} --save-exact
pnpm install --no-frozen-lockfile
- uses: peter-evans/create-pull-request@v8
id: cpr
with:
commit-message: 'fix(ignore): update ${{ github.event.client_payload.package }} to ${{ github.event.client_payload.version }}'
branch: 'deps/${{ github.event.client_payload.package }}'
title: 'fix(ignore): update ${{ github.event.client_payload.package }} to ${{ github.event.client_payload.version }}'
token: ${{ secrets.GH_TOKEN }}
- run: sleep 5 # Otherwise pull request may not exist yet causing automerge to fail
- run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
if: steps.cpr.outputs.pull-request-operation == 'created'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
-38
View File
@@ -1,38 +0,0 @@
on:
repository_dispatch:
types: update_dep
name: update-dep
permissions: {}
jobs:
update_dep:
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: dev
token: ${{ secrets.GH_TOKEN }}
- uses: actions/setup-node@v3
with:
node-version: 20
cache: npm
- run: npm install ${{ github.event.client_payload.package }}@${{ github.event.client_payload.version }} --save-exact
- uses: peter-evans/create-pull-request@v5
id: cpr
with:
commit-message: "fix(ignore): update ${{ github.event.client_payload.package }} to ${{ github.event.client_payload.version }}"
branch: "deps/${{ github.event.client_payload.package }}"
title: Update ${{ github.event.client_payload.package }} to ${{ github.event.client_payload.version }}
token: ${{ secrets.GH_TOKEN }}
- run: sleep 5 # Otherwise pull request may not exist yet causing automerge to fail
- uses: peter-evans/enable-pull-request-automerge@v3
if: steps.cpr.outputs.pull-request-operation == 'created'
with:
token: ${{ secrets.GH_TOKEN }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash
-31
View File
@@ -1,31 +0,0 @@
name: Update dependencies
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 0"
permissions:
contents: read
jobs:
update_deps:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: dev
token: ${{ secrets.GH_TOKEN }}
- uses: actions/setup-node@v3
with:
node-version: 20
cache: npm
- run: npx npm-check-updates -u -x connect-gzip-static # connect-gzip-static only supports node 16
- run: npm install
- uses: peter-evans/create-pull-request@v5
with:
commit-message: "fix(ignore): update dependencies"
branch: "deps/all"
title: Update dependencies
+14 -13
View File
@@ -45,9 +45,16 @@ typings/
# Optional npm cache directory
.npm
# Optional pnpm cache directory
.pnpm-store
# Optional eslint cache
.eslintcache
# Build cache
tsconfig.tsbuildinfo
tsconfig.types.tsbuildinfo
# Optional REPL history
.node_repl_history
@@ -60,23 +67,17 @@ typings/
# dotenv environment variables file
.env
# data
data/database.db
data/config.json
data/log*.txt
data/state.json
data/log
data-backup/
data/coordinator_backup.json
data/.storage
data/database.db.backup
data/extension
# MacOS indexing file
.DS_Store
# IDE specific folders
.idea
.*.swp
# Ignore config
data/*.yaml
data/*
!data/configuration.example.yaml
data-bench
# commit-user-lookup.json
scripts/commit-user-lookup.json
+1
View File
@@ -8,6 +8,7 @@ coverage
.codeclimate.yml
.github
babel.config.js
tsconfig.tsbuildinfo
#linters
.jscsrc
+2 -3
View File
@@ -1,4 +1,3 @@
{
".": "1.31.0",
"bootstrap-sha": "b2dd21e164b457d5cd0c9d455cdb879d881c44bd"
}
".": "2.12.0"
}
+386
View File
@@ -0,0 +1,386 @@
# AGENTS.md
## Project Overview
Zigbee2MQTT is a Zigbee to MQTT bridge that allows you to use your Zigbee devices without the vendor's bridge or gateway. It bridges events and allows you to control Zigbee devices via MQTT, integrating them with any smart home infrastructure.
### Architecture
- **Language**: TypeScript 5.9.3 compiled to JavaScript (ES modules with NodeNext resolution)
- **Runtime**: Node.js (versions 20, 22, or 24)
- **Package Manager**: pnpm 10.12.1 (strictly enforced via `packageManager` field)
- **Core Dependencies**:
- `zigbee-herdsman` (6.2.0 - exact version, handles Zigbee adapter communication)
- `zigbee-herdsman-converters` (25.42.0 - exact version, device definitions)
- `mqtt` (5.14.1 - MQTT client)
- `winston` (3.18.3 - logging)
### Project Structure
```
lib/ # TypeScript source code
├── controller.ts # Main controller orchestrating components
├── mqtt.ts # MQTT client management
├── zigbee.ts # Zigbee network management
├── state.ts # State management
├── eventBus.ts # Event-driven communication
├── extension/ # Extension system (plugins)
│ └── extension.ts # Abstract base class
├── model/ # Domain models (Device, Group)
├── util/ # Utility functions
└── types/ # TypeScript type definitions
test/ # Vitest test files with mocks
data/ # Runtime configuration and database
dist/ # Compiled JavaScript output
```
## Setup Commands
### Prerequisites
- Node.js version 20, 22, or 24
- pnpm 10.12.1 (will be auto-installed via corepack if not present)
### Installation
```bash
# Install dependencies (uses pnpm lockfile)
pnpm install --frozen-lockfile
# For development without lockfile restrictions
pnpm install
```
### Initial Build
```bash
# Full build (TypeScript compilation + hash generation)
pnpm run build
# Build type definitions only
pnpm run build:types
```
## Development Workflow
### Starting Development
```bash
# Watch mode - recompile on file changes
pnpm run build:watch
# In another terminal, start Zigbee2MQTT
pnpm start
```
### Code Quality Checks
```bash
# Run Biome linter and formatter (check only)
pnpm run check
# Auto-fix linting and formatting issues
pnpm run check:w
# The check runs with --error-on-warnings flag
# Configuration: biome.json (4-space indent, 150 line width, no bracket spacing)
```
### Clean Build
```bash
# Remove build artifacts
pnpm run clean
# Removes: coverage/, dist/, tsconfig.tsbuildinfo
```
## Testing Instructions
### Running Tests
```bash
# Run all tests once
pnpm test
# Run tests with coverage report
pnpm run test:coverage
# Watch mode - re-run tests on changes
pnpm run test:watch
# Run benchmarks
pnpm run bench
```
### Test Requirements
- **Coverage**: 100% code coverage is enforced (configured in `test/vitest.config.mts`)
- **Framework**: Vitest 3.1.1 with @vitest/coverage-v8
- **Test Files**: Located in `test/` directory with `.test.ts` extension
- **Mocks**: Centralized in `test/mocks/` directory
- **Coverage Report**: Generated in `coverage/` directory (HTML report at `coverage/index.html`)
### Running Specific Tests
```bash
# Run tests matching a pattern
pnpm vitest run -t "test name pattern" --config ./test/vitest.config.mts
# Run specific test file
pnpm vitest run test/controller.test.ts --config ./test/vitest.config.mts
# Focus on one test area in watch mode
pnpm vitest watch -t "Extension" --config ./test/vitest.config.mts
```
## Code Style Guidelines
### TypeScript Conventions
- **Module System**: ES modules with NodeNext resolution
- **Target**: ESNext
- **Strict Mode**: Enabled (`noImplicitAny`, `noImplicitThis`)
- **Decorators**: Experimental decorators enabled (used for `@bind` from `bind-decorator`)
### Import Order
1. Node.js built-in modules (with `node:` prefix)
2. Third-party libraries
3. Type-only imports from external packages (using `type` keyword)
4. Internal absolute imports
5. Type-only imports from internal modules
Example:
```typescript
import fs from "node:fs";
import bind from "bind-decorator";
import type {IClientOptions} from "mqtt";
import {connectAsync} from "mqtt";
import type {Zigbee2MQTTAPI} from "./types/api";
import logger from "./util/logger";
```
### Naming Conventions
- **Classes**: PascalCase (e.g., `Extension`, `Device`)
- **Functions/Methods**: camelCase (e.g., `publishEntityState`)
- **Constants**: SCREAMING_SNAKE_CASE (e.g., `CURRENT_VERSION`)
- **Interfaces/Types**: PascalCase (e.g., `MqttPublishOptions`)
- **Files**: camelCase for TypeScript (e.g., `eventBus.ts`)
### Code Patterns
- **Async/Await**: Always use async/await, explicitly type return as `Promise<Type>`
- **Error Handling**: Use `throw new Error("message")`, log with winston logger
- **Event Handlers**: Use `@bind` decorator to preserve `this` context
- **Logging**: Use `logger.info()`, `logger.warning()`, `logger.error()`, `logger.debug()`
### Formatting Rules (Biome)
- 4-space indentation
- 150 character line width
- No bracket spacing in objects
- Run `pnpm run check:w` to auto-format
## Build and Deployment
### Build Process
```bash
# Production build
pnpm run build
# Outputs:
# - Compiled JavaScript in dist/
# - Type definitions in dist/types/
# - Includes hash generation for version tracking
```
### Pre-publish
```bash
# Automatically runs before publishing
pnpm run prepack
# Performs: clean + build
```
### Environment Setup
- Configuration stored in `data/configuration.yaml`
- Database in `data/database.db`
- Logs in `data/log/`
- External extensions in `data/external_extensions/`
- External converters in `data/external_converters/`
## Architecture Patterns
### Extension System
All features are implemented as extensions that inherit from the abstract `Extension` base class:
```typescript
abstract class Extension {
protected zigbee: Zigbee;
protected mqtt: Mqtt;
protected state: State;
protected publishEntityState: PublishEntityState;
protected eventBus: EventBus;
async start(): Promise<void> {} // Initialize extension
async stop(): Promise<void> {} // Cleanup extension
}
```
**Key Points**:
- Constructor should only assign properties (no side effects)
- Initialization happens in `start()` method
- Use EventBus for inter-component communication
- Extensions are loaded and managed by the Controller
### Event-Driven Communication
Components communicate via the strongly-typed EventBus:
```typescript
// Emit events
this.eventBus.emit('deviceMessage', {device, message});
// Listen to events
this.eventBus.on('deviceMessage', this.onDeviceMessage, this);
```
### Dependency Injection
The Controller instantiates and injects dependencies into all extensions. Follow this pattern when creating new extensions.
## Pull Request Guidelines
### Target Branch
- **Always create PRs against the `dev` branch**
- The `master` branch is for production releases only
### Before Submitting
```bash
# Run all checks
pnpm run check
pnpm test
# Ensure 100% code coverage
pnpm run test:coverage
# Build successfully
pnpm run build
```
### PR Requirements
- All CI checks must pass (linting, tests, build)
- 100% test coverage maintained
- Code follows Biome formatting rules
- Commit messages should be descriptive
- Reference related issues when applicable
### CI Pipeline
The GitHub Actions CI workflow (`.github/workflows/ci.yml`) runs:
1. Biome code quality checks (`pnpm run check`)
2. TypeScript compilation (`pnpm run build`)
3. Full test suite with coverage (`pnpm run test:coverage`)
4. Benchmarks (on dev branch and PRs)
5. Docker image builds (on dev branch and tags)
## Working with Device Support
### Adding New Devices
**Important**: Device support is NOT added to this repository. All device definitions live in `zigbee-herdsman-converters`.
- Follow the guide at: https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html
- No changes to zigbee2mqtt codebase are needed for new devices
- Device definitions are automatically picked up from `zigbee-herdsman-converters`
## Debugging and Troubleshooting
### Development Setup
For the easiest development experience, set up a bare-metal installation following:
https://www.zigbee2mqtt.io/guide/installation/01_linux.html
### Logging
- Winston logger is initialized in `lib/util/logger.ts`
- Log levels: `error`, `warning`, `info`, `debug`
- Logs are written to console and/or file based on configuration
- Use structured logging with context (device names, IEEE addresses)
### Common Issues
1. **Import errors after file moves**: Run `pnpm run check` to verify TypeScript and ESLint
2. **Test failures**: Check if mocks in `test/mocks/` need updates
3. **Build errors**: Ensure Node.js version is 20, 22, or 24
4. **Coverage issues**: View HTML report at `coverage/index.html` to identify uncovered code
### Performance Considerations
- Use `rimrafSync` for synchronous file operations
- Leverage async/await to avoid blocking
- Cache computed values in getters when appropriate
- EventBus provides loose coupling between components
## Critical Version Requirements
### Exact Versions
These dependencies use **exact versions** (no semver ranges) - do not upgrade without thorough testing:
- `zigbee-herdsman@6.2.0` - Critical for Zigbee protocol compatibility
- `zigbee-herdsman-converters@25.42.0` - Device definitions must match herdsman version
### Node.js Compatibility
Only these Node.js versions are supported:
- Node.js 20.x
- Node.js 22.x
- Node.js 24.x
Using other versions may cause runtime errors or incompatibilities.
## Additional Notes
### Package Manager
This project **requires pnpm 10.12.1**. The `packageManager` field in package.json enforces this via Corepack.
Do not use npm or yarn - they will not respect the pnpm-specific configuration.
### TypeScript Compilation
- Source files in `lib/` are compiled to `dist/`
- Type definitions exported from `dist/types/api.d.ts`
- Source maps are inlined for debugging
- Uses composite project references for faster incremental builds
### External Extensions
To load external extensions:
1. Place JavaScript files in `data/external_extensions/`
2. They will be automatically loaded on startup
3. No configuration changes needed
### Code Quality Tools
- **Linting/Formatting**: Biome 2.2.5 (replaces ESLint + Prettier)
- **Type Checking**: TypeScript 5.9.3
- **Testing**: Vitest 3.1.1
- **Coverage**: @vitest/coverage-v8
### Documentation
- Main documentation: https://www.zigbee2mqtt.io/
- Contributing guide: `CONTRIBUTING.md`
- Coding standards: `.github/copilot-instructions.md`
- Issue tracker: https://github.com/Koenkk/zigbee2mqtt/issues
+1925 -16
View File
File diff suppressed because it is too large Load Diff
+11 -12
View File
@@ -1,4 +1,3 @@
# Contributor Covenant Code of Conduct
## Our Pledge
@@ -18,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +105,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
+10 -4
View File
@@ -1,8 +1,14 @@
# Contributing to Zigbee2MQTT
Everybody is invited and welcome to contribute to Zigbee2MQTT. Zigbee2MQTT is writen in JavaScript and is based upon [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) and [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters). Zigbee-herdsman-converters contains all device definition, zigbee-herdsman is responsible for handling all communication with the adapter.
> [!WARNING]
> Any AI-driven pull request with more than 500 lines of code will not be considered. If wanting to submit something that requires more, split the work into easily review-able pull requests that can be introduced in increments (e.g. pre-refactor, base feature, additional features).
Everybody is invited and welcomed to contribute to Zigbee2MQTT.
Zigbee2MQTT is written in TypeScript.
It uses [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) for communication with the adapter/coordinator and [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters) to provide device-specific definitions.
- 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 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).
- Easiest way to start developing Zigbee2MQTT is by setting up a development environment (a.k.a. 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 `pnpm test`. Zigbee2MQTT enforces 100% code coverage, in case you add new code check if your code is covered by running `pnpm run test:coverage`. The coverage report can be found under `coverage/lcov-report/index.html`.
- Linting & formatting is also enforced and can be run with `pnpm run check` (can use `pnpm run check:w` to fix small issues automatically).
- If you want to add support for a new device no change to Zigbee2MQTT has 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).
+64 -32
View File
@@ -5,33 +5,27 @@
<br>
<br>
<div style="display: flex;">
<a href="https://github.com/Koenkk/zigbee2mqtt/actions?query=workflow%3ACI">
<img src="https://github.com/koenkk/zigbee2mqtt/workflows/CI/badge.svg">
</a>
<a href="https://github.com/Koenkk/zigbee2mqtt/releases">
<img src="https://img.shields.io/github/release/koenkk/zigbee2mqtt.svg">
</a>
<a href="https://www.npmjs.com/package/zigbee2mqtt">
<img src="https://img.shields.io/npm/v/zigbee2mqtt">
</a>
<a href="https://github.com/Koenkk/zigbee2mqtt/actions/workflows/ci.yml">
<img src="https://github.com/Koenkk/zigbee2mqtt/actions/workflows/ci.yml/badge.svg">
</a>
<a href="https://github.com/Koenkk/zigbee2mqtt/actions/workflows/github-code-scanning/codeql">
<img src="https://github.com/Koenkk/zigbee2mqtt/actions/workflows/github-code-scanning/codeql/badge.svg">
</a>
<a href="https://discord.gg/dadfWYE">
<img src="https://img.shields.io/discord/556563650429583360.svg">
</a>
<a href="https://github.com/Koenkk/zigbee2mqtt/stargazers">
<img src="https://img.shields.io/github/stars/koenkk/zigbee2mqtt.svg">
</a>
<a href="https://www.paypal.me/koenkk">
<img src="https://img.shields.io/badge/donate-PayPal-blue.svg">
</a>
<a href="https://discord.gg/dadfWYE">
<img src="https://img.shields.io/discord/556563650429583360.svg">
</a>
<a href="http://zigbee2mqtt.discourse.group/">
<img src="https://img.shields.io/discourse/https/zigbee2mqtt.discourse.group/status.svg">
</a>
<a>
<img src="https://img.shields.io/badge/Coverage-100%25-brightgreen.svg">
</a>
<a href="https://www.codacy.com/manual/Koenkk/zigbee2mqtt?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=Koenkk/zigbee2mqtt&amp;utm_campaign=Badge_Grade">
<img src="https://api.codacy.com/project/badge/Grade/24f1e0fe39f04daa810e8a1416693d3f">
</a>
<a href="https://www.npmjs.com/package/zigbee2mqtt">
<img src="https://img.shields.io/npm/v/zigbee2mqtt">
</a>
</div>
<h1>Zigbee2MQTT 🌉 🐝</h1>
<p>
@@ -43,48 +37,86 @@
</div>
## [Getting started](https://www.zigbee2mqtt.io/guide/getting-started)
The [documentation](https://www.zigbee2mqtt.io/) provides you all the information needed to get up and running! Make sure you don't skip sections if this is your first visit, as there might be important details in there for you.
If you aren't familiar with **Zigbee** terminology make sure you [read this](https://www.zigbee2mqtt.io/advanced/zigbee/01_zigbee_network.html) to help you out.
## [Integrations](https://www.zigbee2mqtt.io/guide/usage/integrations.html)
Zigbee2MQTT integrates well with (almost) every home automation solution because it uses MQTT. However the following integrations are worth mentioning:
<img align="left" height="100px" width="100px" src="https://user-images.githubusercontent.com/7738048/40914297-49e6e560-6800-11e8-8904-36cce896e5a8.png">
### [Home Assistant](https://www.home-assistant.io/)
- [Home Assistant OS](https://www.home-assistant.io/installation/): Using [the official addon](https://github.com/zigbee2mqtt/hassio-zigbee2mqtt)
- Other installation: using instructions [here](https://www.zigbee2mqtt.io/guide/usage/integrations/home_assistant.html)
<br>
<a href="https://www.home-assistant.io/"><img align="left" src="https://avatars.githubusercontent.com/u/13844975?s=75" style="padding-right: 20px;" alt="Home Assistant"></a>
<img align="left" height="100px" width="100px" src="https://user-images.githubusercontent.com/2734836/47615848-b8dd8700-dabd-11e8-9d77-175002dd8987.png">
<br clear="right">
[Home Assistant OS](https://www.home-assistant.io/installation/) using [the official addon](https://github.com/zigbee2mqtt/hassio-zigbee2mqtt) ([other installations](https://www.zigbee2mqtt.io/guide/usage/integrations/home_assistant.html))
<br clear="both">
### [Homey](https://homey.app/)
<a href="https://homey.app/"><img align="left" src="https://avatars.githubusercontent.com/u/8502422?s=75" style="padding-right: 20px;" alt="Homey"></a>
<br clear="right">
Integration implemented in the [Homey App](https://homey.app/nl-nl/app/com.gruijter.zigbee2mqtt/) ([documentation & support](https://community.homey.app/t/83214))
<br clear="both">
### [Domoticz](https://www.domoticz.com/)
- Integration implemented in [domoticz-zigbee2mqtt-plugin](https://github.com/stas-demydiuk/domoticz-zigbee2mqtt-plugin)
<br>
<a href="https://www.domoticz.com/"><img align="left" src="https://avatars.githubusercontent.com/u/6067010?s=75" style="padding-right: 20px;" alt="Domoticz"></a>
<img align="left" height="100px" width="100px" src="./images/gladys-assistant-logo.jpg">
<br clear="right">
Integration implemented in Domoticz ([documentation](https://www.domoticz.com/wiki/Zigbee2MQTT)).
<br clear="both">
### [Gladys Assistant](https://gladysassistant.com/)
- Integration implemented natively in Gladys Assistant. Read documentation [here](https://gladysassistant.com/docs/integrations/zigbee2mqtt/)
<br>
<a href="https://gladysassistant.com/"><img align="left" src="https://avatars.githubusercontent.com/u/7365162?s=75" style="padding-right: 20px;" alt="Gladys Assistant"></a>
<br clear="right">
Integration implemented natively in Gladys Assistant ([documentation](https://gladysassistant.com/docs/integrations/zigbee2mqtt/)).
<br clear="both">
### [ioBroker](https://www.iobroker.net/)
<a href="https://www.iobroker.net/"><img align="left" src="https://avatars.githubusercontent.com/u/7231148?s=75" style="padding-right: 20px;" alt="ioBroker"></a>
<br clear="right">
Integration implemented in ioBroker ([documentation](https://github.com/o0shojo0o/ioBroker.zigbee2mqtt)).
<br clear="both">
## Architecture
![Architecture](images/architecture.png)
![Architecture](images/architecture-new.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 provides a [web-based interface](https://github.com/nurikk/zigbee2mqtt-frontend) that allows monitoring and configuration.
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 adapter to handle Zigbee communication 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/wiki/References#texas-instruments-zstack) to communicate with the adapter. 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/wiki/References#csa-zigbee-alliance-spec) 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 several web-based interfaces ([zigbee2mqtt-frontend](https://github.com/nurikk/zigbee2mqtt-frontend), [zigbee2mqtt-windfront](https://github.com/Nerivec/zigbee2mqtt-windfront)) 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.
Zigbee2MQTT uses TypeScript. Therefore after making changes to files in the `lib/` directory you need to recompile Zigbee2MQTT. This can be done by executing `pnpm run build`. For faster development instead of running `pnpm run build` you can run `pnpm run build:watch` in another terminal session, this will recompile as you change files.
Before running any of the commands, you'll first need to run `pnpm install --include=dev`.
Before submitting changes run `pnpm run check:w` then `pnpm run test:coverage`.
## Supported devices
See [Supported devices](https://www.zigbee2mqtt.io/supported-devices) to check whether your device is supported. There is quite an extensive list, including devices from vendors like [Xiaomi](https://www.zigbee2mqtt.io/supported-devices/#v=Xiaomi), [Ikea](https://www.zigbee2mqtt.io/supported-devices/#v=IKEA), [Philips](https://www.zigbee2mqtt.io/supported-devices/#v=Philips), [OSRAM](https://www.zigbee2mqtt.io/supported-devices/#v=OSRAM) and more.
If it's not listed in [Supported devices](https://www.zigbee2mqtt.io/supported-devices), support can be added (fairly) easily, see [How to support new devices](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html).
## Support & help
If you need assistance you can check [opened issues](https://github.com/Koenkk/zigbee2mqtt/issues). Feel free to help with Pull Requests when you were able to fix things or add new devices or just share the love on social media.
-11
View File
@@ -1,11 +0,0 @@
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
assumptions: {setPublicClassFields: true},
plugins: [
['@babel/plugin-proposal-decorators', {'legacy': true}],
['@babel/plugin-proposal-class-properties'],
],
};
+99
View File
@@ -0,0 +1,99 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"indentStyle": "space",
"indentWidth": 4,
"lineWidth": 150,
"bracketSpacing": false
},
"files": {
"includes": ["**", "!package.json", "!!**/dist", "!!**/coverage"]
},
"linter": {
"includes": ["**"],
"rules": {
"correctness": {
"noUnusedImports": "error"
},
"style": {
"noParameterAssign": "off",
"useThrowNewError": "error",
"useThrowOnlyError": "error",
"useNamingConvention": {
"level": "error",
"options": {
"strictCase": false,
"conventions": [
{
"selector": {
"kind": "objectLiteralProperty"
},
"formats": ["snake_case", "camelCase", "CONSTANT_CASE", "PascalCase"]
},
{
"selector": {
"kind": "const"
},
"formats": ["snake_case", "camelCase", "CONSTANT_CASE", "PascalCase"]
},
{
"selector": {
"kind": "typeProperty"
},
"formats": ["snake_case", "camelCase", "CONSTANT_CASE", "PascalCase"]
},
{
"selector": {
"kind": "enumMember"
},
"formats": ["CONSTANT_CASE", "PascalCase"]
}
]
}
},
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
},
"performance": {
"noDelete": "off"
},
"suspicious": {
"noConstEnum": "off",
"useAwait": "error"
},
"nursery": {
"useExhaustiveSwitchCases": "error",
"noFloatingPromises": "error",
"noMisusedPromises": "error"
}
}
},
"overrides": [
{
"includes": ["test/**"],
"linter": {
"rules": {
"style": {
"noNonNullAssertion": "off",
"useNamingConvention": "off"
},
"suspicious": {
"noImplicitAnyLet": "off"
}
}
}
}
]
}
+3 -3
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env node
const path = require('path');
process.env['ZIGBEE2MQTT_DATA'] = process.env['ZIGBEE2MQTT_DATA'] || path.join(process.env['HOME'], '.z2m');
require('./index');
const path = require("node:path");
process.env.ZIGBEE2MQTT_DATA = process.env.ZIGBEE2MQTT_DATA || path.join(process.env.HOME, ".z2m");
require("./index");
+46
View File
@@ -0,0 +1,46 @@
# Indicates the configuration version (used by configuration migrations)
version: 5
# Home Assistant integration (MQTT discovery)
homeassistant:
enabled: false
# Enable the frontend, runs on port 8080 by default
frontend:
enabled: true
# port: 8080
# MQTT settings
mqtt:
# MQTT base topic for zigbee2mqtt MQTT messages
base_topic: zigbee2mqtt
# MQTT server URL
server: "mqtt://localhost"
# MQTT server authentication, uncomment if required:
# user: my_user
# password: my_password
# Serial settings, only required when Zigbee2MQTT fails to start with:
# USB adapter discovery error (No valid USB adapter found).
# Specify valid 'adapter' and 'port' in your configuration.
# serial:
# # Location of the adapter
# # USB adapters - use format "port: /dev/serial/by-id/XXX"
# # Ethernet adapters - use format "port: tcp://192.168.1.12:6638"
# port: /dev/serial/by-id/usb-Texas_Instruments_TI_CC2531_USB_CDC___0X00124B0018ED3DDF-if00
# # Adapter type, allowed values: `zstack`, `ember`, `deconz`, `zigate` or `zboss`
# adapter: zstack
# Periodically check whether devices are online/offline
# availability:
# enabled: false
# Advanced settings
advanced:
# channel: 11
# Let Zigbee2MQTT generate a network key on first start
network_key: GENERATE
# Let Zigbee2MQTT generate a pan_id on first start
pan_id: GENERATE
# Let Zigbee2MQTT generate a ext_pan_id on first start
ext_pan_id: GENERATE
-20
View File
@@ -1,20 +0,0 @@
# Home Assistant integration (MQTT discovery)
homeassistant: false
# allow new devices to join
permit_join: true
# MQTT settings
mqtt:
# MQTT base topic for zigbee2mqtt MQTT messages
base_topic: zigbee2mqtt
# MQTT server URL
server: 'mqtt://localhost'
# MQTT server authentication, uncomment if required:
# user: my_user
# password: my_password
# Serial settings
serial:
# Location of CC2531 USB sniffer
port: /dev/ttyACM0
+36 -13
View File
@@ -1,26 +1,48 @@
FROM alpine:3.18.0 as base
ARG TARGETPLATFORM
FROM alpine:3.23 AS base
ENV NODE_ENV=production
WORKDIR /app
RUN apk add --no-cache tzdata eudev tini nodejs
# Dependencies and build
FROM base as dependencies_and_build
FROM base AS deps
COPY package*.json tsconfig.json index.js ./
COPY lib ./lib
COPY package.json pnpm-lock.yaml ./
RUN apk add --no-cache --virtual .buildtools make gcc g++ python3 linux-headers git npm && \
npm ci --production --no-audit --no-optional --no-update-notifier && \
# Serialport needs to be rebuild for Alpine https://serialport.io/docs/9.x.x/guide-installation#alpine-linux
npm rebuild --build-from-source && \
apk del .buildtools
# Make and such are needed to compile serialport for riscv64
# Install pnpm, configure a tmp-based store, then use it under a cache mount so each arch gets its own store
# NOTE: We do not install devDependencies here (the --prod argument in pnpm install) as we use an already
# built artifact (./dist directory) from previous GitHub runner stages. Omitting devDependencies here
# saves storage as this node_modules/ is being copied straight to the release stage later.
RUN --mount=type=cache,id=pnpm-store,target=/tmp/pnpm-store \
apk add --no-cache make gcc g++ python3 linux-headers npm jq && \
npm install -g $(jq -r '.packageManager' package.json) && \
pnpm config set store-dir /tmp/pnpm-store && \
pnpm install --frozen-lockfile --prod --no-optional && \
# force serialport to rebuild on current platform instead of using prebuilds
rm -rf $(find ./node_modules/.pnpm/ -wholename "*/@serialport/bindings-cpp/prebuilds" -type d) && \
pnpm rebuild @serialport/bindings-cpp
# Release
FROM base as release
FROM base AS release
COPY --from=dependencies_and_build /app/node_modules ./node_modules
ARG DATE
ARG VERSION
LABEL org.opencontainers.image.authors="Koen Kanters"
LABEL org.opencontainers.image.title="Zigbee2MQTT"
LABEL org.opencontainers.image.description="Zigbee to MQTT bridge using Zigbee-herdsman"
LABEL org.opencontainers.image.url="https://github.com/Koenkk/zigbee2mqtt"
LABEL org.opencontainers.image.documentation="https://www.zigbee2mqtt.io/"
LABEL org.opencontainers.image.source="https://github.com/Koenkk/zigbee2mqtt"
LABEL org.opencontainers.image.licenses="GPL-3.0"
LABEL org.opencontainers.image.created=${DATE}
LABEL org.opencontainers.image.version=${VERSION}
COPY --from=deps /app/node_modules ./node_modules
COPY dist ./dist
COPY package.json LICENSE index.js data/configuration.yaml ./
COPY package.json LICENSE index.js data/configuration.example.yaml ./
COPY docker/docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
@@ -30,7 +52,8 @@ RUN mkdir /app/data
ARG COMMIT
RUN echo "$COMMIT" > dist/.hash
ENV NODE_ENV production
# Expose the default frontend port (see lib/util/settings.ts defaults.frontend.port)
EXPOSE 8080
ENTRYPOINT ["docker-entrypoint.sh"]
CMD [ "/sbin/tini", "--", "node", "index.js"]
-14
View File
@@ -1,14 +0,0 @@
version: '3'
services:
zigbee2mqtt:
container_name: zigbee2mqtt
image: koenkk/zigbee2mqtt
volumes:
- ./data:/app/data
devices:
# CC251
#- /dev/ttyUSB_cc2531:/dev/ttyACM0
# CC2530 / GBAN GB2530S
#- /dev/ttyUSB_cc2530:/dev/ttyACM0
restart: always
network_mode: host
-5
View File
@@ -9,9 +9,4 @@ fi
echo "Using '$DATA' as data directory"
if [ ! -f "$DATA/configuration.yaml" ]; then
echo "Creating configuration file..."
cp /app/configuration.yaml "$DATA/configuration.yaml"
fi
exec "$@"
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Executable → Regular
+120 -1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 25 KiB

+127 -63
View File
@@ -1,133 +1,197 @@
const semver = require('semver');
const engines = require('./package.json').engines;
const fs = require('fs');
const os = require('os');
const path = require('path');
const {exec} = require('child_process');
const {rimrafSync} = require('rimraf');
require('source-map-support').install();
const fs = require("node:fs");
const path = require("node:path");
const {exec} = require("node:child_process");
require("source-map-support").install();
/** @type {import("./dist/controller").Controller | undefined} */
let controller;
let stopping = false;
let watchdogCount = 0;
let unsolicitedStop = false;
// csv in minutes, default: 1min, 5min, 15min, 30min, 60min
let watchdogDelays = [2000, 60000, 300000, 900000, 1800000, 3600000];
const hashFile = path.join(__dirname, 'dist', '.hash');
if (process.env.Z2M_WATCHDOG != null && process.env.Z2M_WATCHDOG !== "default") {
if (/^\d+(.\d+)?(,\d+(.\d+)?)*$/.test(process.env.Z2M_WATCHDOG)) {
watchdogDelays = process.env.Z2M_WATCHDOG.split(",").map((v) => Number.parseFloat(v) * 60000);
} else {
console.log(`Invalid watchdog delays (must use number-only CSV format representing minutes, example: 'Z2M_WATCHDOG=1,5,15,30,60'.`);
process.exit(1);
}
}
const hashFile = path.join(__dirname, "dist", ".hash");
/** @type {(code: number) => Promise<void>} */
async function triggerWatchdog(code) {
const delay = watchdogDelays[watchdogCount];
watchdogCount += 1;
if (delay) {
// garbage collector
controller = undefined;
console.log(`WATCHDOG: Waiting ${delay / 60000}min before next start try.`);
await new Promise((resolve) => setTimeout(resolve, delay));
await start();
} else {
process.exit(code);
}
}
/** @type {() => Promise<void>} */
async function restart() {
await stop(true);
await start();
}
async function exit(code, restart) {
/** @type {(code: number, restart?: boolean) => Promise<void>} */
async function exit(code, restart = false) {
if (!restart) {
process.exit(code);
if (process.env.Z2M_WATCHDOG != null && unsolicitedStop) {
await triggerWatchdog(code);
} else {
process.exit(code);
}
}
}
/** @type {() => Promise<string>} */
async function currentHash() {
const git = require('git-last-commit');
return new Promise((resolve) => {
git.getLastCommit((err, commit) => {
if (err) resolve('unknown');
else resolve(commit.shortHash);
return await new Promise((resolve) => {
exec("git rev-parse --short=8 HEAD", (error, stdout) => {
const commitHash = stdout.trim();
if (error || commitHash === "") {
resolve("unknown");
} else {
resolve(commitHash);
}
});
});
}
/** @type {() => Promise<void>} */
async function writeHash() {
const hash = await currentHash();
fs.writeFileSync(hashFile, hash);
}
/** @type {(reason: "initial build" | "hash changed") => Promise<void>} */
async function build(reason) {
return new Promise((resolve, reject) => {
process.stdout.write(`Building Zigbee2MQTT... (${reason})`);
rimrafSync('dist');
process.stdout.write(`Building Zigbee2MQTT... (${reason})`);
const {totalmem} = await import("node:os");
return await new Promise((resolve, reject) => {
const env = {...process.env};
const _600mb = 629145600;
if (_600mb > os.totalmem() && !env.NODE_OPTIONS) {
const mb600 = 629145600;
if (mb600 > 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';
env.NODE_OPTIONS = "--max_old_space_size=256";
}
exec('npm run build', {env, cwd: __dirname}, async (err, stdout, stderr) => {
// clean build, prevent failures due to tsc incremental building
exec("pnpm run prepack", {env, cwd: __dirname}, (err) => {
if (err) {
process.stdout.write(', failed\n');
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');
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');
process.stdout.write(", finished\n");
resolve();
}
});
});
}
/** @type {() => Promise<void>} */
async function checkDist() {
if (!fs.existsSync(hashFile)) {
await build('initial build');
await build("initial build");
}
const distHash = fs.readFileSync(hashFile, 'utf-8');
const distHash = fs.readFileSync(hashFile, "utf8");
const hash = await currentHash();
if (hash !== 'unknown' && distHash !== hash) {
await build('hash changed');
if (hash !== "unknown" && distHash !== hash) {
await build("hash changed");
}
}
/** @type {() => Promise<void>} */
async function start() {
console.log(`Starting Zigbee2MQTT ${process.env.Z2M_WATCHDOG != null ? `with watchdog (${watchdogDelays})` : "without watchdog"}.`);
await checkDist();
const version = engines.node;
if (!semver.satisfies(process.version, version)) {
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`); // eslint-disable-line
}
// gc
{
const packageJSON = (await import("./package.json", {with: {type: "json"}})).default;
const version = packageJSON.engines.node;
const {satisfies} = await import("semver");
// Validate settings
const settings = require('./dist/util/settings');
settings.reRead();
const errors = settings.validate();
if (errors.length > 0) {
console.log(`\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`);
console.log(' READ THIS CAREFULLY\n');
console.log(`Refusing to start because configuration is not valid, found the following errors:`);
for (const error of errors) {
console.log(`- ${error}`);
if (!satisfies(process.version, version)) {
console.log(`\t\tZigbee2MQTT requires node version ${version}, you are running ${process.version}!\n`);
}
const {onboard} = await import("./dist/util/onboarding.js");
const success = await onboard();
if (!success) {
unsolicitedStop = false;
return await exit(1);
}
console.log(`\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/guide/configuration`); // eslint-disable-line
console.log(`\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n`);
exit(1);
}
const Controller = require('./dist/controller');
const {Controller} = await import("./dist/controller.js");
controller = new Controller(restart, exit);
await controller.start();
// consider next controller.stop() call as unsolicited, only after successful first start
unsolicitedStop = true;
watchdogCount = 0; // reset
}
async function stop(restart) {
await controller.stop(restart);
/** @type {(restart: boolean, signal?: NodeJS.Signals) => Promise<void>} */
async function stop(restart, signal = undefined) {
// `handleQuit` or `restart` never unsolicited
unsolicitedStop = false;
await controller?.stop(restart, undefined, signal);
}
async function handleQuit() {
if (!stopping && controller) {
stopping = true;
await stop(false);
/** @type {(signal: NodeJS.Signals) => Promise<void>} */
async function handleQuit(signal) {
if (!stopping) {
if (controller) {
stopping = true;
await stop(false, signal);
} else {
process.exit(0);
}
}
}
if (require.main === module || require.main.filename.endsWith(path.sep + 'cli.js')) {
if (process.argv.length === 3 && process.argv[2] === 'writehash') {
writeHash();
if (require.main === module || require.main?.filename.endsWith(`${path.sep}cli.js`)) {
if (process.argv.length === 3 && process.argv[2] === "writehash") {
void writeHash();
} else {
process.on('SIGINT', handleQuit);
process.on('SIGTERM', handleQuit);
start();
process.on("SIGINT", handleQuit);
process.on("SIGTERM", handleQuit);
void start();
}
} else {
process.on('SIGINT', handleQuit);
process.on('SIGTERM', handleQuit);
process.on("SIGINT", handleQuit);
process.on("SIGTERM", handleQuit);
module.exports = {start};
}
+342 -182
View File
@@ -1,196 +1,361 @@
import MQTT from './mqtt';
import Zigbee from './zigbee';
import EventBus from './eventBus';
import State from './state';
import logger from './util/logger';
import * as settings from './util/settings';
import utils from './util/utils';
import stringify from 'json-stable-stringify-without-jsonify';
import assert from 'assert';
import bind from 'bind-decorator';
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import {setLogger as zhSetLogger} from "zigbee-herdsman";
import {setLogger as zhcSetLogger} from "zigbee-herdsman-converters";
import EventBus from "./eventBus";
// Extensions
import ExtensionFrontend from './extension/frontend';
import ExtensionPublish from './extension/publish';
import ExtensionReceive from './extension/receive';
import ExtensionNetworkMap from './extension/networkMap';
import ExtensionSoftReset from './extension/legacy/softReset';
import ExtensionHomeAssistant from './extension/homeassistant';
import ExtensionConfigure from './extension/configure';
import ExtensionDeviceGroupMembership from './extension/legacy/deviceGroupMembership';
import ExtensionBridgeLegacy from './extension/legacy/bridgeLegacy';
import ExtensionBridge from './extension/bridge';
import ExtensionGroups from './extension/groups';
import ExtensionAvailability from './extension/availability';
import ExtensionBind from './extension/bind';
import ExtensionReport from './extension/legacy/report';
import ExtensionOnEvent from './extension/onEvent';
import ExtensionOTAUpdate from './extension/otaUpdate';
import ExtensionExternalConverters from './extension/externalConverters';
import ExtensionExternalExtension from './extension/externalExtension';
const AllExtensions = [
ExtensionPublish, ExtensionReceive, ExtensionNetworkMap, ExtensionSoftReset, ExtensionHomeAssistant,
ExtensionConfigure, ExtensionDeviceGroupMembership, ExtensionBridgeLegacy, ExtensionBridge, ExtensionGroups,
ExtensionBind, ExtensionReport, ExtensionOnEvent, ExtensionOTAUpdate,
ExtensionExternalConverters, ExtensionFrontend, ExtensionExternalExtension, ExtensionAvailability,
];
type ExtensionArgs = [Zigbee, MQTT, State, PublishEntityState, EventBus,
(enable: boolean, name: string) => Promise<void>, () => void, (extension: Extension) => Promise<void>];
import ExtensionAvailability from "./extension/availability";
import ExtensionBind from "./extension/bind";
import ExtensionBridge from "./extension/bridge";
import ExtensionConfigure from "./extension/configure";
import type Extension from "./extension/extension";
import ExtensionExternalConverters from "./extension/externalConverters";
import ExtensionExternalExtensions from "./extension/externalExtensions";
import ExtensionGroups from "./extension/groups";
import ExtensionHealth from "./extension/health";
import ExtensionNetworkMap from "./extension/networkMap";
import ExtensionOnEvent from "./extension/onEvent";
import ExtensionOTAUpdate from "./extension/otaUpdate";
import ExtensionPublish from "./extension/publish";
import ExtensionReceive from "./extension/receive";
import Mqtt, {type MqttPublishOptions} from "./mqtt";
import State from "./state";
import type {Zigbee2MQTTAPI} from "./types/api";
import logger from "./util/logger";
import {initSdNotify} from "./util/sd-notify";
import * as settings from "./util/settings";
import utils from "./util/utils";
import Zigbee from "./zigbee";
export class Controller {
private eventBus: EventBus;
private zigbee: Zigbee;
private state: State;
private mqtt: MQTT;
private restartCallback: () => void;
private exitCallback: (code: number, restart: boolean) => void;
private extensions: Extension[];
private extensionArgs: ExtensionArgs;
/** Allows canceling in-progress startup sequence if necessary. Signal can be passed to long-running ops as necessary for finer control. */
#startAbortController: AbortController | undefined;
public readonly eventBus: EventBus;
public readonly zigbee: Zigbee;
public readonly state: State;
public readonly mqtt: Mqtt;
private restartCallback: () => Promise<void>;
private exitCallback: (code: number, restart: boolean) => Promise<void>;
public readonly extensions: Set<Extension>;
public readonly extensionArgs: ConstructorParameters<typeof Extension>;
private sdNotify: Awaited<ReturnType<typeof initSdNotify>>;
constructor(restartCallback: () => void, exitCallback: (code: number, restart: boolean) => void) {
constructor(restartCallback: () => Promise<void>, exitCallback: (code: number, restart: boolean) => Promise<void>) {
logger.init();
this.eventBus = new EventBus( /* istanbul ignore next */ (error) => {
logger.error(`Error: ${error.message}`);
logger.debug(error.stack);
});
zhSetLogger(logger);
zhcSetLogger(logger);
this.eventBus = new EventBus();
this.zigbee = new Zigbee(this.eventBus);
this.mqtt = new MQTT(this.eventBus);
this.mqtt = new Mqtt(this.eventBus);
this.state = new State(this.eventBus, this.zigbee);
this.restartCallback = restartCallback;
this.exitCallback = exitCallback;
// Initialize extensions.
this.extensionArgs = [this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus,
this.enableDisableExtension, this.restartCallback, this.addExtension];
this.extensionArgs = [
this.zigbee,
this.mqtt,
this.state,
this.publishEntityState,
this.eventBus,
this.enableDisableExtension,
this.restartCallback,
this.addExtension,
];
this.extensions = new Set();
this.extensions = [
new ExtensionBridge(...this.extensionArgs),
new ExtensionPublish(...this.extensionArgs),
new ExtensionReceive(...this.extensionArgs),
new ExtensionDeviceGroupMembership(...this.extensionArgs),
new ExtensionConfigure(...this.extensionArgs),
new ExtensionNetworkMap(...this.extensionArgs),
new ExtensionGroups(...this.extensionArgs),
new ExtensionBind(...this.extensionArgs),
new ExtensionOnEvent(...this.extensionArgs),
new ExtensionOTAUpdate(...this.extensionArgs),
new ExtensionReport(...this.extensionArgs),
new ExtensionExternalExtension(...this.extensionArgs),
new ExtensionAvailability(...this.extensionArgs),
settings.get().frontend && new ExtensionFrontend(...this.extensionArgs),
settings.get().advanced.legacy_api && new ExtensionBridgeLegacy(...this.extensionArgs),
settings.get().external_converters.length && new ExtensionExternalConverters(...this.extensionArgs),
settings.get().homeassistant && new ExtensionHomeAssistant(...this.extensionArgs),
/* istanbul ignore next */
settings.get().advanced.soft_reset_timeout !== 0 && new ExtensionSoftReset(...this.extensionArgs),
].filter((n) => n);
if (settings.get().advanced.enable_external_js) {
this.extensions.add(new ExtensionExternalConverters(...this.extensionArgs));
} else {
logger.info("External JS (converters/extensions) is disabled");
}
this.extensions.add(new ExtensionOnEvent(...this.extensionArgs));
this.extensions.add(new ExtensionBridge(...this.extensionArgs));
this.extensions.add(new ExtensionPublish(...this.extensionArgs));
this.extensions.add(new ExtensionReceive(...this.extensionArgs));
this.extensions.add(new ExtensionConfigure(...this.extensionArgs));
this.extensions.add(new ExtensionNetworkMap(...this.extensionArgs));
this.extensions.add(new ExtensionGroups(...this.extensionArgs));
this.extensions.add(new ExtensionBind(...this.extensionArgs));
this.extensions.add(new ExtensionOTAUpdate(...this.extensionArgs));
this.extensions.add(new ExtensionAvailability(...this.extensionArgs));
this.extensions.add(new ExtensionHealth(...this.extensionArgs));
if (settings.get().advanced.enable_external_js) {
this.extensions.add(new ExtensionExternalExtensions(...this.extensionArgs));
}
}
async start(): Promise<void> {
this.#startAbortController = new AbortController();
const abortSignal = this.#startAbortController.signal;
if (settings.get().frontend.enabled) {
const {Frontend} = await import("./extension/frontend.js");
this.extensions.add(new Frontend(...this.extensionArgs));
}
if (settings.get().homeassistant.enabled) {
const {HomeAssistant} = await import("./extension/homeassistant.js");
this.extensions.add(new HomeAssistant(...this.extensionArgs));
}
this.state.start();
logger.logOutput();
if (abortSignal.aborted) {
logger.info(`Zigbee2MQTT start aborted, reason=${abortSignal.reason}`);
return;
}
const info = await utils.getZigbee2MQTTVersion();
logger.info(`Starting Zigbee2MQTT version ${info.version} (commit #${info.commitHash})`);
// Start zigbee
let startResult;
try {
startResult = await this.zigbee.start();
this.eventBus.onAdapterDisconnected(this, this.onZigbeeAdapterDisconnected);
const started = await this.zigbee.start(abortSignal);
if (started) {
this.eventBus.onAdapterDisconnected(this, async () => {
logger.error("Adapter disconnected, stopping");
await this.stop(false, 2);
});
}
} catch (error) {
logger.error('Failed to start zigbee');
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);
await this.exit(1);
// skip if aborted by `stop`
if (error !== "SIGINT" && error !== "SIGTERM" && error !== "STOPABORT") {
logger.error("Failed to start zigbee-herdsman");
logger.error(
"Check https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start_crashes-runtime.html for possible solutions",
);
logger.error("Exiting...");
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.error((error as Error).stack!);
/* v8 ignore start */
if ((error as Error).message.includes("USB adapter discovery error (No valid USB adapter found)")) {
logger.error("If this happens after updating to Zigbee2MQTT 2.0.0, see https://github.com/Koenkk/zigbee2mqtt/discussions/24364");
}
/* v8 ignore stop */
return 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');
if (abortSignal.aborted) {
logger.info(`Zigbee2MQTT start aborted, reason=${abortSignal.reason}`);
return;
}
// Log zigbee clients on startup
const devices = this.zigbee.devices(false);
logger.info(`Currently ${devices.length} devices are joined:`);
for (const device of devices) {
const model = device.definition ?
`${device.definition.model} - ${device.definition.vendor} ${device.definition.description}` :
'Not supported';
let deviceCount = 0;
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
// `definition` validated by `isSupported`
const model = device.isSupported
? // biome-ignore lint/style/noNonNullAssertion: valid from `isSupported`
`${device.definition!.model} - ${device.definition!.vendor} ${device.definition!.description}`
: "Not supported";
logger.info(`${device.name} (${device.ieeeAddr}): ${model} (${device.zh.type})`);
deviceCount++;
}
// Enable zigbee join
try {
if (settings.get().permit_join) {
logger.warn('`permit_join` set to `true` in configuration.yaml.');
logger.warn('Allowing new devices to join.');
logger.warn('Set `permit_join` to `false` once you joined all devices.');
}
await this.zigbee.permitJoin(settings.get().permit_join);
} catch (error) {
logger.error(`Failed to set permit join to ${settings.get().permit_join}`);
}
logger.info(`Currently ${deviceCount} devices are joined.`);
// MQTT
try {
await this.mqtt.connect();
} catch (error) {
logger.error(`MQTT failed to connect, exiting...`);
logger.error(`MQTT failed to connect, exiting... (${(error as Error).message})`);
await this.zigbee.stop();
await this.exit(1);
return await this.exit(1);
}
// Call extensions
await this.callExtensions('start', [...this.extensions]);
if (abortSignal.aborted) {
logger.info(`Zigbee2MQTT start aborted, reason=${abortSignal.reason}`);
return;
}
// copy current Set of extensions to ignore possible external extensions added while looping
for (const extension of new Set(this.extensions)) {
await this.startExtension(extension);
if (abortSignal.aborted) {
logger.info(`Zigbee2MQTT start aborted, reason=${abortSignal.reason}`);
return;
}
}
// Send all cached states.
if (settings.get().advanced.cache_state_send_on_startup && settings.get().advanced.cache_state) {
for (const entity of [...devices, ...this.zigbee.groups()]) {
for (const entity of this.zigbee.devicesAndGroupsIterator()) {
if (this.state.exists(entity)) {
this.publishEntityState(entity, this.state.get(entity), 'publishCached');
await this.publishEntityState(entity, this.state.get(entity), "publishCached");
if (abortSignal.aborted) {
logger.info(`Zigbee2MQTT start aborted, reason=${abortSignal.reason}`);
return;
}
}
}
}
this.eventBus.onLastSeenChanged(this,
(data) => utils.publishLastSeen(data, settings.get(), false, this.publishEntityState));
this.eventBus.onLastSeenChanged(this, (data) => {
utils.publishLastSeen(data, settings.get(), false, this.publishEntityState).catch(() => {});
});
logger.info(`Zigbee2MQTT started!`);
logger.info("Zigbee2MQTT started!");
this.sdNotify = await initSdNotify();
settings.setOnboarding(false);
this.#startAbortController = undefined; // gc
}
@bind async enableDisableExtension(enable: boolean, name: string): Promise<void> {
if (!enable) {
const extension = this.extensions.find((e) => e.constructor.name === name);
if (extension) {
await this.callExtensions('stop', [extension]);
this.extensions.splice(this.extensions.indexOf(extension), 1);
if (enable) {
let extension: Extension;
switch (name) {
case "Frontend": {
if (!settings.get().frontend.enabled) {
throw new Error("Tried to enable Frontend extension disabled in settings");
}
// this is not actually used, not tested either
/* v8 ignore start */
const {Frontend} = await import("./extension/frontend.js");
extension = new Frontend(...this.extensionArgs);
break;
/* v8 ignore stop */
}
case "HomeAssistant": {
if (!settings.get().homeassistant.enabled) {
throw new Error("Tried to enable HomeAssistant extension disabled in settings");
}
const {HomeAssistant} = await import("./extension/homeassistant.js");
extension = new HomeAssistant(...this.extensionArgs);
break;
}
default: {
throw new Error(
`Extension ${name} does not exist (should be added with 'addExtension') or is built-in that cannot be enabled at runtime`,
);
}
}
const existingExtension = this.getExtension(name);
if (existingExtension) {
await this.removeExtension(existingExtension);
}
this.extensions.add(extension);
} else {
const Extension = AllExtensions.find((e) => e.name === name);
assert(Extension, `Extension '${name}' does not exist`);
const extension = new Extension(...this.extensionArgs);
this.extensions.push(extension);
await this.callExtensions('start', [extension]);
switch (name) {
case "Frontend": {
if (settings.get().frontend.enabled) {
throw new Error("Tried to disable Frontend extension enabled in settings");
}
break;
}
case "HomeAssistant": {
if (settings.get().homeassistant.enabled) {
throw new Error("Tried to disable HomeAssistant extension enabled in settings");
}
break;
}
case "Availability":
case "Bind":
case "Bridge":
case "Configure":
case "ExternalConverters":
case "ExternalExtensions":
case "Groups":
case "NetworkMap":
case "OnEvent":
case "OTAUpdate":
case "Publish":
case "Receive": {
throw new Error(`Built-in extension ${name} cannot be disabled at runtime`);
}
}
const extension = this.getExtension(name);
if (extension) {
await this.removeExtension(extension);
}
}
}
public getExtension(name: string): Extension | undefined {
for (const extension of this.extensions) {
if (extension.constructor.name === name) {
return extension;
}
}
}
@bind async addExtension(extension: Extension): Promise<void> {
this.extensions.push(extension);
await this.callExtensions('start', [extension]);
for (const ext of this.extensions) {
if (ext.constructor.name === extension.constructor.name) {
throw new Error(`Extension with name ${ext.constructor.name} already present`);
}
}
this.extensions.add(extension);
await this.startExtension(extension);
}
async stop(restart = false): Promise<void> {
// Call extensions
await this.callExtensions('stop', this.extensions);
async removeExtension(extension: Extension): Promise<void> {
if (this.extensions.delete(extension)) {
await this.stopExtension(extension);
}
}
private async startExtension(extension: Extension): Promise<void> {
try {
await extension.start();
} catch (error) {
logger.error(`Failed to start '${extension.constructor.name}' (${(error as Error).stack})`);
}
}
private async stopExtension(extension: Extension): Promise<void> {
try {
await extension.stop();
} catch (error) {
logger.error(`Failed to stop '${extension.constructor.name}' (${(error as Error).stack})`);
}
}
async stop(restart = false, code = 0, signal: NodeJS.Signals | undefined = undefined): Promise<void> {
logger.info(`Stopping Zigbee2MQTT (restart=${restart}, code=${code}, signal=${signal})`);
this.#startAbortController?.abort(signal ?? "STOPABORT");
this.sdNotify?.notifyStopping();
let localCode = 0;
for (const extension of this.extensions) {
try {
await extension.stop();
} catch (error) {
logger.error(`Failed to stop '${extension.constructor.name}' (${(error as Error).stack})`);
localCode = 1;
}
}
this.eventBus.removeListeners(this);
// Wrap-up
@@ -199,27 +364,23 @@ export class Controller {
try {
await this.zigbee.stop();
logger.info('Stopped Zigbee2MQTT');
await this.exit(0, restart);
logger.info("Stopped Zigbee2MQTT");
} catch (error) {
logger.error('Failed to stop Zigbee2MQTT');
await this.exit(1, restart);
logger.error(`Failed to stop Zigbee2MQTT (${(error as Error).stack})`);
localCode = 1;
}
this.sdNotify?.stop();
return await this.exit(code !== 0 ? code : localCode, restart);
}
async exit(code: number, restart = false): Promise<void> {
await logger.end();
this.exitCallback(code, restart);
return await this.exitCallback(code, restart);
}
@bind async onZigbeeAdapterDisconnected(): Promise<void> {
logger.error('Adapter disconnected, stopping');
await this.stop();
}
@bind async publishEntityState(entity: Group | Device, payload: KeyValue,
stateChangeReason?: StateChangeReason): Promise<void> {
let message = {...payload};
@bind async publishEntityState(entity: Group | Device, payload: KeyValue, stateChangeReason?: StateChangeReason): Promise<void> {
let message: Zigbee2MQTTAPI["{friendlyName}"] = {...payload};
// Update state cache with new state.
const newState = this.state.set(entity, payload, stateChangeReason);
@@ -229,34 +390,46 @@ export class Controller {
message = newState;
}
const options: MQTTOptions = {
retain: utils.getObjectProperty(entity.options, 'retain', false) as boolean,
qos: utils.getObjectProperty(entity.options, 'qos', 0) as 0 | 1 | 2,
const options: MakePartialExcept<MqttPublishOptions, "clientOptions" | "meta"> = {
clientOptions: {
retain: utils.getObjectProperty(entity.options, "retain", false),
qos: utils.getObjectProperty(entity.options, "qos", 0),
},
meta: {
isEntityState: true,
},
};
const retention = utils.getObjectProperty<number | false>(entity.options, "retention", false);
const retention = utils.getObjectProperty(entity.options, 'retention', false);
if (retention !== false) {
options.properties = {messageExpiryInterval: retention as number};
options.clientOptions.properties = {messageExpiryInterval: retention};
}
if (entity.isDevice() && settings.get().mqtt.include_device_information) {
message.device = {
friendlyName: entity.name, model: entity.definition ? entity.definition.model : 'unknown',
ieeeAddr: entity.ieeeAddr, networkAddress: entity.zh.networkAddress, type: entity.zh.type,
friendlyName: entity.name,
model: entity.definition?.model,
ieeeAddr: entity.ieeeAddr,
networkAddress: entity.zh.networkAddress,
type: entity.zh.type,
manufacturerID: entity.zh.manufacturerID,
powerSource: entity.zh.powerSource, applicationVersion: entity.zh.applicationVersion,
stackVersion: entity.zh.stackVersion, zclVersion: entity.zh.zclVersion,
hardwareVersion: entity.zh.hardwareVersion, dateCode: entity.zh.dateCode,
powerSource: entity.zh.powerSource,
applicationVersion: entity.zh.applicationVersion,
stackVersion: entity.zh.stackVersion,
zclVersion: entity.zh.zclVersion,
hardwareVersion: entity.zh.hardwareVersion,
dateCode: entity.zh.dateCode,
softwareBuildID: entity.zh.softwareBuildID,
// Manufacturer name can contain \u0000, remove this.
// https://github.com/home-assistant/core/issues/85691
manufacturerName: entity.zh.manufacturerName?.split('\u0000')[0],
/* v8 ignore next */
manufacturerName: entity.zh.manufacturerName?.split("\u0000")[0],
};
}
// Add lastseen
const lastSeen = settings.get().advanced.last_seen;
if (entity.isDevice() && lastSeen !== 'disable' && entity.zh.lastSeen) {
if (entity.isDevice() && lastSeen !== "disable" && entity.zh.lastSeen) {
message.last_seen = utils.formatDate(entity.zh.lastSeen, lastSeen);
}
@@ -272,13 +445,13 @@ export class Controller {
// Filter mqtt message attributes
utils.filterProperties(entity.options.filtered_attributes, message);
if (Object.entries(message).length) {
if (!utils.objectIsEmpty(message)) {
const output = settings.get().advanced.output;
if (output === 'attribute_and_json' || output === 'json') {
if (output === "attribute_and_json" || output === "json") {
await this.mqtt.publish(entity.name, stringify(message), options);
}
if (output === 'attribute_and_json' || output === 'attribute') {
if (output === "attribute_and_json" || output === "attribute") {
await this.iteratePayloadAttributeOutput(`${entity.name}/`, message, options);
}
}
@@ -286,25 +459,25 @@ export class Controller {
this.eventBus.emitPublishEntityState({entity, message, stateChangeReason, payload});
}
async iteratePayloadAttributeOutput(topicRoot: string, payload: KeyValue, options: MQTTOptions): Promise<void> {
async iteratePayloadAttributeOutput(topicRoot: string, payload: KeyValue, options: Partial<MqttPublishOptions>): Promise<void> {
for (const [key, value] of Object.entries(payload)) {
let subPayload = value;
let message = null;
// Special cases
if (key === 'color' && utils.objectHasProperties(subPayload, ['r', 'g', 'b'])) {
if (key === "color" && utils.objectHasProperties(subPayload, ["r", "g", "b"])) {
subPayload = [subPayload.r, subPayload.g, subPayload.b];
}
// Check Array first, since it is also an Object
if (subPayload === null || subPayload === undefined) {
message = '';
message = "";
} else if (Array.isArray(subPayload)) {
message = subPayload.map((x) => `${x}`).join(',');
} else if (typeof subPayload === 'object') {
message = subPayload.map((x) => `${x}`).join(",");
} else if (typeof subPayload === "object") {
await this.iteratePayloadAttributeOutput(`${topicRoot}${key}-`, subPayload, options);
} else {
message = typeof subPayload === 'string' ? subPayload : stringify(subPayload);
message = typeof subPayload === "string" ? subPayload : stringify(subPayload);
}
if (message !== null) {
@@ -312,17 +485,4 @@ export class Controller {
}
}
}
private async callExtensions(method: 'start' | 'stop', extensions: Extension[]): Promise<void> {
for (const extension of extensions) {
try {
await extension[method]?.();
} catch (error) {
/* istanbul ignore next */
logger.error(`Failed to call '${extension.constructor.name}' '${method}' (${error.stack})`);
}
}
}
}
module.exports = Controller;
+168 -68
View File
@@ -1,174 +1,274 @@
import events from 'events';
events.captureRejections = true;
import events from "node:events";
import logger from "./util/logger";
// eslint-disable-next-line
type ListenerKey = object;
export default class EventBus {
private callbacksByExtension: { [s: string]: { event: string, callback: (...args: unknown[]) => void }[] } = {};
private emitter = new events.EventEmitter();
interface EventBusMap {
adapterDisconnected: [];
permitJoinChanged: [data: eventdata.PermitJoinChanged];
publishAvailability: [];
entityRenamed: [data: eventdata.EntityRenamed];
entityRemoved: [data: eventdata.EntityRemoved];
lastSeenChanged: [data: eventdata.LastSeenChanged];
deviceNetworkAddressChanged: [data: eventdata.DeviceNetworkAddressChanged];
deviceAnnounce: [data: eventdata.DeviceAnnounce];
deviceInterview: [data: eventdata.DeviceInterview];
deviceJoined: [data: eventdata.DeviceJoined];
entityOptionsChanged: [data: eventdata.EntityOptionsChanged];
exposesChanged: [data: eventdata.ExposesChanged];
deviceLeave: [data: eventdata.DeviceLeave];
deviceMessage: [data: eventdata.DeviceMessage];
mqttMessage: [data: eventdata.MQTTMessage];
mqttMessagePublished: [data: eventdata.MQTTMessagePublished];
publishEntityState: [data: eventdata.PublishEntityState];
groupMembersChanged: [data: eventdata.GroupMembersChanged];
devicesChanged: [];
scenesChanged: [data: eventdata.ScenesChanged];
reconfigure: [data: eventdata.Reconfigure];
stateChange: [data: eventdata.StateChange];
}
type EventBusListener<K> = K extends keyof EventBusMap
? EventBusMap[K] extends unknown[]
? (...args: EventBusMap[K]) => Promise<void> | void
: never
: never;
constructor(onError: (error: Error) => void) {
type Stats = {
devices: Map<
string, // IEEE address
{
lastSeenChanges?: {messages: number; first: number};
leaveCounts: number;
networkAddressChanges: number;
}
>;
mqtt: {
published: number;
received: number;
};
};
export default class EventBus {
private callbacksByExtension = new Map<string, {event: keyof EventBusMap; callback: EventBusListener<keyof EventBusMap>}[]>();
private emitter = new events.EventEmitter<EventBusMap>();
readonly stats: Stats = {devices: new Map(), mqtt: {published: 0, received: 0}};
constructor() {
this.emitter.setMaxListeners(100);
this.emitter.on('error', onError);
}
public emitAdapterDisconnected(): void {
this.emitter.emit('adapterDisconnected');
this.emitter.emit("adapterDisconnected");
}
public onAdapterDisconnected(key: ListenerKey, callback: () => void): void {
this.on('adapterDisconnected', callback, key);
public onAdapterDisconnected(key: ListenerKey, callback: () => Promise<void>): void {
this.on("adapterDisconnected", callback, key);
}
public emitPermitJoinChanged(data: eventdata.PermitJoinChanged): void {
this.emitter.emit('permitJoinChanged', data);
this.emitter.emit("permitJoinChanged", data);
}
public onPermitJoinChanged(key: ListenerKey, callback: (data: eventdata.PermitJoinChanged) => void): void {
this.on('permitJoinChanged', callback, key);
}
public emitPublishAvailability(): void {
this.emitter.emit('publishAvailability');
}
public onPublishAvailability(key: ListenerKey, callback: () => void): void {
this.on('publishAvailability', callback, key);
this.on("permitJoinChanged", callback, key);
}
public emitEntityRenamed(data: eventdata.EntityRenamed): void {
this.emitter.emit('deviceRenamed', data);
this.emitter.emit("entityRenamed", data);
}
public onEntityRenamed(key: ListenerKey, callback: (data: eventdata.EntityRenamed) => void): void {
this.on('deviceRenamed', callback, key);
this.on("entityRenamed", callback, key);
}
public emitDeviceRemoved(data: eventdata.DeviceRemoved): void {
this.emitter.emit('deviceRemoved', data);
public emitEntityRemoved(data: eventdata.EntityRemoved): void {
this.emitter.emit("entityRemoved", data);
}
public onDeviceRemoved(key: ListenerKey, callback: (data: eventdata.DeviceRemoved) => void): void {
this.on('deviceRemoved', callback, key);
public onEntityRemoved(key: ListenerKey, callback: (data: eventdata.EntityRemoved) => void): void {
this.on("entityRemoved", callback, key);
}
public emitLastSeenChanged(data: eventdata.LastSeenChanged): void {
this.emitter.emit('lastSeenChanged', data);
this.emitter.emit("lastSeenChanged", data);
const device = this.stats.devices.get(data.device.ieeeAddr);
if (device?.lastSeenChanges) {
device.lastSeenChanges.messages += 1;
} else {
this.stats.devices.set(data.device.ieeeAddr, {
lastSeenChanges: {messages: 1, first: Date.now()},
leaveCounts: 0,
networkAddressChanges: 0,
});
}
}
public onLastSeenChanged(key: ListenerKey, callback: (data: eventdata.LastSeenChanged) => void): void {
this.on('lastSeenChanged', callback, key);
this.on("lastSeenChanged", callback, key);
}
public emitDeviceNetworkAddressChanged(data: eventdata.DeviceNetworkAddressChanged): void {
this.emitter.emit('deviceNetworkAddressChanged', data);
this.emitter.emit("deviceNetworkAddressChanged", data);
const device = this.stats.devices.get(data.device.ieeeAddr);
if (device) {
device.networkAddressChanges += 1;
} else {
this.stats.devices.set(data.device.ieeeAddr, {leaveCounts: 0, networkAddressChanges: 1});
}
}
public onDeviceNetworkAddressChanged(
key: ListenerKey, callback: (data: eventdata.DeviceNetworkAddressChanged) => void): void {
this.on('deviceNetworkAddressChanged', callback, key);
public onDeviceNetworkAddressChanged(key: ListenerKey, callback: (data: eventdata.DeviceNetworkAddressChanged) => void): void {
this.on("deviceNetworkAddressChanged", callback, key);
}
public emitDeviceAnnounce(data: eventdata.DeviceAnnounce): void {
this.emitter.emit('deviceAnnounce', data);
this.emitter.emit("deviceAnnounce", data);
}
public onDeviceAnnounce(key: ListenerKey, callback: (data: eventdata.DeviceAnnounce) => void): void {
this.on('deviceAnnounce', callback, key);
this.on("deviceAnnounce", callback, key);
}
public emitDeviceInterview(data: eventdata.DeviceInterview): void {
this.emitter.emit('deviceInterview', data);
this.emitter.emit("deviceInterview", data);
}
public onDeviceInterview(key: ListenerKey, callback: (data: eventdata.DeviceInterview) => void): void {
this.on('deviceInterview', callback, key);
this.on("deviceInterview", callback, key);
}
public emitDeviceJoined(data: eventdata.DeviceJoined): void {
this.emitter.emit('deviceJoined', data);
this.emitter.emit("deviceJoined", data);
}
public onDeviceJoined(key: ListenerKey, callback: (data: eventdata.DeviceJoined) => void): void {
this.on('deviceJoined', callback, key);
this.on("deviceJoined", callback, key);
}
public emitEntityOptionsChanged(data: eventdata.EntityOptionsChanged): void {
this.emitter.emit('entityOptionsChanged', data);
this.emitter.emit("entityOptionsChanged", data);
}
public onEntityOptionsChanged(key: ListenerKey, callback: (data: eventdata.EntityOptionsChanged) => void): void {
this.on('entityOptionsChanged', callback, key);
this.on("entityOptionsChanged", callback, key);
}
public emitExposesChanged(data: eventdata.ExposesChanged): void {
this.emitter.emit("exposesChanged", data);
}
public onExposesChanged(key: ListenerKey, callback: (data: eventdata.ExposesChanged) => void): void {
this.on("exposesChanged", callback, key);
}
public emitDeviceLeave(data: eventdata.DeviceLeave): void {
this.emitter.emit('deviceLeave', data);
this.emitter.emit("deviceLeave", data);
const device = this.stats.devices.get(data.ieeeAddr);
if (device) {
device.leaveCounts += 1;
} else {
this.stats.devices.set(data.ieeeAddr, {leaveCounts: 1, networkAddressChanges: 0});
}
}
public onDeviceLeave(key: ListenerKey, callback: (data: eventdata.DeviceLeave) => void): void {
this.on('deviceLeave', callback, key);
this.on("deviceLeave", callback, key);
}
public emitDeviceMessage(data: eventdata.DeviceMessage): void {
this.emitter.emit('deviceMessage', data);
this.emitter.emit("deviceMessage", data);
}
public onDeviceMessage(key: ListenerKey, callback: (data: eventdata.DeviceMessage) => void): void {
this.on('deviceMessage', callback, key);
this.on("deviceMessage", callback, key);
}
public emitMQTTMessage(data: eventdata.MQTTMessage): void {
this.emitter.emit('mqttMessage', data);
this.emitter.emit("mqttMessage", data);
this.stats.mqtt.received += 1;
}
public onMQTTMessage(key: ListenerKey, callback: (data: eventdata.MQTTMessage) => void): void {
this.on('mqttMessage', callback, key);
this.on("mqttMessage", callback, key);
}
public emitMQTTMessagePublished(data: eventdata.MQTTMessagePublished): void {
this.emitter.emit('mqttMessagePublished', data);
this.emitter.emit("mqttMessagePublished", data);
this.stats.mqtt.published += 1;
}
public onMQTTMessagePublished(key: ListenerKey, callback: (data: eventdata.MQTTMessagePublished) => void): void {
this.on('mqttMessagePublished', callback, key);
this.on("mqttMessagePublished", callback, key);
}
public emitPublishEntityState(data: eventdata.PublishEntityState): void {
this.emitter.emit('publishEntityState', data);
this.emitter.emit("publishEntityState", data);
}
public onPublishEntityState(key: ListenerKey, callback: (data: eventdata.PublishEntityState) => void): void {
this.on('publishEntityState', callback, key);
this.on("publishEntityState", callback, key);
}
public emitGroupMembersChanged(data: eventdata.GroupMembersChanged): void {
this.emitter.emit('groupMembersChanged', data);
this.emitter.emit("groupMembersChanged", data);
}
public onGroupMembersChanged(key: ListenerKey, callback: (data: eventdata.GroupMembersChanged) => void): void {
this.on('groupMembersChanged', callback, key);
this.on("groupMembersChanged", callback, key);
}
public emitDevicesChanged(): void {
this.emitter.emit('devicesChanged');
this.emitter.emit("devicesChanged");
}
public onDevicesChanged(key: ListenerKey, callback: () => void): void {
this.on('devicesChanged', callback, key);
this.on("devicesChanged", callback, key);
}
public emitScenesChanged(): void {
this.emitter.emit('scenesChanged');
public emitScenesChanged(data: eventdata.ScenesChanged): void {
this.emitter.emit("scenesChanged", data);
}
public onScenesChanged(key: ListenerKey, callback: () => void): void {
this.on('scenesChanged', callback, key);
public onScenesChanged(key: ListenerKey, callback: (data: eventdata.ScenesChanged) => void): void {
this.on("scenesChanged", callback, key);
}
public emitReconfigure(data: eventdata.Reconfigure): void {
this.emitter.emit('reconfigure', data);
this.emitter.emit("reconfigure", data);
}
public onReconfigure(key: ListenerKey, callback: (data: eventdata.Reconfigure) => void): void {
this.on('reconfigure', callback, key);
this.on("reconfigure", callback, key);
}
public emitStateChange(data: eventdata.StateChange): void {
this.emitter.emit('stateChange', data);
this.emitter.emit("stateChange", data);
}
public onStateChange(key: ListenerKey, callback: (data: eventdata.StateChange) => void): void {
this.on('stateChange', callback, key);
this.on("stateChange", callback, key);
}
private on(event: string, callback: (...args: unknown[]) => void, key: ListenerKey): void {
if (!this.callbacksByExtension[key.constructor.name]) this.callbacksByExtension[key.constructor.name] = [];
this.callbacksByExtension[key.constructor.name].push({event, callback});
this.emitter.on(event, callback);
public emitExposesAndDevicesChanged(device: Device): void {
this.emitDevicesChanged();
this.emitExposesChanged({device});
}
private on<K extends keyof EventBusMap>(event: K, callback: EventBusListener<K>, key: ListenerKey): void {
if (!this.callbacksByExtension.has(key.constructor.name)) {
this.callbacksByExtension.set(key.constructor.name, []);
}
const wrappedCallback = async (...args: never[]): Promise<void> => {
try {
await callback(...args);
} catch (error) {
logger.error(`EventBus error '${key.constructor.name}/${event}': ${(error as Error).message}`);
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((error as Error).stack!);
}
};
// biome-ignore lint/style/noNonNullAssertion: just created if wasn't valid
this.callbacksByExtension.get(key.constructor.name)!.push({event, callback: wrappedCallback});
this.emitter.on(event, wrappedCallback as EventBusListener<K>);
}
public removeListeners(key: ListenerKey): void {
this.callbacksByExtension[key.constructor.name]?.forEach(
(e) => this.emitter.removeListener(e.event, e.callback));
const callbacks = this.callbacksByExtension.get(key.constructor.name);
if (callbacks) {
for (const cb of callbacks) {
this.emitter.removeListener(cb.event, cb.callback);
}
}
}
}
+241 -110
View File
@@ -1,190 +1,296 @@
import Extension from './extension';
import logger from '../util/logger';
import utils from '../util/utils';
import * as settings from '../util/settings';
import debounce from 'debounce';
import bind from 'bind-decorator';
import assert from "node:assert";
import bind from "bind-decorator";
import debounce from "debounce";
import type * as zhc from "zigbee-herdsman-converters";
import type {Zigbee2MQTTAPI} from "../types/api";
const retrieveOnReconnect = [
{keys: ['state']},
{keys: ['brightness'], condition: (state: KeyValue): boolean => state.state === 'ON'},
{keys: ['color', 'color_temp'], condition: (state: KeyValue): boolean => state.state === 'ON'},
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
const RETRIEVE_ON_RECONNECT: readonly {keys: string[]; condition?: (state: KeyValue) => boolean}[] = [
{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} = {};
private retrieveStateDebouncers: {[s: string]: () => void} = {};
/** Mapped by IEEE address */
private readonly timers = new Map<string, NodeJS.Timeout>();
/** Mapped by IEEE address or Group ID */
private readonly lastPublishedAvailabilities = new Map<string | number, boolean>();
/** Mapped by IEEE address */
private readonly pingBackoffs = new Map<string, number>();
/** IEEE addresses, waiting for last seen changes to take them out of "availability sleep" */
private readonly backoffPausedDevices = new Set<string>();
/** Mapped by IEEE address */
private readonly retrieveStateDebouncers = new Map<string, () => void>();
private pingQueue: Device[] = [];
private pingQueueExecuting = false;
private stopped = false;
private getTimeout(device: Device): number {
if (typeof device.options.availability === 'object' && device.options.availability?.timeout != null) {
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';
let value = settings.get().availability?.[key]?.timeout;
if (value == null) value = key == 'active' ? 10 : 1500;
return utils.minutes(value);
return utils.minutes(this.isActiveDevice(device) ? settings.get().availability.active.timeout : settings.get().availability.passive.timeout);
}
private getMaxJitter(device: Device): number {
if (typeof device.options.availability === "object" && device.options.availability?.max_jitter != null) {
return device.options.availability.max_jitter;
}
return settings.get().availability.active.max_jitter;
}
private getBackoff(device: Device): boolean {
if (typeof device.options.availability === "object" && device.options.availability?.backoff != null) {
return device.options.availability.backoff;
}
return settings.get().availability.active.backoff;
}
private getPauseOnBackoffGt(device: Device): number {
if (typeof device.options.availability === "object" && device.options.availability?.pause_on_backoff_gt != null) {
return device.options.availability.pause_on_backoff_gt;
}
return settings.get().availability.active.pause_on_backoff_gt;
}
private isActiveDevice(device: Device): boolean {
return (device.zh.type === 'Router' && device.zh.powerSource !== 'Battery') ||
device.zh.powerSource === 'Mains (single phase)';
return (
(device.zh.type === "Router" && device.zh.powerSource !== "Battery") ||
(device.zh.powerSource !== undefined && device.zh.powerSource !== "Unknown" && device.zh.powerSource !== "Battery")
);
}
private isAvailable(entity: Device | Group): boolean {
if (entity.isDevice()) {
const ago = Date.now() - entity.zh.lastSeen;
return ago < this.getTimeout(entity);
const lastSeen = entity.zh.lastSeen ?? /* v8 ignore next */ 0;
return Date.now() - lastSeen < this.getTimeout(entity);
}
for (const memberDevice of entity.membersDevices()) {
if (this.lastPublishedAvailabilities.get(memberDevice.ieeeAddr) === true) {
return true;
}
}
return false;
}
private resetTimer(device: Device, resetBackoff = false): void {
clearTimeout(this.timers.get(device.ieeeAddr));
this.removeFromPingQueue(device);
// If the timer triggers, the device is not available anymore otherwise resetTimer already has been called
if (this.isActiveDevice(device)) {
const backoffEnabled = this.getBackoff(device);
const jitter = Math.random() * this.getMaxJitter(device);
let backoff = 1;
if (resetBackoff) {
// always cleanup even if backoff disabled (ensures proper state if changed at runtime)
this.backoffPausedDevices.delete(device.ieeeAddr);
this.pingBackoffs.delete(device.ieeeAddr);
} else if (backoffEnabled) {
backoff = this.pingBackoffs.get(device.ieeeAddr) ?? 1;
}
// never paused if was reset (just deleted) or backoff disabled, might as well skip the Set lookup
if (!backoffEnabled || resetBackoff || !this.backoffPausedDevices.has(device.ieeeAddr)) {
// If device did not check in, ping it, if that fails it will be marked as offline
this.timers.set(
device.ieeeAddr,
setTimeout(this.addToPingQueue.bind(this, device), (this.getTimeout(device) + utils.seconds(1) + jitter) * backoff),
);
}
} else {
return entity.membersDevices().length === 0 ||
entity.membersDevices().map((d) => this.availabilityCache[d.ieeeAddr]).includes(true);
this.timers.set(
device.ieeeAddr,
setTimeout(this.publishAvailability.bind(this, device, true), this.getTimeout(device) + utils.seconds(1)),
);
}
}
private resetTimer(device: Device): void {
clearTimeout(this.timers[device.ieeeAddr]);
// If the timer triggers, the device is not available anymore otherwise resetTimer already have been called
if (this.isActiveDevice(device)) {
// If device did not check in, ping it, if that fails it will be marked as offline
this.timers[device.ieeeAddr] = setTimeout(
() => this.addToPingQueue(device), this.getTimeout(device) + utils.seconds(1));
} else {
this.timers[device.ieeeAddr] = setTimeout(
() => this.publishAvailability(device, true), this.getTimeout(device) + utils.seconds(1));
}
private clearTimer(ieeeAddress: string): void {
clearTimeout(this.timers.get(ieeeAddress));
this.timers.delete(ieeeAddress);
}
private addToPingQueue(device: Device): void {
this.pingQueue.push(device);
this.pingQueueExecuteNext();
this.pingQueueExecuteNext().catch(utils.noop);
}
private removeFromPingQueue(device: Device): void {
const index = this.pingQueue.findIndex((d) => d.ieeeAddr === device.ieeeAddr);
index != -1 && this.pingQueue.splice(index, 1);
if (index !== -1) {
this.pingQueue.splice(index, 1);
}
}
private async pingQueueExecuteNext(): Promise<void> {
if (this.pingQueue.length === 0 || this.pingQueueExecuting) return;
this.pingQueueExecuting = true;
if (this.pingQueue.length === 0 || this.pingQueueExecuting) {
return;
}
this.pingQueueExecuting = true;
const device = this.pingQueue[0];
let pingedSuccessfully = false;
const available = this.availabilityCache[device.ieeeAddr] || this.isAvailable(device);
let pingSuccess = false;
const available = this.lastPublishedAvailabilities.get(device.ieeeAddr) || this.isAvailable(device);
const attempts = available ? 2 : 1;
for (let i = 0; i < attempts; i++) {
for (let i = 1; i <= attempts; i++) {
try {
// Enable recovery if device is marked as available and first ping fails.
const disableRecovery = !(i == 1 && available);
await device.zh.ping(disableRecovery);
pingedSuccessfully = true;
logger.debug(`Successfully pinged '${device.name}' (attempt ${i + 1}/${attempts})`);
await device.zh.ping(!available || i !== 2);
pingSuccess = true;
logger.debug(`Successfully pinged '${device.name}' (attempt ${i}/${attempts})`);
break;
} catch (error) {
logger.warn(`Failed to ping '${device.name}' (attempt ${i + 1}/${attempts}, ${error.message})`);
logger.warning(`Failed to ping '${device.name}' (attempt ${i}/${attempts}, ${(error as Error).message})`);
// Try again in 3 seconds.
const lastAttempt = i - 1 === attempts;
!lastAttempt && await utils.sleep(3);
if (i !== attempts) {
await utils.sleep(3);
}
}
}
this.publishAvailability(device, !pingedSuccessfully);
this.resetTimer(device);
if (this.stopped) {
// Exit here to avoid triggering any follow-up activity (e.g., re-queuing another ping attempt).
return;
}
if (!pingSuccess && this.getBackoff(device)) {
const currentBackoff = this.pingBackoffs.get(device.ieeeAddr) ?? 1;
// setting is "greater than" but since we already did the ping, we use ">=" for comparison below (pause next)
const pauseOnBackoff = this.getPauseOnBackoffGt(device);
if (pauseOnBackoff > 0 && currentBackoff >= pauseOnBackoff) {
this.backoffPausedDevices.add(device.ieeeAddr);
} else {
// results in backoffs: *1.5, *3, *6, *12... (with default timeout: 10, 15, 30, 60, 120)
this.pingBackoffs.set(device.ieeeAddr, currentBackoff * (available ? 1.5 : 2));
}
}
await this.publishAvailability(device, !pingSuccess);
this.resetTimer(device, pingSuccess);
this.removeFromPingQueue(device);
// Sleep 2 seconds before executing next ping
await utils.sleep(2);
this.pingQueueExecuting = false;
this.pingQueueExecuteNext();
await this.pingQueueExecuteNext();
}
override async start(): Promise<void> {
this.eventBus.onEntityRenamed(this, (data) => {
if (this.stopped) {
throw new Error("This extension cannot be restarted.");
}
this.eventBus.onEntityRenamed(this, async (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);
await this.mqtt.publish(`${data.from}/availability`, "", {clientOptions: {retain: true, qos: 1}});
await this.publishAvailability(data.entity, false, true);
}
});
this.eventBus.onDeviceRemoved(this, (data) => clearTimeout(this.timers[data.ieeeAddr]));
this.eventBus.onDeviceLeave(this, (data) => clearTimeout(this.timers[data.ieeeAddr]));
this.eventBus.onEntityRemoved(this, (data) => data.entity.isDevice() && this.clearTimer(data.entity.ID));
this.eventBus.onDeviceLeave(this, (data) => this.clearTimer(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();
}
@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(entity, true, false, true);
// Publish initial availability
await this.publishAvailabilityForAllEntities();
if (entity.isDevice()) {
this.resetTimer(entity);
// Start availability for the devices
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
if (utils.isAvailabilityEnabledForEntity(device, settings.get())) {
this.resetTimer(device);
// If an active device is initially unavailable, ping it.
if (this.isActiveDevice(entity) && !this.isAvailable(entity)) {
this.addToPingQueue(entity);
}
// If an active device is unavailable on start, add it to the pingqueue immediately.
if (this.isActiveDevice(device) && !this.isAvailable(device)) {
this.addToPingQueue(device);
}
}
}
}
private publishAvailability(entity: Device | Group, logLastSeen: boolean,
forcePublish=false, skipGroups=false): void {
private async publishAvailabilityForAllEntities(): Promise<void> {
for (const entity of this.zigbee.devicesAndGroupsIterator(utils.deviceNotCoordinator)) {
if (utils.isAvailabilityEnabledForEntity(entity, settings.get())) {
await this.publishAvailability(entity, true, false, true);
}
}
}
private async publishAvailability(entity: Device | Group, logLastSeen: boolean, forcePublish = false, skipGroups = false): Promise<void> {
if (logLastSeen && entity.isDevice()) {
const ago = Date.now() - entity.zh.lastSeen;
const ago = Date.now() - (entity.zh.lastSeen ?? /* v8 ignore next */ 0);
if (this.isActiveDevice(entity)) {
logger.debug(`Active device '${entity.name}' was last seen ` +
`'${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
logger.debug(`Active device '${entity.name}' was last seen '${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
} else {
logger.debug(
`Passive device '${entity.name}' was last seen '${(ago / utils.hours(1)).toFixed(2)}' hours ago.`);
logger.debug(`Passive device '${entity.name}' was last seen '${(ago / utils.hours(1)).toFixed(2)}' hours ago.`);
}
}
const available = this.isAvailable(entity);
if (!forcePublish && this.availabilityCache[entity.ID] == available) {
if (!forcePublish && this.lastPublishedAvailabilities.get(entity.ID) === available) {
return;
}
if (entity.isDevice() && entity.ieeeAddr in this.availabilityCache && available &&
this.availabilityCache[entity.ieeeAddr] === false) {
if (entity.isDevice() && available && this.lastPublishedAvailabilities.get(entity.ieeeAddr) === false) {
logger.debug(`Device '${entity.name}' reconnected`);
this.retrieveState(entity);
}
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});
const payload: Zigbee2MQTTAPI["{friendlyName}/availability"] = {state: available ? "online" : "offline"};
this.lastPublishedAvailabilities.set(entity.ID, available);
await this.mqtt.publish(topic, JSON.stringify(payload), {clientOptions: {retain: true, qos: 1}});
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));
for (const group of this.zigbee.groupsIterator()) {
if (group.hasMember(entity) && utils.isAvailabilityEnabledForEntity(group, settings.get())) {
await this.publishAvailability(group, false, forcePublish);
}
}
}
}
@bind private onLastSeenChanged(data: eventdata.LastSeenChanged): void {
@bind private async onLastSeenChanged(data: eventdata.LastSeenChanged): Promise<void> {
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);
this.publishAvailability(data.device, false);
this.resetTimer(data.device, true);
await this.publishAvailability(data.device, false);
}
}
override async stop(): Promise<void> {
Object.values(this.timers).forEach((t) => clearTimeout(t));
super.stop();
this.stopped = true;
this.pingQueue = [];
for (const [, t] of this.timers) {
clearTimeout(t);
}
await super.stop();
}
private retrieveState(device: Device): void {
@@ -192,23 +298,48 @@ export default class Availability extends Extension {
* Retrieve state of a device in a debounced manner, this function is called on a 'deviceAnnounce' which a
* device can send multiple times after each other.
*/
if (device.definition && !device.zh.interviewing && !this.retrieveStateDebouncers[device.ieeeAddr]) {
this.retrieveStateDebouncers[device.ieeeAddr] = debounce(async () => {
logger.debug(`Retrieving state of '${device.name}' after reconnect`);
// Color and color temperature converters do both, only needs to be called once.
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));
if (device.definition && device.interviewed && !this.retrieveStateDebouncers.get(device.ieeeAddr)) {
this.retrieveStateDebouncers.set(
device.ieeeAddr,
debounce(async () => {
logger.debug(`Retrieving state of '${device.name}' after reconnect`);
// Color and color temperature converters do both, only needs to be called once.
for (const item of RETRIEVE_ON_RECONNECT) {
if (item.condition && this.state.get(device) && !item.condition(this.state.get(device))) {
continue;
}
// biome-ignore lint/style/noNonNullAssertion: doesn't change once valid
const converter = device.definition!.toZigbee.find((c) => !c.key || c.key.find((k) => item.keys.includes(k)));
const options: KeyValue = device.options;
const state = this.state.get(device);
const meta: zhc.Tz.Meta = {
message: this.state.get(device),
// biome-ignore lint/style/noNonNullAssertion: doesn't change once valid
mapped: device.definition!,
endpoint_name: undefined,
options,
state,
device: device.zh,
/* v8 ignore next */
publish: (payload: KeyValue) => this.publishEntityState(device, payload),
};
try {
const endpoint = device.endpoint();
assert(endpoint);
await converter?.convertGet?.(endpoint, item.keys[0], meta);
} catch (error) {
logger.error(`Failed to read state of '${device.name}' after reconnect (${(error as Error).message})`);
}
await utils.sleep(500);
}
}, utils.seconds(2)),
);
}
this.retrieveStateDebouncers[device.ieeeAddr]?.();
this.retrieveStateDebouncers.get(device.ieeeAddr)?.();
}
}
+455 -317
View File
@@ -1,360 +1,454 @@
import * as settings from '../util/settings';
import logger from '../util/logger';
import utils from '../util/utils';
import Extension from './extension';
import stringify from 'json-stable-stringify-without-jsonify';
import debounce from 'debounce';
import * as zigbeeHerdsman from 'zigbee-herdsman/dist';
import bind from 'bind-decorator';
import Device from '../model/device';
import Group from '../model/group';
import assert from "node:assert";
import bind from "bind-decorator";
import debounce from "debounce";
import stringify from "json-stable-stringify-without-jsonify";
import {Zcl} from "zigbee-herdsman";
import type {TClusterAttributeKeys} from "zigbee-herdsman/dist/zspec/zcl/definition/clusters-types";
import type {ClusterName} from "zigbee-herdsman/dist/zspec/zcl/definition/tstype";
import Device from "../model/device";
import Group from "../model/group";
import type {Zigbee2MQTTAPI, Zigbee2MQTTResponseEndpoints} from "../types/api";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils, {DEFAULT_BIND_GROUP_ID} from "../util/utils";
import Extension from "./extension";
const legacyApi = settings.get().advanced.legacy_api;
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/(bind|unbind)/.+$`);
const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/(bind|unbind)`);
const clusterCandidates = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering',
'hvacThermostat', 'msIlluminanceMeasurement', 'msTemperatureMeasurement', 'msRelativeHumidity',
'msSoilMoisture', 'msCO2'];
const ALL_CLUSTER_CANDIDATES: readonly ClusterName[] = [
"genScenes",
"genOnOff",
"genLevelCtrl",
"lightingColorCtrl",
"closuresWindowCovering",
"hvacThermostat",
"msIlluminanceMeasurement",
"msTemperatureMeasurement",
"msRelativeHumidity",
"msSoilMoisture",
"msCO2",
];
// See zigbee-herdsman-converters
const defaultBindGroup = {type: 'group_number', ID: 901, name: 'default_bind_group'};
const DEFAULT_BIND_GROUP = {type: "group_number", ID: DEFAULT_BIND_GROUP_ID, name: "default_bind_group"};
const DEFAULT_REPORT_CONFIG = {minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1};
const defaultReportConfiguration = {
minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1,
};
const getColorCapabilities = async (endpoint: zh.Endpoint): Promise<{colorTemperature: boolean, colorXY: boolean}> => {
if (endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities') == null) {
await endpoint.read('lightingColorCtrl', ['colorCapabilities']);
const getColorCapabilities = async (endpoint: zh.Endpoint): Promise<{colorTemperature: boolean; colorXY: boolean}> => {
if (endpoint.getClusterAttributeValue("lightingColorCtrl", "colorCapabilities") == null) {
await endpoint.read("lightingColorCtrl", ["colorCapabilities"]);
}
const value = endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities') as number;
const value = endpoint.getClusterAttributeValue("lightingColorCtrl", "colorCapabilities") as number;
return {
colorTemperature: (value & 1<<4) > 0,
colorXY: (value & 1<<3) > 0,
colorTemperature: (value & (1 << 4)) > 0,
colorXY: (value & (1 << 3)) > 0,
};
};
const reportClusters: {[s: string]:
{attribute: string, minimumReportInterval: number, maximumReportInterval: number, reportableChange: number
condition?: (endpoint: zh.Endpoint) => Promise<boolean>}[]} =
{
'genOnOff': [
{attribute: 'onOff', ...defaultReportConfiguration, minimumReportInterval: 0, reportableChange: 0},
],
'genLevelCtrl': [
{attribute: 'currentLevel', ...defaultReportConfiguration},
],
'lightingColorCtrl': [
const REPORT_CLUSTERS = {
genOnOff: [{attribute: "onOff" as const, ...DEFAULT_REPORT_CONFIG, minimumReportInterval: 0, reportableChange: 0}],
genLevelCtrl: [{attribute: "currentLevel" as const, ...DEFAULT_REPORT_CONFIG}],
lightingColorCtrl: [
{
attribute: 'colorTemperature', ...defaultReportConfiguration,
condition: async (endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorTemperature,
attribute: "colorTemperature" as const,
...DEFAULT_REPORT_CONFIG,
condition: async (endpoint: zh.Endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorTemperature,
},
{
attribute: 'currentX', ...defaultReportConfiguration,
condition: async (endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorXY,
attribute: "currentX" as const,
...DEFAULT_REPORT_CONFIG,
condition: async (endpoint: zh.Endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorXY,
},
{
attribute: 'currentY', ...defaultReportConfiguration,
condition: async (endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorXY,
attribute: "currentY" as const,
...DEFAULT_REPORT_CONFIG,
condition: async (endpoint: zh.Endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorXY,
},
],
'closuresWindowCovering': [
{attribute: 'currentPositionLiftPercentage', ...defaultReportConfiguration},
{attribute: 'currentPositionTiltPercentage', ...defaultReportConfiguration},
closuresWindowCovering: [
{attribute: "currentPositionLiftPercentage" as const, ...DEFAULT_REPORT_CONFIG},
{attribute: "currentPositionTiltPercentage" as const, ...DEFAULT_REPORT_CONFIG},
],
};
type PollOnMessage = {
cluster: {[s: string]: {type: string, data: KeyValue}[]}
read: {cluster: string, attributes: string[], attributesForEndpoint?: (endpoint: zh.Endpoint) => Promise<string[]>}
manufacturerIDs: number[],
manufacturerNames: string [],
}[];
const pollOnMessage: PollOnMessage = [
const POLL_ON_MESSAGE = [
{
// On messages that have the cluster and type of below
cluster: {
manuSpecificPhilips: [
{type: 'commandHueNotification', data: {button: 2}},
{type: 'commandHueNotification', data: {button: 3}},
{type: "commandHueNotification", data: {button: 2}},
{type: "commandHueNotification", data: {button: 3}},
],
genLevelCtrl: [
{type: 'commandStep', data: {}},
{type: 'commandStepWithOnOff', data: {}},
{type: 'commandStop', data: {}},
{type: 'commandMoveWithOnOff', data: {}},
{type: 'commandStopWithOnOff', data: {}},
{type: 'commandMove', data: {}},
{type: 'commandMoveToLevelWithOnOff', data: {}},
],
genScenes: [
{type: 'commandRecall', data: {}},
{type: "commandStep", data: {}},
{type: "commandStepWithOnOff", data: {}},
{type: "commandStop", data: {}},
{type: "commandMoveWithOnOff", data: {}},
{type: "commandStopWithOnOff", data: {}},
{type: "commandMove", data: {}},
{type: "commandMoveToLevelWithOnOff", data: {}},
],
genScenes: [{type: "commandRecall", data: {}}],
},
// Read the following attributes
read: {cluster: 'genLevelCtrl', attributes: ['currentLevel']},
read: {cluster: "genLevelCtrl" as const, attributes: ["currentLevel"] as TClusterAttributeKeys<"genLevelCtrl">},
// When the bound devices/members of group have the following manufacturerIDs
manufacturerIDs: [
zigbeeHerdsman.Zcl.ManufacturerCode.Philips,
zigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
zigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
zigbeeHerdsman.Zcl.ManufacturerCode.TELINK,
zigbeeHerdsman.Zcl.ManufacturerCode.BUSCH_JAEGER,
],
manufacturerNames: [
'GLEDOPTO',
'Trust International B.V.\u0000',
Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
Zcl.ManufacturerCode.ATMEL,
Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
Zcl.ManufacturerCode.TELINK_MICRO,
Zcl.ManufacturerCode.BUSCH_JAEGER_ELEKTRO,
],
manufacturerNames: ["GLEDOPTO", "Trust International B.V.\u0000"],
},
{
cluster: {
genLevelCtrl: [
{type: 'commandStepWithOnOff', data: {}},
{type: 'commandMoveWithOnOff', data: {}},
{type: 'commandStopWithOnOff', data: {}},
{type: 'commandMoveToLevelWithOnOff', data: {}},
{type: "commandStepWithOnOff", data: {}},
{type: "commandMoveWithOnOff", data: {}},
{type: "commandStopWithOnOff", data: {}},
{type: "commandMoveToLevelWithOnOff", data: {}},
],
genOnOff: [
{type: 'commandOn', data: {}},
{type: 'commandOff', data: {}},
{type: 'commandOffWithEffect', data: {}},
{type: 'commandToggle', data: {}},
],
genScenes: [
{type: 'commandRecall', data: {}},
{type: "commandOn", data: {}},
{type: "commandOff", data: {}},
{type: "commandOffWithEffect", data: {}},
{type: "commandToggle", data: {}},
],
genScenes: [{type: "commandRecall", data: {}}],
manuSpecificPhilips: [
{type: 'commandHueNotification', data: {button: 1}},
{type: 'commandHueNotification', data: {button: 4}},
{type: "commandHueNotification", data: {button: 1}},
{type: "commandHueNotification", data: {button: 4}},
],
},
read: {cluster: 'genOnOff', attributes: ['onOff']},
read: {cluster: "genOnOff" as const, attributes: ["onOff"] as TClusterAttributeKeys<"genOnOff">},
manufacturerIDs: [
zigbeeHerdsman.Zcl.ManufacturerCode.Philips,
zigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
zigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
zigbeeHerdsman.Zcl.ManufacturerCode.TELINK,
zigbeeHerdsman.Zcl.ManufacturerCode.BUSCH_JAEGER,
],
manufacturerNames: [
'GLEDOPTO',
'Trust International B.V.\u0000',
Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
Zcl.ManufacturerCode.ATMEL,
Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
Zcl.ManufacturerCode.TELINK_MICRO,
Zcl.ManufacturerCode.BUSCH_JAEGER_ELEKTRO,
],
manufacturerNames: ["GLEDOPTO", "Trust International B.V.\u0000"],
},
{
cluster: {
genScenes: [
{type: 'commandRecall', data: {}},
],
genScenes: [{type: "commandRecall", data: {}}],
},
read: {
cluster: 'lightingColorCtrl',
attributes: [] as string[],
cluster: "lightingColorCtrl" as const,
attributes: [] as TClusterAttributeKeys<"lightingColorCtrl">,
// Since not all devices support the same attributes they need to be calculated dynamically
// depending on the capabilities of the endpoint.
attributesForEndpoint: async (endpoint): Promise<string[]> => {
attributesForEndpoint: async (endpoint: zh.Endpoint): Promise<TClusterAttributeKeys<"lightingColorCtrl">> => {
const supportedAttrs = await getColorCapabilities(endpoint);
const readAttrs: string[] = [];
supportedAttrs.colorXY && readAttrs.push('currentX', 'currentY');
supportedAttrs.colorTemperature && readAttrs.push('colorTemperature');
const readAttrs: TClusterAttributeKeys<"lightingColorCtrl"> = [];
if (supportedAttrs.colorXY) {
readAttrs.push("currentX", "currentY");
}
if (supportedAttrs.colorTemperature) {
readAttrs.push("colorTemperature");
}
return readAttrs;
},
},
manufacturerIDs: [
zigbeeHerdsman.Zcl.ManufacturerCode.Philips,
zigbeeHerdsman.Zcl.ManufacturerCode.ATMEL,
zigbeeHerdsman.Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
zigbeeHerdsman.Zcl.ManufacturerCode.MUELLER_LICHT_INT,
zigbeeHerdsman.Zcl.ManufacturerCode.TELINK,
Zcl.ManufacturerCode.SIGNIFY_NETHERLANDS_B_V,
Zcl.ManufacturerCode.ATMEL,
Zcl.ManufacturerCode.GLEDOPTO_CO_LTD,
Zcl.ManufacturerCode.MUELLER_LICHT_INTERNATIONAL_INC,
Zcl.ManufacturerCode.TELINK_MICRO,
// Note: ManufacturerCode.BUSCH_JAEGER is left out intentionally here as their devices don't support colors
],
manufacturerNames: [
'GLEDOPTO',
'Trust International B.V.\u0000',
],
manufacturerNames: ["GLEDOPTO", "Trust International B.V.\u0000"],
},
];
interface ParsedMQTTMessage {
type: 'bind' | 'unbind', sourceKey: string, targetKey: string, clusters: string[], skipDisableReporting: boolean
type: "bind" | "unbind";
sourceKey?: string;
sourceEndpointKey?: string | number;
targetKey?: string | number;
targetEndpointKey?: string | number;
clusters?: string[];
skipDisableReporting: boolean;
resolvedSource?: Device;
resolvedTarget?: Device | Group | typeof DEFAULT_BIND_GROUP;
resolvedSourceEndpoint?: zh.Endpoint;
resolvedBindTarget?: number | zh.Endpoint | zh.Group;
}
export default class Bind extends Extension {
#topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/(bind|unbind|binds/clear)`);
private pollDebouncers: {[s: string]: () => void} = {};
// biome-ignore lint/suspicious/useAwait: API
override async start(): Promise<void> {
this.eventBus.onDeviceMessage(this, this.poll);
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onGroupMembersChanged(this, this.onGroupMembersChanged);
}
private parseMQTTMessage(data: eventdata.MQTTMessage): ParsedMQTTMessage {
let type: 'bind' | 'unbind' = null;
let sourceKey = null;
let targetKey = null;
let clusters = null;
let skipDisableReporting = false;
private parseMQTTMessage(
data: eventdata.MQTTMessage,
): [raw: KeyValue | undefined, parsed: ParsedMQTTMessage | undefined, error: string | undefined] {
if (data.topic.match(this.#topicRegex)) {
const type = data.topic.endsWith("unbind") ? "unbind" : "bind";
let skipDisableReporting = false;
const message = JSON.parse(data.message) as Zigbee2MQTTAPI["bridge/request/device/bind"];
if (legacyApi && data.topic.match(legacyTopicRegex)) {
const topic = data.topic.replace(`${settings.get().mqtt.base_topic}/bridge/`, '');
type = topic.split('/')[0] as 'bind' | 'unbind';
sourceKey = topic.replace(`${type}/`, '');
targetKey = data.message;
} else if (data.topic.match(topicRegex)) {
type = data.topic.endsWith('unbind') ? 'unbind' : 'bind';
const message = JSON.parse(data.message);
sourceKey = message.from;
targetKey = message.to;
clusters = message.clusters;
skipDisableReporting = 'skip_disable_reporting' in message ? message.skip_disable_reporting : false;
if (typeof message !== "object" || message.from == null || message.to == null) {
return [message, {type, skipDisableReporting}, "Invalid payload"];
}
const sourceKey = message.from;
const sourceEndpointKey = message.from_endpoint ?? "default";
const targetKey = message.to;
const targetEndpointKey = message.to_endpoint;
const clusters = message.clusters;
skipDisableReporting = message.skip_disable_reporting != null ? message.skip_disable_reporting : false;
const resolvedSource = this.zigbee.resolveEntity(message.from) as Device;
if (!resolvedSource || !(resolvedSource instanceof Device)) {
return [message, {type, skipDisableReporting}, `Source device '${message.from}' does not exist`];
}
const resolvedTarget =
message.to === DEFAULT_BIND_GROUP.name || message.to === DEFAULT_BIND_GROUP.ID
? DEFAULT_BIND_GROUP
: this.zigbee.resolveEntity(message.to);
if (!resolvedTarget) {
return [message, {type, skipDisableReporting}, `Target device or group '${message.to}' does not exist`];
}
const resolvedSourceEndpoint = resolvedSource.endpoint(sourceEndpointKey);
if (!resolvedSourceEndpoint) {
return [
message,
{type, skipDisableReporting},
`Source device '${resolvedSource.name}' does not have endpoint '${sourceEndpointKey}'`,
];
}
// resolves to 'default' endpoint if targetEndpointKey is invalid (used by frontend for 'Coordinator')
const resolvedBindTarget =
resolvedTarget instanceof Device
? resolvedTarget.endpoint(targetEndpointKey)
: resolvedTarget instanceof Group
? resolvedTarget.zh
: Number(resolvedTarget.ID);
if (resolvedTarget instanceof Device && !resolvedBindTarget) {
return [
message,
{type, skipDisableReporting},
`Target device '${resolvedTarget.name}' does not have endpoint '${targetEndpointKey}'`,
];
}
return [
message,
{
type,
sourceKey,
sourceEndpointKey,
targetKey,
targetEndpointKey,
clusters,
skipDisableReporting,
resolvedSource,
resolvedTarget,
resolvedSourceEndpoint,
resolvedBindTarget,
},
undefined,
];
}
return {type, sourceKey, targetKey, clusters, skipDisableReporting};
return [undefined, undefined, undefined];
}
@bind private async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
const {type, sourceKey, targetKey, clusters, skipDisableReporting} = this.parseMQTTMessage(data);
if (!type) return null;
const message = utils.parseJSON(data.message, data.message);
if (data.topic.endsWith("binds/clear")) {
const message = JSON.parse(data.message) as Zigbee2MQTTAPI["bridge/request/device/binds/clear"];
let error = null;
const parsedSource = utils.parseEntityID(sourceKey);
const parsedTarget = utils.parseEntityID(targetKey);
const source = this.zigbee.resolveEntity(parsedSource.ID);
const target = targetKey === 'default_bind_group' ?
defaultBindGroup : this.zigbee.resolveEntity(parsedTarget.ID);
const responseData: KeyValue = {from: sourceKey, to: targetKey};
if (typeof message !== "object" || typeof message.target !== "string") {
await this.publishResponse("binds/clear", message, {}, "Invalid payload");
return;
}
if (!source || !(source instanceof Device)) {
error = `Source device '${sourceKey}' does not exist`;
} else if (!target) {
error = `Target device or group '${targetKey}' does not exist`;
} else {
const successfulClusters: string[] = [];
const failedClusters = [];
const attemptedClusters = [];
const target = this.zigbee.resolveEntity(message.target);
const bindSource: zh.Endpoint = source.endpoint(parsedSource.endpoint);
let bindTarget: number | zh.Group | zh.Endpoint = null;
if (target instanceof Device) bindTarget = target.endpoint(parsedTarget.endpoint);
else if (target instanceof Group) bindTarget = target.zh;
else bindTarget = Number(target.ID);
if (!(target instanceof Device)) {
await this.publishResponse("binds/clear", message, {}, "Invalid target");
return;
}
// Find which clusters are supported by both the source and target.
// Groups are assumed to support all clusters.
for (const cluster of clusterCandidates) {
if (clusters && !clusters.includes(cluster)) continue;
let matchingClusters = false;
// this list is raw (not resolved) to allow clearing any specific target (not only currently known)
const eui64List = message.ieee_list ?? ["0xffffffffffffffff"];
const anyClusterValid = utils.isZHGroup(bindTarget) || typeof bindTarget === 'number' ||
(target as Device).zh.type === 'Coordinator';
await target.zh.clearAllBindings(eui64List);
if (!anyClusterValid && utils.isEndpoint(bindTarget)) {
matchingClusters = ((bindTarget.supportsInputCluster(cluster) &&
bindSource.supportsOutputCluster(cluster)) ||
(bindSource.supportsInputCluster(cluster) &&
bindTarget.supportsOutputCluster(cluster)) );
const responseData: Zigbee2MQTTAPI["bridge/response/device/binds/clear"] = {
target: message.target,
ieee_list: eui64List,
};
await this.publishResponse("binds/clear", message, responseData);
this.eventBus.emitDevicesChanged();
return;
}
const [raw, parsed, error] = this.parseMQTTMessage(data);
if (!raw || !parsed) {
return;
}
if (error) {
await this.publishResponse(parsed.type, raw, {}, error);
return;
}
const {
type,
sourceKey,
sourceEndpointKey,
targetKey,
targetEndpointKey,
clusters,
skipDisableReporting,
resolvedSource,
resolvedTarget,
resolvedSourceEndpoint,
resolvedBindTarget,
} = parsed;
assert(resolvedSource, "`resolvedSource` is missing");
assert(resolvedTarget, "`resolvedTarget` is missing");
assert(resolvedSourceEndpoint, "`resolvedSourceEndpoint` is missing");
assert(resolvedBindTarget !== undefined, "`resolvedBindTarget` is missing");
const successfulClusters: string[] = [];
const failedClusters = [];
const attemptedClusters = [];
// Find which clusters are supported by both the source and target.
// Groups are assumed to support all clusters.
const clusterCandidates = clusters ?? ALL_CLUSTER_CANDIDATES;
for (const cluster of clusterCandidates) {
let matchingClusters = false;
const anyClusterValid =
utils.isZHGroup(resolvedBindTarget) ||
typeof resolvedBindTarget === "number" ||
(resolvedTarget instanceof Device && resolvedTarget.zh.type === "Coordinator");
if (!anyClusterValid && utils.isZHEndpoint(resolvedBindTarget)) {
matchingClusters =
(resolvedBindTarget.supportsInputCluster(cluster) && resolvedSourceEndpoint.supportsOutputCluster(cluster)) ||
(resolvedSourceEndpoint.supportsInputCluster(cluster) && resolvedBindTarget.supportsOutputCluster(cluster));
}
const sourceValid = resolvedSourceEndpoint.supportsInputCluster(cluster) || resolvedSourceEndpoint.supportsOutputCluster(cluster);
if (sourceValid && (anyClusterValid || matchingClusters)) {
logger.debug(`${type}ing cluster '${cluster}' from '${resolvedSource.name}' to '${resolvedTarget.name}'`);
attemptedClusters.push(cluster);
try {
if (type === "bind") {
await resolvedSourceEndpoint.bind(cluster, resolvedBindTarget);
} else {
await resolvedSourceEndpoint.unbind(cluster, resolvedBindTarget);
}
successfulClusters.push(cluster);
logger.info(
`Successfully ${type === "bind" ? "bound" : "unbound"} cluster '${cluster}' from '${resolvedSource.name}' to '${resolvedTarget.name}'`,
);
} catch (error) {
failedClusters.push(cluster);
logger.error(`Failed to ${type} cluster '${cluster}' from '${resolvedSource.name}' to '${resolvedTarget.name}' (${error})`);
}
}
}
const sourceValid = bindSource.supportsInputCluster(cluster) ||
bindSource.supportsOutputCluster(cluster);
if (attemptedClusters.length === 0) {
logger.error(`Nothing to ${type} from '${resolvedSource.name}' to '${resolvedTarget.name}'`);
await this.publishResponse(parsed.type, raw, {}, `Nothing to ${type}`);
return;
}
if ( sourceValid && (anyClusterValid || matchingClusters)) {
logger.debug(`${type}ing cluster '${cluster}' from '${source.name}' to '${target.name}'`);
attemptedClusters.push(cluster);
if (failedClusters.length === attemptedClusters.length) {
await this.publishResponse(parsed.type, raw, {}, `Failed to ${type}`);
return;
}
try {
if (type === 'bind') {
await bindSource.bind(cluster, bindTarget);
} else {
await bindSource.unbind(cluster, bindTarget);
}
const responseData: Zigbee2MQTTAPI["bridge/response/device/bind"] | Zigbee2MQTTAPI["bridge/response/device/unbind"] = {
// biome-ignore lint/style/noNonNullAssertion: valid with assert above on `resolvedSource`
from: sourceKey!,
// biome-ignore lint/style/noNonNullAssertion: valid with assert above on `resolvedSourceEndpoint`
from_endpoint: sourceEndpointKey!,
// biome-ignore lint/style/noNonNullAssertion: valid with assert above on `resolvedTarget`
to: targetKey!,
to_endpoint: targetEndpointKey,
clusters: successfulClusters,
failed: failedClusters,
};
successfulClusters.push(cluster);
logger.info(
`Successfully ${type === 'bind' ? 'bound' : 'unbound'} cluster '${cluster}' from ` +
`'${source.name}' to '${target.name}'`,
);
if (successfulClusters.length !== 0) {
if (type === "bind") {
await this.setupReporting(
resolvedSourceEndpoint.binds.filter((b) => successfulClusters.includes(b.cluster.name) && b.target === resolvedBindTarget),
);
} else if (typeof resolvedBindTarget !== "number" && !skipDisableReporting) {
await this.disableUnnecessaryReportings(resolvedBindTarget);
}
}
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
this.mqtt.publish(
'bridge/log',
stringify({type: `device_${type}`,
message: {from: source.name, to: target.name, cluster}}),
);
}
} catch (error) {
failedClusters.push(cluster);
logger.error(
`Failed to ${type} cluster '${cluster}' from '${source.name}' to ` +
`'${target.name}' (${error})`,
);
await this.publishResponse(parsed.type, raw, responseData);
this.eventBus.emitDevicesChanged();
}
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
this.mqtt.publish(
'bridge/log',
stringify({type: `device_${type}_failed`,
message: {from: source.name, to: target.name, cluster}}),
);
private async publishResponse<T extends Zigbee2MQTTResponseEndpoints>(
type: ParsedMQTTMessage["type"] | "binds/clear",
request: KeyValue,
data: Zigbee2MQTTAPI[T],
error?: string,
): Promise<void> {
const response = utils.getResponse(request, data, error);
await this.mqtt.publish(`bridge/response/device/${type}`, stringify(response));
if (error) {
logger.error(error);
}
}
@bind async onGroupMembersChanged(data: eventdata.GroupMembersChanged): Promise<void> {
if (data.action === "add") {
const bindsToGroup: zh.Bind[] = [];
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
for (const endpoint of device.zh.endpoints) {
for (const bind of endpoint.binds) {
if (bind.target === data.group.zh) {
bindsToGroup.push(bind);
}
}
}
}
if (attemptedClusters.length === 0) {
logger.error(`Nothing to ${type} from '${source.name}' to '${target.name}'`);
error = `Nothing to ${type}`;
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
this.mqtt.publish(
'bridge/log',
stringify({type: `device_${type}_failed`, message: {from: source.name, to: target.name}}),
);
}
} else if (failedClusters.length === attemptedClusters.length) {
error = `Failed to ${type}`;
}
responseData[`clusters`] = successfulClusters;
responseData[`failed`] = failedClusters;
if (successfulClusters.length !== 0) {
if (type === 'bind') {
await this.setupReporting(bindSource.binds.filter((b) =>
successfulClusters.includes(b.cluster.name) && b.target === bindTarget));
} else if ((typeof bindTarget !== 'number') && !skipDisableReporting) {
await this.disableUnnecessaryReportings(bindTarget);
}
}
}
const triggeredViaLegacyApi = data.topic.match(legacyTopicRegex);
if (!triggeredViaLegacyApi) {
const response = utils.getResponse(message, responseData, error);
await this.mqtt.publish(`bridge/response/device/${type}`, stringify(response));
}
if (error) {
logger.error(error);
} else {
this.eventBus.emitDevicesChanged();
}
}
@bind async onGroupMembersChanged(data: eventdata.GroupMembersChanged): Promise<void> {
if (data.action === 'add') {
const bindsToGroup = this.zigbee.devices(false).map((c) => c.zh.endpoints)
.reduce((a, v) => a.concat(v)).map((e) => e.binds)
.reduce((a, v) => a.concat(v)).filter((b) => b.target === data.group.zh);
await this.setupReporting(bindsToGroup);
} else { // action === remove/remove_all
} else {
// action === remove/remove_all
if (!data.skipDisableReporting) {
await this.disableUnnecessaryReportings(data.endpoint);
}
@@ -362,36 +456,54 @@ export default class Bind extends Extension {
}
getSetupReportingEndpoints(bind: zh.Bind, coordinatorEp: zh.Endpoint): zh.Endpoint[] {
const endpoints = utils.isEndpoint(bind.target) ? [bind.target] : bind.target.members;
const endpoints = utils.isZHEndpoint(bind.target) ? [bind.target] : bind.target.members;
return endpoints.filter((e) => {
const supportsInputCluster = e.supportsInputCluster(bind.cluster.name);
const hasConfiguredReporting = !!e.configuredReportings.find((c) => c.cluster.name === bind.cluster.name);
const hasBind = !!e.binds.find((b) => b.cluster.name === bind.cluster.name && b.target === coordinatorEp);
return supportsInputCluster && !(hasBind && hasConfiguredReporting);
if (!e.supportsInputCluster(bind.cluster.name)) {
return false;
}
const hasConfiguredReporting = e.configuredReportings.some((c) => c.cluster.name === bind.cluster.name);
if (!hasConfiguredReporting) {
return true;
}
const hasBind = e.binds.some((b) => b.cluster.name === bind.cluster.name && b.target === coordinatorEp);
return !hasBind;
});
}
async setupReporting(binds: zh.Bind[]): Promise<void> {
const coordinatorEndpoint = this.zigbee.firstCoordinatorEndpoint();
for (const bind of binds.filter((b) => b.cluster.name in reportClusters)) {
for (const endpoint of this.getSetupReportingEndpoints(bind, coordinatorEndpoint)) {
const entity = `${this.zigbee.resolveEntity(endpoint.getDevice()).name}/${endpoint.ID}`;
try {
await endpoint.bind(bind.cluster.name, coordinatorEndpoint);
const items = [];
for (const c of reportClusters[bind.cluster.name]) {
/* istanbul ignore else */
if (!c.condition || await c.condition(endpoint)) {
const i = {...c};
delete i.condition;
items.push(i);
}
}
await endpoint.configureReporting(bind.cluster.name, items);
logger.info(`Successfully setup reporting for '${entity}' cluster '${bind.cluster.name}'`);
} catch (error) {
logger.warn(`Failed to setup reporting for '${entity}' cluster '${bind.cluster.name}'`);
for (const bind of binds) {
if (bind.cluster.name in REPORT_CLUSTERS) {
for (const endpoint of this.getSetupReportingEndpoints(bind, coordinatorEndpoint)) {
// biome-ignore lint/style/noNonNullAssertion: TODO: biome migration: ???
const resolvedDevice = this.zigbee.resolveEntity(endpoint.getDevice())!;
const entity = `${resolvedDevice.name}/${endpoint.ID}`;
try {
await endpoint.bind(bind.cluster.name, coordinatorEndpoint);
const items = [];
// biome-ignore lint/style/noNonNullAssertion: valid from outer `if`
for (const c of REPORT_CLUSTERS[bind.cluster.name as keyof typeof REPORT_CLUSTERS]!) {
if (!("condition" in c) || !c.condition || (await c.condition(endpoint))) {
const {attribute, minimumReportInterval, maximumReportInterval, reportableChange} = c;
items.push({attribute, minimumReportInterval, maximumReportInterval, reportableChange});
}
}
await endpoint.configureReporting(bind.cluster.name as keyof typeof REPORT_CLUSTERS, items);
logger.info(`Successfully setup reporting for '${entity}' cluster '${bind.cluster.name}'`);
} catch (error) {
logger.warning(`Failed to setup reporting for '${entity}' cluster '${bind.cluster.name}' (${(error as Error).message})`);
}
}
}
}
@@ -401,39 +513,52 @@ export default class Bind extends Extension {
async disableUnnecessaryReportings(target: zh.Group | zh.Endpoint): Promise<void> {
const coordinator = this.zigbee.firstCoordinatorEndpoint();
const endpoints = utils.isEndpoint(target) ? [target] : target.members;
const endpoints = utils.isZHEndpoint(target) ? [target] : target.members;
const allBinds: zh.Bind[] = [];
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
for (const endpoint of device.zh.endpoints) {
allBinds.push(...endpoint.binds);
}
}
for (const endpoint of endpoints) {
const device = this.zigbee.resolveEntity(endpoint.getDevice()) as Device;
const entity = `${device.name}/${endpoint.ID}`;
const boundClusters = endpoint.binds.filter((b) => b.target === coordinator)
.map((b) => b.cluster.name);
const requiredClusters = this.zigbee.devices(false).map((c) => c.zh.endpoints)
.reduce((a, v) => a.concat(v))
.map((e) => e.binds).reduce((a, v) => a.concat(v)).filter((bind) => {
if (utils.isEndpoint(bind.target)) {
return bind.target === endpoint;
} else {
return bind.target.members.includes(endpoint);
}
}).map((b) => b.cluster.name).filter((v, i, a) => a.indexOf(v) === i);
const requiredClusters: string[] = [];
const boundClusters: string[] = [];
for (const cluster of boundClusters.filter((c) => !requiredClusters.includes(c) && c in reportClusters)) {
for (const bind of allBinds) {
if (utils.isZHEndpoint(bind.target) ? bind.target === endpoint : bind.target.members.includes(endpoint)) {
requiredClusters.push(bind.cluster.name);
}
}
for (const b of endpoint.binds) {
if (b.target === coordinator && !requiredClusters.includes(b.cluster.name) && b.cluster.name in REPORT_CLUSTERS) {
boundClusters.push(b.cluster.name);
}
}
for (const cluster of boundClusters) {
try {
await endpoint.unbind(cluster, coordinator);
const items = [];
for (const item of reportClusters[cluster]) {
/* istanbul ignore else */
if (!item.condition || await item.condition(endpoint)) {
const i = {...item};
delete i.condition;
items.push({...i, maximumReportInterval: 0xFFFF});
// biome-ignore lint/style/noNonNullAssertion: valid from loop (pushed to array only if in)
for (const item of REPORT_CLUSTERS[cluster as keyof typeof REPORT_CLUSTERS]!) {
if (!("condition" in item) || !item.condition || (await item.condition(endpoint))) {
const {attribute, minimumReportInterval, reportableChange} = item;
items.push({attribute, minimumReportInterval, maximumReportInterval: 0xffff, reportableChange});
}
}
await endpoint.configureReporting(cluster, items);
await endpoint.configureReporting(cluster as keyof typeof REPORT_CLUSTERS, items);
logger.info(`Successfully disabled reporting for '${entity}' cluster '${cluster}'`);
} catch (error) {
logger.warn(`Failed to disable reporting for '${entity}' cluster '${cluster}'`);
logger.warning(`Failed to disable reporting for '${entity}' cluster '${cluster}' (${(error as Error).message})`);
}
}
@@ -450,48 +575,61 @@ export default class Bind extends Extension {
* When dimming the bulb via the dimmer switch the state is therefore not reported.
* When we receive a message from a Hue dimmer we read the brightness from the bulb (if bound).
*/
const polls = pollOnMessage.filter((p) =>
p.cluster[data.cluster]?.find((c) => c.type === data.type && utils.equalsPartial(data.data, c.data)));
const polls = POLL_ON_MESSAGE.filter((p) =>
p.cluster[data.cluster as keyof (typeof p)["cluster"]]?.some((c) => c.type === data.type && utils.equalsPartial(data.data, c.data)),
);
if (polls.length) {
const toPoll: Set<zh.Endpoint> = new Set();
const toPoll = new Set<zh.Endpoint>();
// Add bound devices
for (const endpoint of data.device.zh.endpoints) {
for (const bind of endpoint.binds) {
if (utils.isEndpoint(bind.target) && bind.target.getDevice().type !== 'Coordinator') {
if (utils.isZHEndpoint(bind.target) && bind.target.getDevice().type !== "Coordinator") {
toPoll.add(bind.target);
}
}
}
// If message is published to a group, add members of the group
const group = data.groupID && data.groupID !== 0 && this.zigbee.groupByID(data.groupID);
if (group) {
group.zh.members.forEach((m) => toPoll.add(m));
if (data.groupID && data.groupID !== 0) {
// If message is published to a group, add members of the group
const group = this.zigbee.groupByID(data.groupID);
if (group) {
for (const member of group.zh.members) {
toPoll.add(member);
}
}
}
for (const endpoint of toPoll) {
const device = endpoint.getDevice();
for (const poll of polls) {
if ((!poll.manufacturerIDs.includes(endpoint.getDevice().manufacturerID) &&
!poll.manufacturerNames.includes(endpoint.getDevice().manufacturerName)) ||
!endpoint.supportsInputCluster(poll.read.cluster)) {
if (
// biome-ignore lint/style/noNonNullAssertion: manufacturerID/manufacturerName can be undefined and won't match `includes`, but TS enforces same-type
(!poll.manufacturerIDs.includes(device.manufacturerID!) && !poll.manufacturerNames.includes(device.manufacturerName!)) ||
!endpoint.supportsInputCluster(poll.read.cluster)
) {
continue;
}
let readAttrs = poll.read.attributes;
if (poll.read.attributesForEndpoint) {
const attrsForEndpoint = await poll.read.attributesForEndpoint(endpoint);
readAttrs = [...poll.read.attributes, ...attrsForEndpoint];
}
const key = `${endpoint.getDevice().ieeeAddr}_${endpoint.ID}_${pollOnMessage.indexOf(poll)}`;
const key = `${device.ieeeAddr}_${endpoint.ID}_${POLL_ON_MESSAGE.indexOf(poll)}`;
if (!this.pollDebouncers[key]) {
this.pollDebouncers[key] = debounce(async () => {
try {
await endpoint.read(poll.read.cluster, readAttrs);
} catch (error) {
logger.error(`Failed to poll ${readAttrs} from ` +
`${this.zigbee.resolveEntity(endpoint.getDevice()).name}`);
// biome-ignore lint/style/noNonNullAssertion: TODO: biome migration: ???
const resolvedDevice = this.zigbee.resolveEntity(device)!;
logger.error(`Failed to poll ${readAttrs} from ${resolvedDevice.name} (${(error as Error).message})`);
}
}, 1000);
}
+603 -405
View File
File diff suppressed because it is too large Load Diff
+91 -72
View File
@@ -1,136 +1,155 @@
import * as settings from '../util/settings';
import utils from '../util/utils';
import logger from '../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import zhc from 'zigbee-herdsman-converters';
import Extension from './extension';
import bind from 'bind-decorator';
import Device from '../model/device';
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import Device from "../model/device";
import type {Zigbee2MQTTAPI} from "../types/api";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
/**
* This extension calls the zigbee-herdsman-converters definition configure() method
*/
export default class Configure extends Extension {
private configuring = new Set();
private attempts: {[s: string]: number} = {};
private configuring = new Set<string>();
private attempts = new Map<string, number>();
private topic = `${settings.get().mqtt.base_topic}/bridge/request/device/configure`;
private legacyTopic = `${settings.get().mqtt.base_topic}/bridge/configure`;
@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')) {
if (data.device.zh.meta?.configured !== undefined) {
delete data.device.zh.meta.configured;
data.device.zh.save();
}
await this.configure(data.device, 'reporting_disabled');
await this.configure(data.device, "reporting_disabled");
}
@bind private async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
if (data.topic === this.legacyTopic) {
const device = this.zigbee.resolveEntity(data.message);
if (!device || !(device instanceof Device)) {
logger.error(`Device '${data.message}' does not exist`);
return;
}
if (data.topic === this.topic) {
const message = utils.parseJSON(data.message, data.message) as Zigbee2MQTTAPI["bridge/request/device/configure"];
const ID = typeof message === "object" ? message.id : message;
let error: string | undefined;
if (!device.definition || !device.definition.configure) {
logger.warn(`Skipping configure of '${device.name}', device does not require this.`);
return;
}
this.configure(device, 'mqtt_message', true);
} else if (data.topic === this.topic) {
const message = utils.parseJSON(data.message, data.message);
const ID = typeof message === 'object' && message.hasOwnProperty('id') ? message.id : message;
let error = null;
const device = this.zigbee.resolveEntity(ID);
if (!device || !(device instanceof Device)) {
error = `Device '${ID}' does not exist`;
} else if (!device.definition || !device.definition.configure) {
error = `Device '${device.name}' cannot be configured`;
if (ID === undefined) {
error = "Invalid payload";
} else {
try {
await this.configure(device, 'mqtt_message', true, true);
} catch (e) {
error = `Failed to configure (${e.message})`;
const device = this.zigbee.resolveEntity(ID);
if (!device || !(device instanceof Device)) {
error = `Device '${ID}' does not exist`;
} else if (!device.definition?.configure) {
error = `Device '${device.name}' cannot be configured`;
} else {
try {
await this.configure(device, "mqtt_message", true, true);
} catch (e) {
error = `Failed to configure (${(e as Error).message})`;
}
}
}
const response = utils.getResponse(message, {id: ID}, error);
await this.mqtt.publish(`bridge/response/device/configure`, stringify(response));
const response = utils.getResponse<"bridge/response/device/configure">(message, {id: ID}, error);
await this.mqtt.publish("bridge/response/device/configure", stringify(response));
}
}
override async start(): Promise<void> {
override start(): Promise<void> {
setImmediate(async () => {
for (const device of this.zigbee.devices(false)) {
await this.configure(device, 'started');
// Only configure routers on startup, end devices are likely sleeping and
// will reconfigure once they send a message
for (const device of this.zigbee.devicesIterator((d) => d.type === "Router")) {
// Sleep 10 seconds between configuring on startup to not DDoS the coordinator when many devices have to be configured.
await utils.sleep(10);
await this.configure(device, "started");
}
});
this.eventBus.onDeviceJoined(this, (data) => {
if (data.device.zh.meta.hasOwnProperty('configured')) {
this.eventBus.onDeviceJoined(this, async (data) => {
if (data.device.zh.meta.configured !== undefined) {
delete data.device.zh.meta.configured;
data.device.zh.save();
}
this.configure(data.device, 'zigbee_event');
await this.configure(data.device, "zigbee_event");
});
this.eventBus.onDeviceInterview(this, (data) => this.configure(data.device, 'zigbee_event'));
this.eventBus.onLastSeenChanged(this, (data) => this.configure(data.device, 'zigbee_event'));
// TODO: this is triggering for any `data.status`, but should only for `successful`? (relies on `device.definition?` early-return?)
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.onReconfigure(this, this.onReconfigure);
return Promise.resolve();
}
private async configure(device: Device, event: 'started' | 'zigbee_event' | 'reporting_disabled' | 'mqtt_message',
force=false, throwError=false): Promise<void> {
if (!force) {
if (device.options.disabled || !device.definition?.configure || !device.zh.interviewCompleted) {
return;
}
private async configure(
device: Device,
event: "started" | "zigbee_event" | "reporting_disabled" | "mqtt_message",
force = false,
throwError = false,
): Promise<void> {
if (!device.definition?.configure) {
return;
}
if (device.zh.meta?.hasOwnProperty('configured') &&
device.zh.meta.configured === zhc.getConfigureKey(device.definition)) {
const definitionVersion = device.definition.version;
if (!force) {
if (device.options.disabled || !device.interviewed) {
return;
}
// Only configure end devices when it is active, otherwise it will likely fails as they are sleeping.
if (device.zh.type === 'EndDevice' && event !== 'zigbee_event') {
if (device.zh.type === "EndDevice" && event !== "zigbee_event") {
return;
}
const shouldReconfigure =
// Should always reconfigure when not configured before
device.zh.meta?.configured === undefined ||
// Or should reconfigure when definition.version is not '0.0.0' and differs from last `meta.configured`.
// In older Z2M versions the stored `meta.configured` was the hash of the configure function.
// Since we don't want to reconfigure all devices, we don't re-configure when the definition has the default version of '0.0.0'.
(definitionVersion !== "0.0.0" && device.zh.meta?.configured !== definitionVersion);
if (!shouldReconfigure) {
return;
}
}
if (this.configuring.has(device.ieeeAddr) || (this.attempts[device.ieeeAddr] >= 3 && !force)) {
if (this.configuring.has(device.ieeeAddr)) {
return;
}
const attempts = this.attempts.get(device.ieeeAddr) ?? 0;
if (attempts >= 3 && !force) {
return;
}
this.configuring.add(device.ieeeAddr);
if (!this.attempts.hasOwnProperty(device.ieeeAddr)) {
this.attempts[device.ieeeAddr] = 0;
}
logger.info(`Configuring '${device.name}'`);
try {
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);
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), device.definition);
this.attempts.delete(device.ieeeAddr);
logger.info(`Successfully configured '${device.name}' (definition v${definitionVersion})`);
device.zh.meta.configured = definitionVersion;
device.zh.save();
this.eventBus.emitDevicesChanged();
} catch (error) {
this.attempts[device.ieeeAddr]++;
const attempt = this.attempts[device.ieeeAddr];
const msg = `Failed to configure '${device.name}', attempt ${attempt} (${error.stack})`;
const newAttempts = attempts + 1;
this.attempts.set(device.ieeeAddr, newAttempts);
const msg = `Failed to configure '${device.name}', attempt ${newAttempts} (${(error as Error).stack})`;
logger.error(msg);
if (throwError) {
throw error;
}
} finally {
this.configuring.delete(device.ieeeAddr);
}
this.configuring.delete(device.ieeeAddr);
}
}
+17 -10
View File
@@ -1,18 +1,18 @@
abstract class Extension {
protected zigbee: Zigbee;
protected mqtt: MQTT;
protected mqtt: Mqtt;
protected state: State;
protected publishEntityState: PublishEntityState;
protected eventBus: EventBus;
protected enableDisableExtension: (enable: boolean, name: string) => Promise<void>;
protected restartCallback: () => void;
protected restartCallback: () => Promise<void>;
protected addExtension: (extension: Extension) => Promise<void>;
/**
* Besides intializing variables, the constructor should do nothing!
* Besides initializing variables, the constructor should do nothing!
*
* @param {Zigbee} zigbee Zigbee controller
* @param {MQTT} mqtt MQTT controller
* @param {Mqtt} mqtt MQTT controller
* @param {State} state State controller
* @param {Function} publishEntityState Method to publish device state to MQTT.
* @param {EventBus} eventBus The event bus
@@ -20,9 +20,16 @@ abstract class Extension {
* @param {restartCallback} restartCallback Restart Zigbee2MQTT
* @param {addExtension} addExtension Add an extension
*/
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>) {
constructor(
zigbee: Zigbee,
mqtt: Mqtt,
state: State,
publishEntityState: PublishEntityState,
eventBus: EventBus,
enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
restartCallback: () => Promise<void>,
addExtension: (extension: Extension) => Promise<void>,
) {
this.zigbee = zigbee;
this.mqtt = mqtt;
this.state = state;
@@ -36,18 +43,18 @@ abstract class Extension {
/**
* Is called once the extension has to start
*/
/* istanbul ignore next */
async start(): Promise<void> {}
/**
* Is called once the extension has to stop
*/
// biome-ignore lint/suspicious/useAwait: API
async stop(): Promise<void> {
this.eventBus.removeListeners(this);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public adjustMessageBeforePublish(entity: Group | Device, message: KeyValue): void {}
public adjustMessageBeforePublish(_entity: Group | Device, _message: KeyValue): void {}
}
export default Extension;
+62 -13
View File
@@ -1,18 +1,67 @@
import zhc from 'zigbee-herdsman-converters';
import * as settings from '../util/settings';
import utils from '../util/utils';
import Extension from './extension';
import type {ExternalDefinitionWithExtend} from "zigbee-herdsman-converters";
export default class ExternalConverters extends Extension {
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);
import {addExternalDefinition, removeExternalDefinitions} from "zigbee-herdsman-converters";
for (const definition of utils.getExternalConvertersDefinitions(settings.get())) {
const toAdd = {...definition};
delete toAdd['homeassistant'];
zhc.addDeviceDefinition(toAdd);
import logger from "../util/logger";
import ExternalJSExtension from "./externalJS";
type TModule = ExternalDefinitionWithExtend | ExternalDefinitionWithExtend[];
export default class ExternalConverters extends ExternalJSExtension<TModule> {
constructor(
zigbee: Zigbee,
mqtt: Mqtt,
state: State,
publishEntityState: PublishEntityState,
eventBus: EventBus,
enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
restartCallback: () => Promise<void>,
addExtension: (extension: Extension) => Promise<void>,
) {
super(
zigbee,
mqtt,
state,
publishEntityState,
eventBus,
enableDisableExtension,
restartCallback,
addExtension,
"converter",
"external_converters",
);
}
protected async removeJS(name: string, _mod: TModule): Promise<void> {
removeExternalDefinitions(name);
await this.zigbee.resolveDevicesDefinitions(true);
}
protected async loadJS(name: string, mod: TModule, newName?: string): Promise<void> {
try {
removeExternalDefinitions(name);
const definitions = Array.isArray(mod) ? mod : [mod];
for (const definition of definitions) {
definition.externalConverterName = newName ?? name;
addExternalDefinition(definition);
logger.info(`Loaded external converter '${newName ?? name}'.`);
}
await this.zigbee.resolveDevicesDefinitions(true);
} catch (error) {
logger.error(
/* v8 ignore next */
`Failed to load external converter '${newName ?? name}'. Check the code for syntax error and make sure it is up to date with the current Zigbee2MQTT version.`,
);
logger.warning(
"External converters are not meant for long term usage, but for local testing after which a pull request should be created to add out-of-the-box support for the device",
);
throw error;
}
}
}
-109
View File
@@ -1,109 +0,0 @@
import * as settings from '../util/settings';
import utils from '../util/utils';
import fs from 'fs';
import data from './../util/data';
import path from 'path';
import logger from './../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import bind from 'bind-decorator';
import Extension from './extension';
const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/extension/(save|remove)`);
export default class ExternalExtension extends Extension {
private requestLookup: {[s: string]: (message: KeyValue) => Promise<MQTTResponse>};
override async start(): Promise<void> {
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.requestLookup = {'save': this.saveExtension, 'remove': this.removeExtension};
this.loadUserDefinedExtensions();
await this.publishExtensions();
}
private getExtensionsBasePath(): string {
return data.joinPath('extension');
}
private getListOfUserDefinedExtensions(): {name: string, code: string}[] {
const basePath = this.getExtensionsBasePath();
if (fs.existsSync(basePath)) {
return fs.readdirSync(basePath).filter((f) => f.endsWith('.js')).map((fileName) => {
const extensionFilePath = path.join(basePath, fileName);
return {'name': fileName, 'code': fs.readFileSync(extensionFilePath, 'utf-8')};
});
} else {
return [];
}
}
@bind private async removeExtension(message: KeyValue): Promise<MQTTResponse> {
const {name} = message;
const extensions = this.getListOfUserDefinedExtensions();
const extensionToBeRemoved = extensions.find((e) => e.name === name);
if (extensionToBeRemoved) {
await this.enableDisableExtension(false, extensionToBeRemoved.name);
const basePath = this.getExtensionsBasePath();
const extensionFilePath = path.join(basePath, path.basename(name));
fs.unlinkSync(extensionFilePath);
this.publishExtensions();
logger.info(`Extension ${name} removed`);
return utils.getResponse(message, {}, null);
} else {
return utils.getResponse(message, {}, `Extension ${name} doesn't exists`);
}
}
@bind private async saveExtension(message: KeyValue): Promise<MQTTResponse> {
const {name, code} = message;
const ModuleConstructor = utils.loadModuleFromText(code, name) as typeof Extension;
await this.loadExtension(ModuleConstructor);
const basePath = this.getExtensionsBasePath();
/* istanbul ignore else */
if (!fs.existsSync(basePath)) {
fs.mkdirSync(basePath);
}
const extensionFilePath = path.join(basePath, path.basename(name));
fs.writeFileSync(extensionFilePath, code);
this.publishExtensions();
logger.info(`Extension ${name} loaded`);
return utils.getResponse(message, {}, null);
}
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
const match = data.topic.match(requestRegex);
if (match && this.requestLookup[match[1].toLowerCase()]) {
const message = utils.parseJSON(data.message, data.message) as KeyValue;
try {
const response = await this.requestLookup[match[1].toLowerCase()](message);
await this.mqtt.publish(`bridge/response/extension/${match[1]}`, stringify(response));
} catch (error) {
logger.error(`Request '${data.topic}' failed with error: '${error.message}'`);
const response = utils.getResponse(message, {}, error.message);
await this.mqtt.publish(`bridge/response/extension/${match[1]}`, stringify(response));
}
}
}
@bind private async loadExtension(ConstructorClass: typeof Extension): Promise<void> {
await this.enableDisableExtension(false, ConstructorClass.name);
// @ts-ignore
await this.addExtension(new ConstructorClass(this.zigbee, this.mqtt, this.state, this.publishEntityState,
this.eventBus, settings, logger));
}
private loadUserDefinedExtensions(): void {
const extensions = this.getListOfUserDefinedExtensions();
extensions
.map(({code, name}) => utils.loadModuleFromText(code, name))
.map(this.loadExtension);
}
private async publishExtensions(): Promise<void> {
const extensions = this.getListOfUserDefinedExtensions();
await this.mqtt.publish('bridge/extensions', stringify(extensions), {
retain: true,
qos: 0,
}, settings.get().mqtt.base_topic, true);
}
}
+69
View File
@@ -0,0 +1,69 @@
import logger from "../util/logger";
import * as settings from "../util/settings";
import type Extension from "./extension";
import ExternalJSExtension from "./externalJS";
type TModule = new (...args: ConstructorParameters<typeof Extension>) => Extension;
export default class ExternalExtensions extends ExternalJSExtension<TModule> {
constructor(
zigbee: Zigbee,
mqtt: Mqtt,
state: State,
publishEntityState: PublishEntityState,
eventBus: EventBus,
enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
restartCallback: () => Promise<void>,
addExtension: (extension: Extension) => Promise<void>,
) {
super(
zigbee,
mqtt,
state,
publishEntityState,
eventBus,
enableDisableExtension,
restartCallback,
addExtension,
"extension",
"external_extensions",
);
}
protected async removeJS(_name: string, mod: TModule): Promise<void> {
await this.enableDisableExtension(false, mod.name);
}
protected async loadJS(name: string, mod: TModule, newName?: string): Promise<void> {
try {
// stop if already started
await this.enableDisableExtension(false, mod.name);
await this.addExtension(
new mod(
this.zigbee,
this.mqtt,
this.state,
this.publishEntityState,
this.eventBus,
this.enableDisableExtension,
this.restartCallback,
this.addExtension,
// @ts-expect-error additional params that don't fit the internal `Extension` type
settings,
logger,
),
);
/* v8 ignore start */
logger.info(`Loaded external extension '${newName ?? name}'.`);
} catch (error) {
logger.error(
/* v8 ignore next */
`Failed to load external extension '${newName ?? name}'. Check the code for syntax error and make sure it is up to date with the current Zigbee2MQTT version.`,
);
throw error;
}
/* v8 ignore stop */
}
}
+245
View File
@@ -0,0 +1,245 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import type {Zigbee2MQTTAPI, Zigbee2MQTTResponse} from "../types/api";
import data from "../util/data";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
const SUPPORTED_OPERATIONS = ["save", "remove"];
const TMP_PREFIX = ".tmp-ed42d4f2-";
export default abstract class ExternalJSExtension<M> extends Extension {
protected folderName: string;
protected mqttTopic: string;
protected requestRegex: RegExp;
protected basePath: string;
protected nodeModulesSymlinked = false;
constructor(
zigbee: Zigbee,
mqtt: Mqtt,
state: State,
publishEntityState: PublishEntityState,
eventBus: EventBus,
enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
restartCallback: () => Promise<void>,
addExtension: (extension: Extension) => Promise<void>,
mqttTopic: string,
folderName: string,
) {
super(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension, restartCallback, addExtension);
this.folderName = folderName;
this.mqttTopic = mqttTopic;
this.requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/${mqttTopic}/(save|remove)`);
this.basePath = data.joinPath(folderName);
}
/**
* In case the external JS is not in the Z2M install dir (e.g. when `ZIGBEE2MQTT_DATA` is used), the external
* JS cannot import from `node_modules`.
* To workaround this create a symlink to `node_modules` in the external JS dir.
* https://nodejs.org/api/esm.html#no-node_path
*/
private symlinkNodeModulesIfNecessary() {
if (!this.nodeModulesSymlinked) {
this.nodeModulesSymlinked = true;
const nodeModulesPath = path.join(__dirname, "..", "..", "node_modules");
const z2mDirNormalized = `${path.resolve(path.join(nodeModulesPath, ".."))}${path.sep}`;
const basePathNormalized = `${path.resolve(this.basePath)}${path.sep}`;
const basePathInZ2mDir = basePathNormalized.startsWith(z2mDirNormalized);
if (!basePathInZ2mDir) {
logger.debug(`External JS folder '${this.folderName}' is outside the Z2M install dir, creating a symlink to 'node_modules'`);
const nodeModulesSymlink = path.join(this.basePath, "node_modules");
/* v8 ignore start */
if (fs.existsSync(nodeModulesSymlink)) {
fs.unlinkSync(nodeModulesSymlink);
}
/* v8 ignore stop */
// Type `junction` is required on Windows.
// https://github.com/nodejs/node/issues/18518#issuecomment-513866491
fs.symlinkSync(nodeModulesPath, nodeModulesSymlink, /* v8 ignore next */ os.platform() === "win32" ? "junction" : "dir");
}
}
}
override async start(): Promise<void> {
await super.start();
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
await this.loadFiles();
await this.publishExternalJS();
}
override async stop(): Promise<void> {
await super.stop();
// ensure "node_modules" is never followed & included in 3rd-party backup systems
const nodeModulesSymlink = path.join(this.basePath, "node_modules");
if (fs.existsSync(nodeModulesSymlink)) {
fs.unlinkSync(nodeModulesSymlink);
}
}
private getFilePath(name: string, mkBasePath = false): string {
if (mkBasePath && !fs.existsSync(this.basePath)) {
fs.mkdirSync(this.basePath, {recursive: true});
}
return path.join(this.basePath, name);
}
protected getFileCode(name: string): string {
return fs.readFileSync(this.getFilePath(name), "utf8");
}
protected *getFiles(): Generator<{name: string; code: string}> {
if (fs.existsSync(this.basePath)) {
for (const fileName of fs.readdirSync(this.basePath)) {
if (!fileName.startsWith(TMP_PREFIX) && (fileName.endsWith(".js") || fileName.endsWith(".cjs") || fileName.endsWith(".mjs"))) {
yield {name: fileName, code: this.getFileCode(fileName)};
}
}
}
}
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
const match = data.topic.match(this.requestRegex);
if (match && SUPPORTED_OPERATIONS.includes(match[1].toLowerCase())) {
const message = utils.parseJSON(data.message, data.message);
try {
let response: Awaited<ReturnType<typeof this.save | typeof this.remove>>;
if (match[1].toLowerCase() === "save") {
response = await this.save(
message as Zigbee2MQTTAPI["bridge/request/converter/save"] | Zigbee2MQTTAPI["bridge/request/extension/save"],
);
} else {
response = await this.remove(
message as Zigbee2MQTTAPI["bridge/request/converter/remove"] | Zigbee2MQTTAPI["bridge/request/extension/remove"],
);
}
await this.mqtt.publish(`bridge/response/${this.mqttTopic}/${match[1]}`, stringify(response));
} catch (error) {
logger.error(`Request '${data.topic}' failed with error: '${(error as Error).message}'`);
const response = utils.getResponse(message, {}, `${(error as Error).message}`);
await this.mqtt.publish(`bridge/response/${this.mqttTopic}/${match[1]}`, stringify(response));
}
}
}
protected abstract removeJS(name: string, mod: M): Promise<void>;
protected abstract loadJS(name: string, mod: M, newName?: string): Promise<void>;
@bind private async remove(
message: Zigbee2MQTTAPI["bridge/request/converter/remove"] | Zigbee2MQTTAPI["bridge/request/extension/remove"],
): Promise<Zigbee2MQTTResponse<"bridge/response/converter/remove" | "bridge/response/extension/remove">> {
if (!message.name) {
return utils.getResponse(message, {}, "Invalid payload");
}
const {name} = message;
const toBeRemoved = this.getFilePath(name);
if (fs.existsSync(toBeRemoved)) {
const mod = await this.importFile(toBeRemoved);
await this.removeJS(name, mod.default);
fs.rmSync(toBeRemoved, {force: true});
logger.info(`${name} (${toBeRemoved}) removed.`);
await this.publishExternalJS();
return utils.getResponse(message, {});
}
return utils.getResponse(message, {}, `${name} (${toBeRemoved}) doesn't exists`);
}
@bind private async save(
message: Zigbee2MQTTAPI["bridge/request/converter/save"] | Zigbee2MQTTAPI["bridge/request/extension/save"],
): Promise<Zigbee2MQTTResponse<"bridge/response/converter/save" | "bridge/response/extension/save">> {
if (!message.name || !message.code) {
return utils.getResponse(message, {}, "Invalid payload");
}
const {name, code} = message;
if (!name.endsWith(".mjs") && !name.endsWith(".js") && !name.endsWith(".cjs")) {
return utils.getResponse(message, {}, "JavaScript file must have '.mjs', '.js' or '.cjs' extension");
}
const filePath = this.getFilePath(name, true);
try {
fs.writeFileSync(filePath, code, "utf8");
this.symlinkNodeModulesIfNecessary();
const mod = await this.importFile(filePath);
await this.loadJS(name, mod.default, name);
logger.info(`${name} loaded. Contents written to '${filePath}'.`);
await this.publishExternalJS();
return utils.getResponse(message, {});
} catch (error) {
return utils.getResponse(message, {}, `${name} contains invalid code: ${(error as Error).message}`);
}
}
private async loadFiles(): Promise<void> {
for (const extension of this.getFiles()) {
this.symlinkNodeModulesIfNecessary();
const filePath = this.getFilePath(extension.name);
try {
const mod = await this.importFile(filePath);
await this.loadJS(extension.name, mod.default);
} catch (error) {
logger.error(
`Invalid external ${this.mqttTopic} '${extension.name}' was ignored and renamed to prevent interference with Zigbee2MQTT. (${(error as Error).message})`,
);
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((error as Error).stack!);
// change ext so Z2M doesn't try to load it again and again
fs.renameSync(filePath, `${filePath}.invalid`);
}
}
}
private async publishExternalJS(): Promise<void> {
await this.mqtt.publish(`bridge/${this.mqttTopic}s`, stringify(Array.from(this.getFiles())), {
clientOptions: {retain: true},
skipLog: true,
});
}
// biome-ignore lint/suspicious/noExplicitAny: dynamic module
private async importFile(file: string): Promise<any> {
const ext = path.extname(file);
// Create the file in a temp path to bypass node module cache when importing multiple times.
const tmpFile = path.join(this.basePath, `${TMP_PREFIX}${path.basename(file, ext)}-${crypto.randomUUID()}${ext}`);
fs.copyFileSync(file, tmpFile);
try {
// Do `replaceAll("\\", "/")` to prevent issues on Windows
/* v8 ignore next */
const mod = await import(os.platform() === "win32" ? `file:///${tmpFile.replaceAll("\\", "/")}` : tmpFile);
return mod;
} finally {
fs.rmSync(tmpFile);
}
}
}
+187 -118
View File
@@ -1,142 +1,200 @@
import http from 'http';
import https from 'https';
import gzipStatic, {RequestHandler} from 'connect-gzip-static';
import finalhandler from 'finalhandler';
import logger from '../util/logger';
import frontend from 'zigbee2mqtt-frontend';
import WebSocket from 'ws';
import net from 'net';
import url from 'url';
import fs from 'fs';
import * as settings from '../util/settings';
import utils from '../util/utils';
import stringify from 'json-stable-stringify-without-jsonify';
import Extension from './extension';
import bind from 'bind-decorator';
import assert from "node:assert";
import {existsSync, readFileSync} from "node:fs";
import type {IncomingMessage, Server, ServerResponse} from "node:http";
import {createServer} from "node:http";
import {createServer as createSecureServer} from "node:https";
import type {Socket} from "node:net";
import {posix} from "node:path";
import bind from "bind-decorator";
import expressStaticGzip from "express-static-gzip";
import finalhandler from "finalhandler";
import stringify from "json-stable-stringify-without-jsonify";
import WebSocket from "ws";
import data from "../util/data";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
/**
* This extension servers the frontend
*/
export default class Frontend extends Extension {
private mqttBaseTopic = settings.get().mqtt.base_topic;
private host = settings.get().frontend.host;
private port = settings.get().frontend.port;
private sslCert = settings.get().frontend.ssl_cert;
private sslKey = settings.get().frontend.ssl_key;
private authToken = settings.get().frontend.auth_token;
private retainedMessages = new Map();
private server: http.Server;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private fileServer: RequestHandler;
private wss: WebSocket.Server = null;
export class Frontend extends Extension {
private mqttBaseTopic: string;
private server: Server | undefined;
private wss!: WebSocket.Server;
private baseUrl: 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>) {
constructor(
zigbee: Zigbee,
mqtt: Mqtt,
state: State,
publishEntityState: PublishEntityState,
eventBus: EventBus,
enableDisableExtension: (enable: boolean, name: string) => Promise<void>,
restartCallback: () => Promise<void>,
addExtension: (extension: Extension) => Promise<void>,
) {
super(zigbee, mqtt, state, publishEntityState, eventBus, enableDisableExtension, restartCallback, addExtension);
this.eventBus.onMQTTMessagePublished(this, this.onMQTTPublishMessage);
}
private isHttpsConfigured():boolean {
if (this.sslCert && this.sslKey) {
if (!fs.existsSync(this.sslCert) || !fs.existsSync(this.sslKey)) {
logger.error(`defined ssl_cert '${this.sslCert}' or ssl_key '${this.sslKey}' file path does not exists, server won't be secured.`); /* eslint-disable-line max-len */
return false;
}
return true;
}
return false;
const frontendSettings = settings.get().frontend;
assert(frontendSettings.enabled, `Frontend extension created with setting 'enabled: false'`);
this.baseUrl = frontendSettings.base_url;
this.mqttBaseTopic = settings.get().mqtt.base_topic;
}
override async start(): Promise<void> {
if (this.isHttpsConfigured()) {
const serverOptions = {
key: fs.readFileSync(this.sslKey),
cert: fs.readFileSync(this.sslCert)};
this.server = https.createServer(serverOptions, this.onRequest);
if (settings.get().frontend.disable_ui_serving) {
const {host, port} = settings.get().frontend;
this.wss = new WebSocket.Server({port, host, path: posix.join(this.baseUrl, "api")});
logger.info(
/* v8 ignore next */
`Frontend UI serving is disabled. WebSocket at: ${this.wss.options.host ?? "0.0.0.0"}:${this.wss.options.port}${this.wss.options.path}`,
);
} else {
this.server = http.createServer(this.onRequest);
}
const {host, port, ssl_key: sslKey, ssl_cert: sslCert} = settings.get().frontend;
const hasSSL = (val: string | undefined, key: string): val is string => {
if (val) {
if (existsSync(val)) {
return true;
}
this.server.on('upgrade', this.onUpgrade);
/* istanbul ignore next */
const options = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setHeaders: (res: any, path: string): void => {
if (path.endsWith('index.html')) {
res.setHeader('Cache-Control', 'no-store');
logger.error(`Defined ${key} '${val}' file path does not exists, server won't be secured.`);
}
},
};
this.fileServer = gzipStatic(frontend.getPath(), options);
this.wss = new WebSocket.Server({noServer: true});
this.wss.on('connection', this.onWebSocketConnection);
if (this.host.startsWith('/')) {
this.server.listen(this.host);
logger.info(`Started frontend on socket ${this.host}`);
} else {
this.server.listen(this.port, this.host);
logger.info(`Started frontend on port ${this.host}:${this.port}`);
return false;
};
const options: expressStaticGzip.ExpressStaticGzipOptions = {
enableBrotli: true,
serveStatic: {
/* v8 ignore start */
setHeaders: (res: ServerResponse, path: string): void => {
if (path.endsWith("index.html")) {
res.setHeader("Cache-Control", "no-store");
}
},
/* v8 ignore stop */
},
};
const frontend = (await import(settings.get().frontend.package)) as typeof import("zigbee2mqtt-frontend");
const fileServer = expressStaticGzip(frontend.default.getPath(), options);
const deviceIconsFileServer = expressStaticGzip(data.joinPath("device_icons"), options);
const onRequest = (request: IncomingMessage, response: ServerResponse): void => {
const next = finalhandler(request, response);
// biome-ignore lint/style/noNonNullAssertion: `Only valid for request obtained from Server`
const newUrl = posix.relative(this.baseUrl, request.url!);
// The request url is not within the frontend base url, so the relative path starts with '..'
if (newUrl.startsWith(".")) {
next();
return;
}
// Attach originalUrl so that static-server can perform a redirect to '/' when serving the root directory.
// This is necessary for the browser to resolve relative assets paths correctly.
request.originalUrl = request.url;
request.url = `/${newUrl}`;
request.path = request.url;
if (newUrl.startsWith("device_icons/")) {
request.path = request.path.replace("device_icons/", "");
request.url = request.url.replace("/device_icons", "");
deviceIconsFileServer(request, response, next);
} else {
fileServer(request, response, next);
}
};
if (hasSSL(sslKey, "ssl_key") && hasSSL(sslCert, "ssl_cert")) {
const serverOptions = {key: readFileSync(sslKey), cert: readFileSync(sslCert)};
this.server = createSecureServer(serverOptions, onRequest);
} else {
this.server = createServer(onRequest);
}
this.server.on("upgrade", this.onUpgrade);
if (!host) {
this.server.listen(port);
logger.info(`Started frontend on port ${port}`);
} else if (host.startsWith("/")) {
this.server.listen(host);
logger.info(`Started frontend on socket ${host}`);
} else {
this.server.listen(port, host);
logger.info(`Started frontend on port ${host}:${port}`);
}
this.wss = new WebSocket.Server({noServer: true, path: posix.join(this.baseUrl, "api")});
}
this.wss.on("connection", this.onWebSocketConnection);
this.eventBus.onMQTTMessagePublished(this, this.onMQTTPublishMessageOrEntityState);
this.eventBus.onPublishEntityState(this, this.onMQTTPublishMessageOrEntityState);
}
override async stop(): Promise<void> {
super.stop();
this.wss?.clients.forEach((client) => {
client.send(stringify({topic: 'bridge/state', payload: 'offline'}));
client.terminate();
});
this.wss?.close();
/* istanbul ignore else */
if (this.server) {
return new Promise((cb: () => void) => this.server.close(cb));
await super.stop();
if (this.wss) {
for (const client of this.wss.clients) {
client.send(stringify({topic: "bridge/state", payload: {state: "offline"}}));
client.terminate();
}
this.wss.close();
}
await new Promise((resolve) => (this.server ? this.server.close(resolve) : resolve(undefined)));
}
@bind private onRequest(request: http.IncomingMessage, response: http.ServerResponse): void {
// @ts-ignore
this.fileServer(request, response, finalhandler(request, response));
}
private authenticate(request: http.IncomingMessage, cb: (authenticate: boolean) => void): void {
const {query} = url.parse(request.url, true);
cb(!this.authToken || this.authToken === query.token);
}
@bind private onUpgrade(request: http.IncomingMessage, socket: net.Socket, head: Buffer): void {
@bind private onUpgrade(request: IncomingMessage, socket: Socket, head: Buffer): void {
this.wss.handleUpgrade(request, socket, head, (ws) => {
this.authenticate(request, (isAuthenticated) => {
if (isAuthenticated) {
this.wss.emit('connection', ws, request);
} else {
ws.close(4401, 'Unauthorized');
}
});
// biome-ignore lint/style/noNonNullAssertion: `Only valid for request obtained from Server`
const {searchParams} = new URL(request.url!, "http://localhost"); // dummy base, may not be absolute
const authToken = settings.get().frontend.auth_token;
if (!authToken || authToken === searchParams.get("token")) {
this.wss.emit("connection", ws, request);
} else {
ws.close(4401, "Unauthorized");
}
});
}
@bind private onWebSocketConnection(ws: WebSocket): void {
ws.on('message', (data: Buffer, isBinary: boolean) => {
ws.on("error", (msg) => logger.error(`WebSocket error: ${msg.message}`));
ws.on("message", (data: Buffer, isBinary: boolean) => {
if (!isBinary && data) {
const message = data.toString();
const {topic, payload} = JSON.parse(message);
this.mqtt.onMessage(`${this.mqttBaseTopic}/${topic}`, stringify(payload));
this.mqtt.onMessage(`${this.mqttBaseTopic}/${topic}`, Buffer.from(stringify(payload)));
}
});
for (const [key, value] of this.retainedMessages) {
ws.send(stringify({topic: key, payload: value}));
for (const [topic, payload] of Object.entries(this.mqtt.retainedMessages)) {
if (topic.startsWith(`${this.mqttBaseTopic}/`)) {
ws.send(
stringify({
// Send topic without base_topic
topic: topic.substring(this.mqttBaseTopic.length + 1),
payload: utils.parseJSON(payload.payload, payload.payload),
}),
);
}
}
for (const device of this.zigbee.devices(false)) {
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
const payload = this.state.get(device);
const lastSeen = settings.get().advanced.last_seen;
/* istanbul ignore if */
if (lastSeen !== 'disable') {
payload.last_seen = utils.formatDate(device.zh.lastSeen, lastSeen);
if (lastSeen !== "disable") {
payload.last_seen = utils.formatDate(device.zh.lastSeen ?? /* v8 ignore next */ 0, lastSeen);
}
if (device.zh.linkquality !== undefined) {
@@ -147,23 +205,34 @@ export default class Frontend extends Extension {
}
}
@bind private onMQTTPublishMessage(data: eventdata.MQTTMessagePublished): void {
if (data.topic.startsWith(`${this.mqttBaseTopic}/`)) {
// Send topic without base_topic
const topic = data.topic.substring(this.mqttBaseTopic.length + 1);
const payload = utils.parseJSON(data.payload, data.payload);
if (data.options.retain) {
this.retainedMessages.set(topic, payload);
}
@bind private onMQTTPublishMessageOrEntityState(data: eventdata.MQTTMessagePublished | eventdata.PublishEntityState): void {
let topic: string;
let payload: KeyValue | string;
if (this.wss) {
for (const client of this.wss.clients) {
/* istanbul ignore else */
if (client.readyState === WebSocket.OPEN) {
client.send(stringify({topic, payload}));
}
}
if ("topic" in data) {
// MQTTMessagePublished
if (data.options.meta.isEntityState || !data.topic.startsWith(`${this.mqttBaseTopic}/`)) {
// Don't send entity state to frontend on `MQTTMessagePublished` event, this is handled by
// `PublishEntityState` instead. Reason for this is to skip attribute messages when `output` is
// set to `attribute` or `attribute_and_json`, we only want to send JSON entity states to the
// frontend.
return;
}
// Send topic without base_topic
topic = data.topic.substring(this.mqttBaseTopic.length + 1);
payload = utils.parseJSON(data.payload, data.payload);
} else {
// PublishEntityState
topic = data.entity.name;
payload = data.message;
}
for (const client of this.wss.clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(stringify({topic, payload}));
}
}
}
}
export default Frontend;
+224 -282
View File
@@ -1,173 +1,145 @@
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 equals from 'fast-deep-equal/es6';
import bind from 'bind-decorator';
import Extension from './extension';
import Device from '../model/device';
import Group from '../model/group';
import assert from "node:assert";
import bind from "bind-decorator";
import equals from "fast-deep-equal/es6";
import stringify from "json-stable-stringify-without-jsonify";
import type * as zhc from "zigbee-herdsman-converters";
import Device from "../model/device";
import Group from "../model/group";
import type {Zigbee2MQTTAPI, Zigbee2MQTTResponseEndpoints} from "../types/api";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils, {isLightExpose} from "../util/utils";
import Extension from "./extension";
const topicRegex =
new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/(.+)/(remove|add|remove_all)$`);
const legacyTopicRegexRemoveAll = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/remove_all$`);
const stateProperties: {[s: string]: (value: string, exposes: zhc.DefinitionExpose[]) => boolean} = {
'state': () => true,
'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, 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')) )),
const STATE_PROPERTIES: Readonly<Record<string, (value: string, exposes: zhc.Expose[]) => boolean>> = {
state: () => true,
brightness: (_value, exposes) => exposes.some((e) => isLightExpose(e) && e.features.some((f) => f.name === "brightness")),
color_temp: (_value, exposes) => exposes.some((e) => isLightExpose(e) && e.features.some((f) => f.name === "color_temp")),
color: (_value, exposes) => exposes.some((e) => isLightExpose(e) && e.features.some((f) => f.name === "color_xy" || f.name === "color_hs")),
color_mode: (value, exposes) =>
exposes.some(
(e) =>
isLightExpose(e) &&
(e.features.some((f) => f.name === `color_${value}`) || (value === "color_temp" && e.features.some((f) => f.name === "color_temp"))),
),
};
interface ParsedMQTTMessage {
type: 'remove' | 'add' | 'remove_all', resolvedEntityGroup: Group, resolvedEntityDevice: Device,
error: string, groupKey: string, deviceKey: string, triggeredViaLegacyApi: boolean,
skipDisableReporting: boolean, resolvedEntityEndpoint: zh.Endpoint,
type: "remove" | "add" | "remove_all";
resolvedGroup?: Group;
resolvedDevice?: Device;
resolvedEndpoint?: zh.Endpoint;
groupKey?: string;
deviceKey?: string;
endpointKey?: string | number;
skipDisableReporting: boolean;
}
export default class Groups extends Extension {
private legacyApi = settings.get().advanced.legacy_api;
#topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
private lastOptimisticState: {[s: string]: KeyValue} = {};
// biome-ignore lint/suspicious/useAwait: API
override async start(): Promise<void> {
this.eventBus.onStateChange(this, this.onStateChange);
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
await this.syncGroupsWithSettings();
}
private async syncGroupsWithSettings(): Promise<void> {
const settingsGroups = settings.getGroups();
const zigbeeGroups = this.zigbee.groups();
const addRemoveFromGroup = async (action: 'add' | 'remove', deviceName: string,
groupName: string | number, endpoint: zh.Endpoint, group: Group): Promise<void> => {
try {
logger.info(`${action === 'add' ? 'Adding' : 'Removing'} '${deviceName}' to group '${groupName}'`);
if (action === 'remove') {
await endpoint.removeFromGroup(group.zh);
} else {
await endpoint.addToGroup(group.zh);
}
} catch (error) {
logger.error(`Failed to ${action} '${deviceName}' from '${groupName}'`);
logger.debug(error.stack);
}
};
for (const settingGroup of settingsGroups) {
const groupID = settingGroup.ID;
const zigbeeGroup = zigbeeGroups.find((g) => g.ID === groupID) || this.zigbee.createGroup(groupID);
const settingsEndpoint = settingGroup.devices.map((d) => {
const parsed = utils.parseEntityID(d);
const entity = this.zigbee.resolveEntity(parsed.ID) as Device;
if (!entity) logger.error(`Cannot find '${d}' of group '${settingGroup.friendly_name}'`);
return {'endpoint': entity?.endpoint(parsed.endpoint), 'name': entity?.name};
}).filter((e) => e.endpoint != null);
// In settings but not in zigbee
for (const entity of settingsEndpoint) {
if (!zigbeeGroup.zh.hasMember(entity.endpoint)) {
addRemoveFromGroup('add', entity.name, settingGroup.friendly_name, entity.endpoint, zigbeeGroup);
}
}
// In zigbee but not in settings
for (const endpoint of zigbeeGroup.zh.members) {
if (!settingsEndpoint.find((e) => e.endpoint === endpoint)) {
const deviceName = settings.getDevice(endpoint.getDevice().ieeeAddr).friendly_name;
addRemoveFromGroup('remove', deviceName, settingGroup.friendly_name, endpoint, zigbeeGroup);
}
}
}
for (const zigbeeGroup of zigbeeGroups) {
if (!settingsGroups.find((g) => g.ID === zigbeeGroup.ID)) {
for (const endpoint of zigbeeGroup.zh.members) {
const deviceName = settings.getDevice(endpoint.getDevice().ieeeAddr).friendly_name;
addRemoveFromGroup('remove', deviceName, zigbeeGroup.ID, endpoint, zigbeeGroup);
}
}
}
}
@bind async onStateChange(data: eventdata.StateChange): Promise<void> {
const reason = 'groupOptimistic';
if (data.reason === reason || data.reason === 'publishCached') {
const reason = "groupOptimistic";
if (data.reason === reason || data.reason === "publishCached") {
return;
}
const payload: KeyValue = {};
let endpointName: string | undefined;
const endpointNames: string[] = data.entity instanceof Device ? data.entity.getEndpointNames() : [];
for (let prop of Object.keys(data.update)) {
const value = data.update[prop];
const endpointNameMatch = endpointNames.find((n) => prop.endsWith(`_${n}`));
let endpointName: string = null;
for (let [prop, value] of Object.entries(data.update)) {
const endpointNameMatch = utils.endpointNames.find((n) => prop.endsWith(`_${n}`));
if (endpointNameMatch) {
prop = prop.substring(0, prop.length - endpointNameMatch.length - 1);
endpointName = endpointNameMatch;
}
if (prop in stateProperties) {
if (prop in STATE_PROPERTIES) {
payload[prop] = value;
}
}
if (Object.keys(payload).length) {
const payloadKeys = Object.keys(payload);
if (payloadKeys.length) {
const entity = data.entity;
const groups = this.zigbee.groups().filter((g) => {
return g.options && (!g.options.hasOwnProperty('optimistic') || g.options.optimistic);
});
const groups = [];
for (const group of this.zigbee.groupsIterator()) {
if (group.options && (group.options.optimistic == null || group.options.optimistic)) {
groups.push(group);
}
}
if (entity instanceof Device) {
for (const group of groups) {
if (group.zh.hasMember(entity.endpoint(endpointName)) &&
!equals(this.lastOptimisticState[group.ID], payload) &&
this.shouldPublishPayloadForGroup(group, payload)) {
this.lastOptimisticState[group.ID] = payload;
await this.publishEntityState(group, payload, reason);
const endpoint = entity.endpoint(endpointName);
if (endpoint) {
for (const group of groups) {
if (
group.zh.hasMember(endpoint) &&
!equals(this.lastOptimisticState[group.ID], payload) &&
this.shouldPublishPayloadForGroup(group, payload)
) {
this.lastOptimisticState[group.ID] = payload;
await this.publishEntityState(group, payload, reason);
}
}
}
} else {
// Invalidate the last optimistic group state when group state is changed directly.
delete this.lastOptimisticState[entity.ID];
const groupsToPublish: Set<Group> = new Set();
const groupsToPublish = new Set<Group>();
for (const member of entity.zh.members) {
const device = this.zigbee.resolveEntity(member.getDevice()) as Device;
if (device.options.disabled) continue;
if (device.options.disabled) {
continue;
}
const exposes = device.exposes();
const memberPayload: KeyValue = {};
Object.keys(payload).forEach((key) => {
if (stateProperties[key](payload[key], exposes)) {
for (const key of payloadKeys) {
if (STATE_PROPERTIES[key](payload[key], exposes)) {
memberPayload[key] = payload[key];
}
});
}
const endpointName = device.endpointName(member);
if (endpointName) {
Object.keys(memberPayload).forEach((key) => {
for (const key of Object.keys(memberPayload)) {
memberPayload[`${key}_${endpointName}`] = memberPayload[key];
delete memberPayload[key];
});
}
}
await this.publishEntityState(device, memberPayload, reason);
for (const zigbeeGroup of groups) {
if (zigbeeGroup.zh.hasMember(member) &&
this.shouldPublishPayloadForGroup(zigbeeGroup, payload)) {
if (zigbeeGroup.zh.hasMember(member) && this.shouldPublishPayloadForGroup(zigbeeGroup, payload)) {
groupsToPublish.add(zigbeeGroup);
}
}
}
groupsToPublish.delete(entity);
for (const group of groupsToPublish) {
await this.publishEntityState(group, payload, reason);
}
@@ -176,211 +148,181 @@ export default class Groups extends Extension {
}
private shouldPublishPayloadForGroup(group: Group, payload: KeyValue): boolean {
if (group.options.off_state === 'last_member_state') return true;
if (!payload || payload.state !== 'OFF') return true;
if (this.areAllMembersOff(group)) return true;
return false;
return (
group.options.off_state === "last_member_state" ||
!payload ||
(payload.state !== "OFF" && payload.state !== "CLOSE") ||
this.areAllMembersOffOrClosed(group)
);
}
private areAllMembersOff(group: Group): boolean {
private areAllMembersOffOrClosed(group: Group): boolean {
for (const member of group.zh.members) {
const device = this.zigbee.resolveEntity(member.getDevice());
// biome-ignore lint/style/noNonNullAssertion: TODO: biome migration: valid from loop?
const device = this.zigbee.resolveEntity(member.getDevice())!;
if (this.state.exists(device)) {
const state = this.state.get(device);
if (state.state === 'ON') {
const endpointNames = device.isDevice() && device.getEndpointNames();
const stateKey =
endpointNames &&
endpointNames.length >= member.ID &&
device.definition?.meta?.multiEndpoint &&
!device.definition.meta.multiEndpointSkip?.includes("state")
? `state_${endpointNames[member.ID - 1]}`
: "state";
if (state[stateKey] === "ON" || state[stateKey] === "OPEN") {
return false;
}
}
}
return true;
}
private parseMQTTMessage(data: eventdata.MQTTMessage): ParsedMQTTMessage {
let type: 'remove' | 'add' | 'remove_all' = null;
let resolvedEntityGroup: Group = null;
let resolvedEntityDevice: Device = null;
let resolvedEntityEndpoint: zh.Endpoint = null;
let error: string = null;
let groupKey: string = null;
let deviceKey: string = null;
let triggeredViaLegacyApi = false;
let skipDisableReporting = false;
private parseMQTTMessage(
data: eventdata.MQTTMessage,
): [raw: KeyValue | undefined, parsed: ParsedMQTTMessage | undefined, error: string | undefined] {
const topicRegexMatch = data.topic.match(this.#topicRegex);
/* istanbul ignore else */
const topicRegexMatch = data.topic.match(topicRegex);
const legacyTopicRegexRemoveAllMatch = data.topic.match(legacyTopicRegexRemoveAll);
const legacyTopicRegexMatch = data.topic.match(legacyTopicRegex);
if (topicRegexMatch) {
const type = topicRegexMatch[1] as "remove" | "add" | "remove_all";
let resolvedGroup: Group | undefined;
let groupKey: string | undefined;
let skipDisableReporting = false;
const message = JSON.parse(data.message) as
| Zigbee2MQTTAPI["bridge/request/group/members/add"]
| Zigbee2MQTTAPI["bridge/request/group/members/remove"]
| Zigbee2MQTTAPI["bridge/request/group/members/remove_all"];
if (this.legacyApi && (legacyTopicRegexMatch || legacyTopicRegexRemoveAllMatch)) {
triggeredViaLegacyApi = true;
if (legacyTopicRegexMatch) {
resolvedEntityGroup = this.zigbee.resolveEntity(legacyTopicRegexMatch[1]) as Group;
type = legacyTopicRegexMatch[2] as 'remove' | 'remove_all' | 'add';
if (!resolvedEntityGroup || !(resolvedEntityGroup instanceof Group)) {
logger.error(`Group '${legacyTopicRegexMatch[1]}' does not exist`);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: data.message,
group: legacyTopicRegexMatch[1], error: 'group doesn\'t exists'};
this.mqtt.publish(
'bridge/log',
stringify({type: `device_group_${type}_failed`, message: payload}),
);
}
return null;
}
} else {
type = 'remove_all';
if (typeof message !== "object" || message.device == null) {
return [message, {type, skipDisableReporting}, "Invalid payload"];
}
const parsedEntity = utils.parseEntityID(data.message);
resolvedEntityDevice = this.zigbee.resolveEntity(parsedEntity.ID) as Device;
if (!resolvedEntityDevice || !(resolvedEntityDevice instanceof Device)) {
logger.error(`Device '${data.message}' does not exist`);
const deviceKey = message.device;
skipDisableReporting = message.skip_disable_reporting != null ? message.skip_disable_reporting : false;
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {
friendly_name: data.message, group: legacyTopicRegexMatch[1], error: 'entity doesn\'t exists',
};
this.mqtt.publish(
'bridge/log',
stringify({type: `device_group_${type}_failed`, message: payload}),
);
if (type !== "remove_all") {
if (!("group" in message) || message.group == null) {
return [message, {type, skipDisableReporting}, "Invalid payload"];
}
return null;
}
resolvedEntityEndpoint = resolvedEntityDevice.endpoint(parsedEntity.endpoint);
} else if (topicRegexMatch) {
type = topicRegexMatch[1] as 'remove' | 'add' | 'remove_all';
const message = JSON.parse(data.message);
deviceKey = message.device;
skipDisableReporting = 'skip_disable_reporting' in message ? message.skip_disable_reporting : false;
if (type !== 'remove_all') {
groupKey = message.group;
resolvedEntityGroup = this.zigbee.resolveEntity(message.group) as Group;
if (!resolvedEntityGroup || !(resolvedEntityGroup instanceof Group)) {
error = `Group '${message.group}' does not exist`;
const group = this.zigbee.resolveEntity(message.group);
if (!group || !(group instanceof Group)) {
return [message, {type, skipDisableReporting}, `Group '${message.group}' does not exist`];
}
resolvedGroup = group;
}
const parsed = utils.parseEntityID(message.device);
resolvedEntityDevice = this.zigbee.resolveEntity(parsed.ID) as Device;
if (!error && (!resolvedEntityDevice || !(resolvedEntityDevice instanceof Device))) {
error = `Device '${message.device}' does not exist`;
const resolvedDevice = this.zigbee.resolveEntity(message.device);
if (!resolvedDevice || !(resolvedDevice instanceof Device)) {
return [message, {type, skipDisableReporting}, `Device '${message.device}' does not exist`];
}
if (!error) {
resolvedEntityEndpoint = resolvedEntityDevice.endpoint(parsed.endpoint);
const endpointKey = message.endpoint ?? "default";
const resolvedEndpoint = resolvedDevice.endpoint(message.endpoint);
if (!resolvedEndpoint) {
return [message, {type, skipDisableReporting}, `Device '${resolvedDevice.name}' does not have endpoint '${endpointKey}'`];
}
return [
message,
{
resolvedGroup,
resolvedDevice,
resolvedEndpoint,
type,
groupKey,
deviceKey,
endpointKey,
skipDisableReporting,
},
undefined,
];
}
return {
resolvedEntityGroup, resolvedEntityDevice, type, error, groupKey, deviceKey,
triggeredViaLegacyApi, skipDisableReporting, resolvedEntityEndpoint,
};
return [undefined, undefined, undefined];
}
@bind private async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
const parsed = this.parseMQTTMessage(data);
if (!parsed || !parsed.type) return;
let {
resolvedEntityGroup, resolvedEntityDevice, type, error, triggeredViaLegacyApi,
groupKey, deviceKey, skipDisableReporting, resolvedEntityEndpoint,
} = parsed;
const message = utils.parseJSON(data.message, data.message);
let changedGroups: Group[] = [];
const [raw, parsed, error] = this.parseMQTTMessage(data);
const responseData: KeyValue = {device: deviceKey};
if (groupKey) {
responseData.group = groupKey;
}
if (!error) {
try {
const keys = [
`${resolvedEntityDevice.ieeeAddr}/${resolvedEntityEndpoint.ID}`,
`${resolvedEntityDevice.name}/${resolvedEntityEndpoint.ID}`,
];
const endpointNameLocal = resolvedEntityDevice.endpointName(resolvedEntityEndpoint);
if (endpointNameLocal) {
keys.push(`${resolvedEntityDevice.ieeeAddr}/${endpointNameLocal}`);
keys.push(`${resolvedEntityDevice.name}/${endpointNameLocal}`);
}
if (!endpointNameLocal) {
keys.push(resolvedEntityDevice.name);
keys.push(resolvedEntityDevice.ieeeAddr);
}
if (type === 'add') {
logger.info(`Adding '${resolvedEntityDevice.name}' to '${resolvedEntityGroup.name}'`);
await resolvedEntityEndpoint.addToGroup(resolvedEntityGroup.zh);
settings.addDeviceToGroup(resolvedEntityGroup.ID.toString(), keys);
changedGroups.push(resolvedEntityGroup);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `device_group_add`, message: payload}),
);
}
} else if (type === 'remove') {
logger.info(`Removing '${resolvedEntityDevice.name}' from '${resolvedEntityGroup.name}'`);
await resolvedEntityEndpoint.removeFromGroup(resolvedEntityGroup.zh);
settings.removeDeviceFromGroup(resolvedEntityGroup.ID.toString(), keys);
changedGroups.push(resolvedEntityGroup);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `device_group_remove`, message: payload}),
);
}
} else { // remove_all
logger.info(`Removing '${resolvedEntityDevice.name}' from all groups`);
changedGroups = this.zigbee.groups().filter((g) => g.zh.members.includes(resolvedEntityEndpoint));
await resolvedEntityEndpoint.removeFromAllGroups();
for (const settingsGroup of settings.getGroups()) {
settings.removeDeviceFromGroup(settingsGroup.ID.toString(), keys);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const payload = {friendly_name: resolvedEntityDevice.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `device_group_remove_all`, message: payload}),
);
}
}
}
} catch (e) {
error = `Failed to ${type} from group (${e.message})`;
logger.debug(e.stack);
}
}
if (!triggeredViaLegacyApi) {
const response = utils.getResponse(message, responseData, error);
await this.mqtt.publish(`bridge/response/group/members/${type}`, stringify(response));
if (!raw || !parsed) {
return;
}
if (error) {
logger.error(error);
} else {
for (const group of changedGroups) {
this.eventBus.emitGroupMembersChanged({
group, action: type, endpoint: resolvedEntityEndpoint, skipDisableReporting});
await this.publishResponse(parsed.type, raw, {}, error);
return;
}
const {resolvedGroup, resolvedDevice, resolvedEndpoint, type, groupKey, deviceKey, endpointKey, skipDisableReporting} = parsed;
const changedGroups: Group[] = [];
assert(resolvedDevice, "`resolvedDevice` is missing");
assert(resolvedEndpoint, "`resolvedEndpoint` is missing");
try {
if (type === "add") {
assert(resolvedGroup, "`resolvedGroup` is missing");
logger.info(`Adding endpoint '${resolvedEndpoint.ID}' of device '${resolvedDevice.name}' to group '${resolvedGroup.name}'`);
await resolvedEndpoint.addToGroup(resolvedGroup.zh);
changedGroups.push(resolvedGroup);
// biome-ignore lint/style/noNonNullAssertion: valid from resolved asserts
const respPayload = {device: deviceKey!, endpoint: endpointKey!, group: groupKey!};
await this.publishResponse<"bridge/response/group/members/add">(parsed.type, raw, respPayload);
} else if (type === "remove") {
assert(resolvedGroup, "`resolvedGroup` is missing");
logger.info(`Removing endpoint '${resolvedEndpoint.ID}' of device '${resolvedDevice.name}' from group '${resolvedGroup.name}'`);
await resolvedEndpoint.removeFromGroup(resolvedGroup.zh);
changedGroups.push(resolvedGroup);
// biome-ignore lint/style/noNonNullAssertion: valid from resolved asserts
const respPayload = {device: deviceKey!, endpoint: endpointKey!, group: groupKey!};
await this.publishResponse<"bridge/response/group/members/remove">(parsed.type, raw, respPayload);
} else {
// remove_all
logger.info(`Removing endpoint '${resolvedEndpoint.ID}' of device '${resolvedDevice.name}' from all groups`);
for (const group of this.zigbee.groupsIterator((g) => g.members.includes(resolvedEndpoint))) {
changedGroups.push(group);
}
await resolvedEndpoint.removeFromAllGroups();
// biome-ignore lint/style/noNonNullAssertion: valid from resolved asserts
const respPayload = {device: deviceKey!, endpoint: endpointKey!};
await this.publishResponse<"bridge/response/group/members/remove_all">(parsed.type, raw, respPayload);
}
} catch (e) {
const errorMsg = `Failed to ${type} ${type === "add" ? "to" : "from"} group (${(e as Error).message})`;
await this.publishResponse(parsed.type, raw, {}, errorMsg);
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((e as Error).stack!);
return;
}
for (const group of changedGroups) {
this.eventBus.emitGroupMembersChanged({group, action: type, endpoint: resolvedEndpoint, skipDisableReporting});
}
}
private async publishResponse<T extends Zigbee2MQTTResponseEndpoints>(
type: ParsedMQTTMessage["type"],
request: KeyValue,
data: Zigbee2MQTTAPI[T],
error?: string,
): Promise<void> {
const response = utils.getResponse(request, data, error);
await this.mqtt.publish(`bridge/response/group/members/${type}`, stringify(response));
if (error) {
logger.error(error);
}
}
}
+78
View File
@@ -0,0 +1,78 @@
import * as os from "node:os";
import * as process from "node:process";
import type {Zigbee2MQTTAPI} from "../types/api";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
/** Round with 2 decimals */
const round2 = (n: number): number => Math.round(n * 100.0) / 100.0;
/** Round with 4 decimals */
const round4 = (n: number): number => Math.round(n * 10000.0) / 10000.0;
export default class Health extends Extension {
#checkTimer: NodeJS.Timeout | undefined;
override async start(): Promise<void> {
await super.start();
this.#checkTimer = setInterval(this.#checkHealth.bind(this), utils.minutes(settings.get().health.interval));
}
override async stop(): Promise<void> {
clearInterval(this.#checkTimer);
await super.stop();
}
clearStats(): void {
this.eventBus.stats.devices.clear();
this.eventBus.stats.mqtt.published = 0;
this.eventBus.stats.mqtt.received = 0;
}
async #checkHealth(): Promise<void> {
const sysMemTotalKb = os.totalmem() / 1024;
const sysMemFreeKb = os.freemem() / 1024;
const sysMemUsedKb = sysMemTotalKb - sysMemFreeKb;
const procMemUsedKb = process.memoryUsage().rss / 1024;
const healthcheck: Zigbee2MQTTAPI["bridge/health"] = {
response_time: Date.now(),
os: {
load_average: os.loadavg(), // will be [0,0,0] on Windows (not supported)
memory_used_mb: round2(sysMemUsedKb / 1024),
memory_percent: round4((sysMemUsedKb / sysMemTotalKb) * 100.0),
},
process: {
uptime_sec: Math.floor(process.uptime()),
memory_used_mb: round2(procMemUsedKb / 1024),
memory_percent: round4((procMemUsedKb / sysMemTotalKb) * 100.0),
},
mqtt: {...this.mqtt.stats, ...this.eventBus.stats.mqtt},
devices: {},
};
for (const [ieeeAddr, device] of this.eventBus.stats.devices) {
let messages = 0;
let mps = 0;
if (device.lastSeenChanges) {
const timeDiff = Date.now() - device.lastSeenChanges.first;
messages = device.lastSeenChanges.messages;
mps = timeDiff > 0 ? round4(messages / (timeDiff / 1000.0)) : 0;
}
healthcheck.devices[ieeeAddr] = {
messages,
messages_per_sec: mps,
leave_count: device.leaveCounts,
network_address_changes: device.networkAddressChanges,
};
}
if (settings.get().health.reset_on_check) {
this.clearStats();
}
await this.mqtt.publish("bridge/health", JSON.stringify(healthcheck), {clientOptions: {retain: true, qos: 1}});
}
}
File diff suppressed because it is too large Load Diff
-456
View File
@@ -1,456 +0,0 @@
import * as settings from '../../util/settings';
import logger from '../../util/logger';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import utils from '../../util/utils';
import assert from 'assert';
import Extension from '../extension';
import stringify from 'json-stable-stringify-without-jsonify';
import bind from 'bind-decorator';
const configRegex =
new RegExp(`${settings.get().mqtt.base_topic}/bridge/config/((?:\\w+/get)|(?:\\w+/factory_reset)|(?:\\w+))`);
const allowedLogLevels = ['error', 'warn', 'info', 'debug'];
export default class BridgeLegacy extends Extension {
private lastJoinedDeviceName: string = null;
private supportedOptions: {[s: string]: (topic: string, message: string) => Promise<void> | void};
override async start(): Promise<void> {
this.supportedOptions = {
'permit_join': this.permitJoin,
'last_seen': this.lastSeen,
'elapsed': this.elapsed,
'reset': this.reset,
'log_level': this.logLevel,
'devices': this.devices,
'groups': this.groups,
'devices/get': this.devices,
'rename': this.rename,
'rename_last': this.renameLast,
'remove': this.remove,
'force_remove': this.forceRemove,
'ban': this.ban,
'device_options': this.deviceOptions,
'add_group': this.addGroup,
'remove_group': this.removeGroup,
'force_remove_group': this.removeGroup,
'whitelist': this.whitelist,
'touchlink/factory_reset': this.touchlinkFactoryReset,
};
this.eventBus.onDeviceJoined(this, (data) => this.onZigbeeEvent_('deviceJoined', data, data.device));
this.eventBus.onDeviceInterview(this, (data) => this.onZigbeeEvent_('deviceInterview', data, data.device));
this.eventBus.onDeviceAnnounce(this, (data) => this.onZigbeeEvent_('deviceAnnounce', data, data.device));
this.eventBus.onDeviceLeave(this, (data) => this.onZigbeeEvent_('deviceLeave', data, null));
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
await this.publish();
}
@bind whitelist(topic: string, message: string): void {
try {
const entity = settings.getDevice(message);
assert(entity, `Entity '${message}' does not exist`);
settings.addDeviceToPasslist(entity.ID.toString());
logger.info(`Whitelisted '${entity.friendly_name}'`);
this.mqtt.publish(
'bridge/log',
stringify({type: 'device_whitelisted', message: {friendly_name: entity.friendly_name}}),
);
} catch (error) {
logger.error(`Failed to whitelist '${message}' '${error}'`);
}
}
@bind deviceOptions(topic: string, message: string): void {
let json = null;
try {
json = JSON.parse(message);
} catch (e) {
logger.error('Failed to parse message as JSON');
return;
}
if (!json.hasOwnProperty('friendly_name') || !json.hasOwnProperty('options')) {
logger.error('Invalid JSON message, should contain "friendly_name" and "options"');
return;
}
const entity = settings.getDevice(json.friendly_name);
assert(entity, `Entity '${json.friendly_name}' does not exist`);
settings.changeEntityOptions(entity.ID.toString(), json.options);
logger.info(`Changed device specific options of '${json.friendly_name}' (${stringify(json.options)})`);
}
@bind async permitJoin(topic: string, message: string): Promise<void> {
await this.zigbee.permitJoin(message.toLowerCase() === 'true');
this.publish();
}
@bind async reset(): Promise<void> {
try {
await this.zigbee.reset('soft');
logger.info('Soft resetted ZNP');
} catch (error) {
logger.error('Soft reset failed');
}
}
@bind lastSeen(topic: string, message: string): void {
const allowed = ['disable', 'ISO_8601', 'epoch', 'ISO_8601_local'];
if (!allowed.includes(message)) {
logger.error(`${message} is not an allowed value, possible: ${allowed}`);
return;
}
settings.set(['advanced', 'last_seen'], message);
logger.info(`Set last_seen to ${message}`);
}
@bind elapsed(topic: string, message: string): void {
const allowed = ['true', 'false'];
if (!allowed.includes(message)) {
logger.error(`${message} is not an allowed value, possible: ${allowed}`);
return;
}
settings.set(['advanced', 'elapsed'], message === 'true');
logger.info(`Set elapsed to ${message}`);
}
@bind logLevel(topic: string, message: string): void {
const level = message.toLowerCase() as 'error' | 'warn' | 'info' | 'debug';
if (allowedLogLevels.includes(level)) {
logger.info(`Switching log level to '${level}'`);
logger.setLevel(level);
} else {
logger.error(`Could not set log level to '${level}'. Allowed level: '${allowedLogLevels.join(',')}'`);
}
this.publish();
}
@bind async devices(topic: string): Promise<void> {
const coordinator = await this.zigbee.getCoordinatorVersion();
const devices = this.zigbee.devices().map((device) => {
const payload: KeyValue = {
ieeeAddr: device.ieeeAddr,
type: device.zh.type,
networkAddress: device.zh.networkAddress,
};
if (device.zh.type !== 'Coordinator') {
const definition = zigbeeHerdsmanConverters.findByDevice(device.zh);
payload.model = definition ? definition.model : device.zh.modelID;
payload.vendor = definition ? definition.vendor : '-';
payload.description = definition ? definition.description : '-';
payload.friendly_name = device.name;
payload.manufacturerID = device.zh.manufacturerID;
payload.manufacturerName = device.zh.manufacturerName;
payload.powerSource = device.zh.powerSource;
payload.modelID = device.zh.modelID;
payload.hardwareVersion = device.zh.hardwareVersion;
payload.softwareBuildID = device.zh.softwareBuildID;
payload.dateCode = device.zh.dateCode;
payload.lastSeen = device.zh.lastSeen;
} else {
payload.friendly_name = 'Coordinator';
payload.softwareBuildID = coordinator.type;
payload.dateCode = coordinator.meta.revision.toString();
payload.lastSeen = Date.now();
}
return payload;
});
if (topic.split('/').pop() == 'get') {
this.mqtt.publish(
`bridge/config/devices`, stringify(devices), {}, settings.get().mqtt.base_topic, false, false,
);
} else {
this.mqtt.publish('bridge/log', stringify({type: 'devices', message: devices}));
}
}
@bind groups(): void {
const payload = settings.getGroups().map((g) => {
return {...g, ID: Number(g.ID)};
});
this.mqtt.publish('bridge/log', stringify({type: 'groups', message: payload}));
}
@bind rename(topic: string, message: string): void {
const invalid =
`Invalid rename message format expected {"old": "friendly_name", "new": "new_name"} got ${message}`;
let json = null;
try {
json = JSON.parse(message);
} catch (e) {
logger.error(invalid);
return;
}
// Validate message
if (!json.new || !json.old) {
logger.error(invalid);
return;
}
this._renameInternal(json.old, json.new);
}
@bind renameLast(topic: string, message: string): void {
if (!this.lastJoinedDeviceName) {
logger.error(`Cannot rename last joined device, no device has joined during this session`);
return;
}
this._renameInternal(this.lastJoinedDeviceName, message);
}
_renameInternal(from: string, to: string): void {
try {
const isGroup = settings.getGroup(from) !== null;
settings.changeFriendlyName(from, to);
logger.info(`Successfully renamed - ${from} to ${to} `);
const entity = this.zigbee.resolveEntity(to);
if (entity.isDevice()) {
this.eventBus.emitEntityRenamed({homeAssisantRename: false, from, to, entity});
}
this.mqtt.publish(
'bridge/log',
stringify({type: `${isGroup ? 'group' : 'device'}_renamed`, message: {from, to}}),
);
} catch (error) {
logger.error(`Failed to rename - ${from} to ${to}`);
}
}
@bind addGroup(topic: string, message: string): void {
let id = null;
let name = null;
try {
// json payload with id and friendly_name
const json = JSON.parse(message);
if (json.hasOwnProperty('id')) {
id = json.id;
name = `group_${id}`;
}
if (json.hasOwnProperty('friendly_name')) {
name = json.friendly_name;
}
} catch (e) {
// just friendly_name
name = message;
}
if (name == null) {
logger.error('Failed to add group, missing friendly_name!');
return;
}
const group = settings.addGroup(name, id);
this.zigbee.createGroup(group.ID);
this.mqtt.publish('bridge/log', stringify({type: `group_added`, message: name}));
logger.info(`Added group '${name}'`);
}
@bind removeGroup(topic: string, message: string): void {
const name = message;
const entity = this.zigbee.resolveEntity(message) as Group;
assert(entity && entity.isGroup(), `Group '${message}' does not exist`);
if (topic.includes('force')) {
entity.zh.removeFromDatabase();
} else {
entity.zh.removeFromNetwork();
}
settings.removeGroup(message);
this.mqtt.publish('bridge/log', stringify({type: `group_removed`, message}));
logger.info(`Removed group '${name}'`);
}
@bind async forceRemove(topic: string, message: string): Promise<void> {
await this.removeForceRemoveOrBan('force_remove', message);
}
@bind async remove(topic: string, message: string): Promise<void> {
await this.removeForceRemoveOrBan('remove', message);
}
@bind async ban(topic: string, message: string): Promise<void> {
await this.removeForceRemoveOrBan('ban', message);
}
@bind async removeForceRemoveOrBan(action: string, message: string): Promise<void> {
const entity = this.zigbee.resolveEntity(message.trim()) as Device;
const lookup: KeyValue = {
ban: ['banned', 'Banning', 'ban'],
force_remove: ['force_removed', 'Force removing', 'force remove'],
remove: ['removed', 'Removing', 'remove'],
};
if (!entity) {
logger.error(`Cannot ${lookup[action][2]}, device '${message}' does not exist`);
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
return;
}
const ieeeAddr = entity.ieeeAddr;
const name = entity.name;
const cleanup = (): void => {
// Fire event
this.eventBus.emitDeviceRemoved({ieeeAddr, name});
// Remove from configuration.yaml
settings.removeDevice(entity.ieeeAddr);
// Remove from state
this.state.remove(ieeeAddr);
logger.info(`Successfully ${lookup[action][0]} ${entity.name}`);
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}`, message}));
};
try {
logger.info(`${lookup[action][1]} '${entity.name}'`);
if (action === 'force_remove') {
await entity.zh.removeFromDatabase();
} else {
await entity.zh.removeFromNetwork();
}
cleanup();
} catch (error) {
logger.error(`Failed to ${lookup[action][2]} ${entity.name} (${error})`);
// eslint-disable-next-line
logger.error(`See https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html#zigbee2mqtt-bridge-request for more info`);
this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message}));
}
if (action === 'ban') {
settings.blockDevice(ieeeAddr);
}
}
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
const {topic, message} = data;
if (!topic.match(configRegex)) {
return;
}
const option = topic.match(configRegex)[1];
if (!this.supportedOptions.hasOwnProperty(option)) {
return;
}
await this.supportedOptions[option](topic, message);
return;
}
async publish(): Promise<void> {
const info = await utils.getZigbee2MQTTVersion();
const coordinator = await this.zigbee.getCoordinatorVersion();
const topic = `bridge/config`;
const payload = {
version: info.version,
commit: info.commitHash,
coordinator,
network: await this.zigbee.getNetworkParameters(),
log_level: logger.getLevel(),
permit_join: this.zigbee.getPermitJoin(),
};
await this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0});
}
onZigbeeEvent_(type: string, data: KeyValue, resolvedEntity: Device): void {
if (type === 'deviceJoined' && resolvedEntity) {
this.lastJoinedDeviceName = resolvedEntity.name;
}
if (type === 'deviceJoined') {
this.mqtt.publish(
'bridge/log',
stringify({type: `device_connected`, message: {friendly_name: resolvedEntity.name}}),
);
} else if (type === 'deviceInterview') {
if (data.status === 'successful') {
if (resolvedEntity.definition) {
const {vendor, description, model} = resolvedEntity.definition;
const log = {friendly_name: resolvedEntity.name, model, vendor, description, supported: true};
this.mqtt.publish(
'bridge/log',
stringify({type: `pairing`, message: 'interview_successful', meta: log}),
);
} else {
const meta = {friendly_name: resolvedEntity.name, supported: false};
this.mqtt.publish(
'bridge/log',
stringify({type: `pairing`, message: 'interview_successful', meta}),
);
}
} else if (data.status === 'failed') {
const meta = {friendly_name: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `pairing`, message: 'interview_failed', meta}),
);
} else {
/* istanbul ignore else */
if (data.status === 'started') {
const meta = {friendly_name: resolvedEntity.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `pairing`, message: 'interview_started', meta}),
);
}
}
} else if (type === 'deviceAnnounce') {
const meta = {friendly_name: resolvedEntity.name};
this.mqtt.publish('bridge/log', stringify({type: `device_announced`, message: 'announce', meta}));
} else {
/* istanbul ignore else */
if (type === 'deviceLeave') {
const name = data.ieeeAddr;
const meta = {friendly_name: name};
this.mqtt.publish(
'bridge/log',
stringify({type: `device_removed`, message: 'left_network', meta}),
);
}
}
}
@bind async touchlinkFactoryReset(): Promise<void> {
logger.info('Starting touchlink factory reset...');
this.mqtt.publish(
'bridge/log',
stringify({type: `touchlink`, message: 'reset_started', meta: {status: 'started'}}),
);
const result = await this.zigbee.touchlinkFactoryResetFirst();
if (result) {
logger.info('Successfully factory reset device through Touchlink');
this.mqtt.publish(
'bridge/log',
stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}),
);
} else {
logger.warn('Failed to factory reset device through Touchlink');
this.mqtt.publish(
'bridge/log',
stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}),
);
}
}
}
@@ -1,56 +0,0 @@
/* istanbul ignore file */
import * as settings from '../../util/settings';
import logger from '../../util/logger';
import utils from '../../util/utils';
import Extension from '../extension';
import bind from 'bind-decorator';
import Device from '../../model/device';
const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/device/(.+)/get_group_membership$`);
export default class DeviceGroupMembership extends Extension {
override async start(): Promise<void> {
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
}
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
const match = data.topic.match(topicRegex);
if (!match) {
return null;
}
const parsed = utils.parseEntityID(match[1]);
const device = this.zigbee.resolveEntity(parsed.ID) as Device;
if (!device || !(device instanceof Device)) {
logger.error(`Device '${match[1]}' does not exist`);
return;
}
const endpoint = device.endpoint(parsed.endpoint);
const response = await endpoint.command(
`genGroups`, 'getMembership', {groupcount: 0, grouplist: []}, {},
);
if (!response) {
logger.warn(`Couldn't get group membership of ${device.ieeeAddr}`);
return;
}
let {grouplist, capacity} = response;
grouplist = grouplist.map((gid: string) => {
const g = settings.getGroup(gid);
return g ? g.friendly_name : gid;
});
const msgGroupList = `${device.ieeeAddr} is in groups [${grouplist}]`;
let msgCapacity;
if (capacity === 254) {
msgCapacity = 'it can be a part of at least 1 more group';
} else {
msgCapacity = `its remaining group capacity is ${capacity === 255 ? 'unknown' : capacity}`;
}
logger.info(`${msgGroupList} and ${msgCapacity}`);
this.publishEntityState(device, {group_list: grouplist, group_capacity: capacity});
}
}
-194
View File
@@ -1,194 +0,0 @@
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import logger from '../../util/logger';
import * as settings from '../../util/settings';
import Extension from '../extension';
const defaultConfiguration = {
minimumReportInterval: 3, maximumReportInterval: 300, reportableChange: 1,
};
const ZNLDP12LM = zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'ZNLDP12LM');
const devicesNotSupportingReporting = [
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'CC2530.ROUTER'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'BASICZBR3'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'ZM-CSW032-D'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'TS0001'),
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'TS0115'),
];
const reportKey = 1;
const getColorCapabilities = async (endpoint: zh.Endpoint): Promise<{colorTemperature: boolean, colorXY: boolean}> => {
if (endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities') === undefined) {
await endpoint.read('lightingColorCtrl', ['colorCapabilities']);
}
const value = endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities') as number;
return {
colorTemperature: (value & 1<<4) > 0,
colorXY: (value & 1<<3) > 0,
};
};
const clusters: {[s: string]:
{attribute: string, minimumReportInterval: number, maximumReportInterval: number, reportableChange: number
condition?: (endpoint: zh.Endpoint) => Promise<boolean>}[]} =
{
'genOnOff': [
{attribute: 'onOff', ...defaultConfiguration, minimumReportInterval: 0, reportableChange: 0},
],
'genLevelCtrl': [
{attribute: 'currentLevel', ...defaultConfiguration},
],
'lightingColorCtrl': [
{
attribute: 'colorTemperature', ...defaultConfiguration,
condition: async (endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorTemperature,
},
{
attribute: 'currentX', ...defaultConfiguration,
condition: async (endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorXY,
},
{
attribute: 'currentY', ...defaultConfiguration,
condition: async (endpoint): Promise<boolean> => (await getColorCapabilities(endpoint)).colorXY,
},
],
'closuresWindowCovering': [
{attribute: 'currentPositionLiftPercentage', ...defaultConfiguration},
{attribute: 'currentPositionTiltPercentage', ...defaultConfiguration},
],
};
export default class Report extends Extension {
private queue: Set<string> = new Set();
private failed: Set<string> = new Set();
private enabled = settings.get().advanced.report;
shouldIgnoreClusterForDevice(cluster: string, definition: zhc.Definition): boolean {
if (definition === ZNLDP12LM && cluster === 'closuresWindowCovering') {
// Device announces it but doesn't support it
// https://github.com/Koenkk/zigbee2mqtt/issues/2611
return true;
}
return false;
}
async setupReporting(device: Device): Promise<void> {
if (this.queue.has(device.ieeeAddr) || this.failed.has(device.ieeeAddr)) return;
this.queue.add(device.ieeeAddr);
const term1 = this.enabled ? 'Setup' : 'Disable';
const term2 = this.enabled ? 'setup' : 'disabled';
try {
for (const ep of device.zh.endpoints) {
for (const [cluster, configuration] of Object.entries(clusters)) {
if (ep.supportsInputCluster(cluster) &&
!this.shouldIgnoreClusterForDevice(cluster, device.definition)) {
logger.debug(`${term1} reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`);
const items = [];
for (const entry of configuration) {
if (!entry.hasOwnProperty('condition') || (await entry.condition(ep))) {
const toAdd = {...entry};
if (!this.enabled) toAdd.maximumReportInterval = 0xFFFF;
items.push(toAdd);
delete items[items.length - 1].condition;
}
}
this.enabled ?
await ep.bind(cluster, this.zigbee.firstCoordinatorEndpoint()) :
await ep.unbind(cluster, this.zigbee.firstCoordinatorEndpoint());
await ep.configureReporting(cluster, items);
logger.info(
`Successfully ${term2} reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`,
);
}
}
}
if (this.enabled) {
device.zh.meta.reporting = reportKey;
} else {
delete device.zh.meta.reporting;
this.eventBus.emitReconfigure({device});
}
this.eventBus.emitDevicesChanged();
} catch (error) {
logger.error(
`Failed to ${term1.toLowerCase()} reporting for '${device.ieeeAddr}' - ${error.stack}`,
);
this.failed.add(device.ieeeAddr);
}
device.zh.save();
this.queue.delete(device.ieeeAddr);
}
shouldSetupReporting(device: Device, messageType: string): boolean {
if (!device || !device.zh || !device.definition) return false;
// Handle messages of type endDeviceAnnce and devIncoming.
// This message is typically send when a device comes online after being powered off
// Ikea TRADFRI tend to forget their reporting after powered off.
// Re-setup reporting.
// Only resetup reporting if configuredReportings was not populated yet,
// else reconfigure is done in zigbee-herdsman-converters ikea.js/bulbOnEvent
// configuredReportings are saved since Zigbee2MQTT 1.17.0
// https://github.com/Koenkk/zigbee2mqtt/issues/966
if (this.enabled && messageType === 'deviceAnnounce' && device.isIkeaTradfri() &&
device.zh.endpoints.filter((e) => e.configuredReportings.length === 0).length ===
device.zh.endpoints.length) {
return true;
}
// These do not support reproting.
// https://github.com/Koenkk/zigbee-herdsman/issues/110
const philipsIgnoreSw = ['5.127.1.26581', '5.130.1.30000'];
if (device.zh.manufacturerName === 'Philips' &&
philipsIgnoreSw.includes(device.zh.softwareBuildID)) return false;
if (device.zh.interviewing === true) return false;
if (device.zh.type !== 'Router' || device.zh.powerSource === 'Battery') return false;
// Gledopto devices don't support reporting.
if (devicesNotSupportingReporting.includes(device.definition) ||
device.definition.vendor === 'Gledopto') return false;
if (this.enabled && device.zh.meta.hasOwnProperty('reporting') &&
device.zh.meta.reporting === reportKey) {
return false;
}
if (!this.enabled && !device.zh.meta.hasOwnProperty('reporting')) {
return false;
}
return true;
}
override async start(): Promise<void> {
for (const device of this.zigbee.devices(false)) {
if (this.shouldSetupReporting(device, null)) {
await this.setupReporting(device);
}
}
this.eventBus.onDeviceAnnounce(this, (data) => this.onZigbeeEvent_('deviceAnnounce', data.device));
this.eventBus.onDeviceMessage(this, (data) => this.onZigbeeEvent_('dummy', data.device));
this.eventBus.onDeviceJoined(this, (data) => this.onZigbeeEvent_('dummy', data.device));
this.eventBus.onDeviceNetworkAddressChanged(this, (data) => this.onZigbeeEvent_('dummy', data.device));
}
async onZigbeeEvent_(type: string, device: Device): Promise<void> {
if (this.shouldSetupReporting(device, type)) {
await this.setupReporting(device);
}
}
}
-62
View File
@@ -1,62 +0,0 @@
/* istanbul ignore file */
// DEPRECATED
import * as settings from '../../util/settings';
import logger from '../../util/logger';
import utils from '../../util/utils';
import Extension from '../extension';
/**
* This extensions soft resets the ZNP after a certain timeout.
*/
export default class SoftReset extends Extension {
private timer: NodeJS.Timer = null;
private timeout = utils.seconds(settings.get().advanced.soft_reset_timeout);
override async start(): Promise<void> {
logger.debug(`Soft reset timeout set to ${this.timeout / 1000} seconds`);
this.resetTimer();
this.eventBus.onDeviceMessage(this, () => this.resetTimer());
this.eventBus.onDeviceAnnounce(this, () => this.resetTimer());
this.eventBus.onDeviceNetworkAddressChanged(this, () => this.resetTimer());
this.eventBus.onDeviceJoined(this, () => this.resetTimer());
this.eventBus.onDeviceInterview(this, () => this.resetTimer());
}
private clearTimer(): void {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
private resetTimer(): void {
if (this.timeout === 0) {
return;
}
this.clearTimer();
this.timer = setTimeout(() => this.handleTimeout(), this.timeout);
}
private async handleTimeout(): Promise<void> {
logger.warn('Soft reset timeout triggered');
try {
await this.zigbee.reset('soft');
logger.warn('Soft resetted ZNP due to timeout');
} catch (error) {
logger.warn('Soft reset failed, trying stop/start');
await this.zigbee.stop();
logger.warn('Zigbee stopped');
try {
await this.zigbee.start();
} catch (error) {
logger.error('Failed to restart!');
}
}
this.resetTimer();
}
}
+188 -178
View File
@@ -1,91 +1,74 @@
import * as settings from '../util/settings';
import utils from '../util/utils';
import logger from '../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import Extension from './extension';
import bind from 'bind-decorator';
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import type {Eui64} from "zigbee-herdsman/dist/zspec/tstypes";
import type {LQITableEntry, RoutingTableEntry} from "zigbee-herdsman/dist/zspec/zdo/definition/tstypes";
import type {Zigbee2MQTTAPI, Zigbee2MQTTNetworkMap} from "../types/api";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
interface Link {
source: {ieeeAddr: string, networkAddress: number}, target: {ieeeAddr: string, networkAddress: number},
linkquality: number, depth: number, routes: zh.RoutingTableEntry[],
sourceIeeeAddr: string, targetIeeeAddr: string, sourceNwkAddr: number, lqi: number, relationship: number,
}
interface Topology {
nodes: {
ieeeAddr: string, friendlyName: string, type: string, networkAddress: number, manufacturerName: string,
modelID: string, failed: string[], lastSeen: number,
definition: {model: string, vendor: string, supports: string, description: string}}[],
links: Link[],
}
const SUPPORTED_FORMATS = ["raw", "graphviz", "plantuml"];
/**
* This extension creates a network map
*/
export default class NetworkMap extends Extension {
private legacyApi = settings.get().advanced.legacy_api;
private legacyTopic = `${settings.get().mqtt.base_topic}/bridge/networkmap`;
private legacyTopicRoutes = `${settings.get().mqtt.base_topic}/bridge/networkmap/routes`;
private topic = `${settings.get().mqtt.base_topic}/bridge/request/networkmap`;
private supportedFormats: {[s: string]: (topology: Topology) => KeyValue | string};
#topic = `${settings.get().mqtt.base_topic}/bridge/request/networkmap`;
// biome-ignore lint/suspicious/useAwait: API
override async start(): Promise<void> {
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.supportedFormats = {
'raw': this.raw,
'graphviz': this.graphviz,
'plantuml': this.plantuml,
};
}
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
/* istanbul ignore else */
if (this.legacyApi) {
if ((data.topic === this.legacyTopic || data.topic === this.legacyTopicRoutes) &&
this.supportedFormats.hasOwnProperty(data.message)) {
const includeRoutes = data.topic === this.legacyTopicRoutes;
const topology = await this.networkScan(includeRoutes);
let converted = this.supportedFormats[data.message](topology);
converted = data.message === 'raw' ? stringify(converted) : converted;
this.mqtt.publish(`bridge/networkmap/${data.message}`, converted as string, {});
}
}
if (data.topic === this.#topic) {
const message = utils.parseJSON(data.message, data.message) as Zigbee2MQTTAPI["bridge/request/networkmap"];
if (data.topic === this.topic) {
const message = utils.parseJSON(data.message, data.message);
try {
const type = typeof message === 'object' ? message.type : message;
if (!this.supportedFormats.hasOwnProperty(type)) {
throw new Error(`Type '${type}' not supported, allowed are: ${Object.keys(this.supportedFormats)}`);
const type = typeof message === "object" ? message.type : message;
if (!SUPPORTED_FORMATS.includes(type)) {
throw new Error(`Type '${type}' not supported, allowed are: ${SUPPORTED_FORMATS.join(",")}`);
}
const routes = typeof message === 'object' && message.routes;
const routes = typeof message === "object" && message.routes;
const topology = await this.networkScan(routes);
const value = this.supportedFormats[type](topology);
await this.mqtt.publish(
'bridge/response/networkmap',
stringify(utils.getResponse(message, {routes, type, value}, null)),
);
let responseData: Zigbee2MQTTAPI["bridge/response/networkmap"];
switch (type) {
case "raw": {
responseData = {type, routes, value: this.raw(topology)};
break;
}
case "graphviz": {
responseData = {type, routes, value: this.graphviz(topology)};
break;
}
case "plantuml": {
responseData = {type, routes, value: this.plantuml(topology)};
break;
}
}
await this.mqtt.publish("bridge/response/networkmap", stringify(utils.getResponse(message, responseData)));
} catch (error) {
await this.mqtt.publish(
'bridge/response/networkmap',
stringify(utils.getResponse(message, {}, error.message)),
);
await this.mqtt.publish("bridge/response/networkmap", stringify(utils.getResponse(message, {}, (error as Error).message)));
}
}
}
@bind raw(topology: Topology): KeyValue {
raw(topology: Zigbee2MQTTNetworkMap): Zigbee2MQTTNetworkMap {
return topology;
}
@bind graphviz(topology: Topology): string {
graphviz(topology: Zigbee2MQTTNetworkMap): string {
const colors = settings.get().map_options.graphviz.colors;
let text = 'digraph G {\nnode[shape=record];\n';
let style = '';
let text = "digraph G {\nnode[shape=record];\n";
let style = "";
topology.nodes.forEach((node) => {
for (const node of topology.nodes) {
const labels = [];
// Add friendly name
@@ -93,229 +76,256 @@ export default class NetworkMap extends Extension {
// Add the device short network address, ieeaddr and scan note (if any)
labels.push(
`${node.ieeeAddr} (${utils.toNetworkAddressHex(node.networkAddress)})` +
((node.failed && node.failed.length) ? `failed: ${node.failed.join(',')}` : ''),
`${node.ieeeAddr} (${utils.toNetworkAddressHex(node.networkAddress)})${node.failed?.length ? `failed: ${node.failed.join(",")}` : ""}`,
);
// Add the device model
if (node.type !== 'Coordinator') {
if (node.definition) {
labels.push(`${node.definition.vendor} ${node.definition.description} (${node.definition.model})`);
} else {
// This model is not supported by zigbee-herdsman-converters, add zigbee model information
labels.push(`${node.manufacturerName} ${node.modelID}`);
}
if (node.type !== "Coordinator") {
labels.push(`${node.definition?.vendor} ${node.definition?.description} (${node.definition?.model})`);
}
// Add the device last_seen timestamp
let lastSeen = 'unknown';
const date = node.type === 'Coordinator' ? Date.now() : node.lastSeen;
let lastSeen = "unknown";
const date = node.type === "Coordinator" ? Date.now() : node.lastSeen;
if (date) {
lastSeen = utils.formatDate(date, 'relative') as string;
lastSeen = utils.formatDate(date, "relative") as string;
}
labels.push(lastSeen);
// Escape backslashes and double-quotes to avoid breaking Graphviz .dot files.
const escapedLabels = labels.map((label) => label.replace(/\\/g, "\\\\").replace(/"/g, '\\"'));
// Shape the record according to device type
if (node.type == 'Coordinator') {
style = `style="bold, filled", fillcolor="${colors.fill.coordinator}", ` +
`fontcolor="${colors.font.coordinator}"`;
} else if (node.type == 'Router') {
style = `style="rounded, filled", fillcolor="${colors.fill.router}", ` +
`fontcolor="${colors.font.router}"`;
if (node.type === "Coordinator") {
style = `style="bold, filled", fillcolor="${colors.fill.coordinator}", fontcolor="${colors.font.coordinator}"`;
} else if (node.type === "Router") {
style = `style="rounded, filled", fillcolor="${colors.fill.router}", fontcolor="${colors.font.router}"`;
} else {
style = `style="rounded, dashed, filled", fillcolor="${colors.fill.enddevice}", ` +
`fontcolor="${colors.font.enddevice}"`;
style = `style="rounded, dashed, filled", fillcolor="${colors.fill.enddevice}", fontcolor="${colors.font.enddevice}"`;
}
// Add the device with its labels to the graph as a node.
text += ` "${node.ieeeAddr}" [`+style+`, label="{${labels.join('|')}}"];\n`;
text += ` "${node.ieeeAddr}" [${style}, label="{${escapedLabels.join("|")}}"];\n`;
/**
* Add an edge between the device and its child to the graph
* NOTE: There are situations where a device is NOT in the topology, this can be e.g.
* due to not responded to the lqi scan. In that case we do not add an edge for this device.
*/
topology.links.filter((e) => (e.source.ieeeAddr === node.ieeeAddr)).forEach((e) => {
const lineStyle = (node.type=='EndDevice') ? 'penwidth=1, ' :
(!e.routes.length) ? 'penwidth=0.5, ' : 'penwidth=2, ';
const lineWeight = (!e.routes.length) ? `weight=0, color="${colors.line.inactive}", ` :
`weight=1, color="${colors.line.active}", `;
const textRoutes = e.routes.map((r) => utils.toNetworkAddressHex(r.destinationAddress));
const lineLabels = (!e.routes.length) ? `label="${e.linkquality}"` :
`label="${e.linkquality} (routes: ${textRoutes.join(',')})"`;
text += ` "${node.ieeeAddr}" -> "${e.target.ieeeAddr}"`;
text += ` [${lineStyle}${lineWeight}${lineLabels}]\n`;
});
});
for (const link of topology.links) {
if (link.source.ieeeAddr === node.ieeeAddr) {
const lineStyle = node.type === "EndDevice" ? "penwidth=1, " : !link.routes.length ? "penwidth=0.5, " : "penwidth=2, ";
const lineWeight = !link.routes.length
? `weight=0, color="${colors.line.inactive}", `
: `weight=1, color="${colors.line.active}", `;
const textRoutes = link.routes.map((r) => utils.toNetworkAddressHex(r.destinationAddress));
const lineLabels = !link.routes.length
? `label="${link.linkquality}"`
: `label="${link.linkquality} (routes: ${textRoutes.join(",")})"`;
text += ` "${node.ieeeAddr}" -> "${link.target.ieeeAddr}"`;
text += ` [${lineStyle}${lineWeight}${lineLabels}]\n`;
}
}
}
text += '}';
text += "}";
return text.replace(/\0/g, '');
return text.replace(/\0/g, "");
}
@bind plantuml(topology: Topology): string {
plantuml(topology: Zigbee2MQTTNetworkMap): string {
const text = [];
text.push(`' paste into: https://www.planttext.com/`);
text.push(``);
text.push('@startuml');
text.push("");
text.push("@startuml");
topology.nodes.sort((a, b) => a.friendlyName.localeCompare(b.friendlyName)).forEach((node) => {
for (const node of topology.nodes.sort((a, b) => a.friendlyName.localeCompare(b.friendlyName))) {
// Add friendly name
text.push(`card ${node.ieeeAddr} [`);
text.push(`${node.friendlyName}`);
text.push(`---`);
text.push("---");
// Add the device short network address, ieeaddr and scan note (if any)
text.push(
`${node.ieeeAddr} (${utils.toNetworkAddressHex(node.networkAddress)})` +
((node.failed && node.failed.length) ? ` failed: ${node.failed.join(',')}` : ''),
`${node.ieeeAddr} (${utils.toNetworkAddressHex(node.networkAddress)})${node.failed?.length ? ` failed: ${node.failed.join(",")}` : ""}`,
);
// Add the device model
if (node.type !== 'Coordinator') {
text.push(`---`);
if (node.type !== "Coordinator") {
text.push("---");
const definition = (this.zigbee.resolveEntity(node.ieeeAddr) as Device).definition;
if (definition) {
text.push(`${definition.vendor} ${definition.description} (${definition.model})`);
} else {
// This model is not supported by zigbee-herdsman-converters, add zigbee model information
text.push(`${node.manufacturerName} ${node.modelID}`);
}
text.push(`${definition?.vendor} ${definition?.description} (${definition?.model})`);
}
// Add the device last_seen timestamp
let lastSeen = 'unknown';
const date = node.type === 'Coordinator' ? Date.now() : node.lastSeen;
let lastSeen = "unknown";
const date = node.type === "Coordinator" ? Date.now() : node.lastSeen;
if (date) {
lastSeen = utils.formatDate(date, 'relative') as string;
lastSeen = utils.formatDate(date, "relative") as string;
}
text.push(`---`);
text.push("---");
text.push(lastSeen);
text.push(`]`);
text.push(``);
});
text.push("]");
text.push("");
}
/**
* Add edges between the devices
* NOTE: There are situations where a device is NOT in the topology, this can be e.g.
* due to not responded to the lqi scan. In that case we do not add an edge for this device.
*/
topology.links.forEach((link) => {
for (const link of topology.links) {
text.push(`${link.sourceIeeeAddr} --> ${link.targetIeeeAddr}: ${link.lqi}`);
});
text.push('');
}
text.push(`@enduml`);
text.push("");
return text.join(`\n`);
text.push("@enduml");
return text.join("\n");
}
async networkScan(includeRoutes: boolean): Promise<Topology> {
async networkScan(includeRoutes: boolean): Promise<Zigbee2MQTTNetworkMap> {
logger.info(`Starting network scan (includeRoutes '${includeRoutes}')`);
const devices = this.zigbee.devices().filter((d) => d.zh.type !== 'GreenPower' && !d.options.disabled);
const lqis: Map<Device, zh.LQI> = new Map();
const routingTables: Map<Device, zh.RoutingTable> = new Map();
const failed: Map<Device, string[]> = new Map();
const lqis = new Map<Device, LQITableEntry[]>();
const routingTables = new Map<Device, RoutingTableEntry[]>();
const failed = new Map<Device, string[]>();
const requestWithRetry = async <T>(request: () => Promise<T>): Promise<T> => {
try {
const result = await request();
for (const device of devices.filter((d) => d.zh.type != 'EndDevice')) {
failed.set(device, []);
return result;
} catch {
// Network is possibly congested, sleep 5 seconds to let the network settle.
await utils.sleep(5);
return await request();
}
};
for (const device of this.zigbee.devicesIterator((d) => d.type !== "GreenPower" && d.type !== "EndDevice")) {
if (device.options.disabled) {
continue;
}
const deviceFailures: string[] = [];
failed.set(device, deviceFailures);
await utils.sleep(1); // sleep 1 second between each scan to reduce stress on network.
const doRequest = async <T>(request: () => Promise<T>, firstAttempt = true): Promise<T> => {
try {
return await request();
} catch (error) {
if (!firstAttempt) {
throw error;
} else {
// Network is possibly congested, sleep 5 seconds to let the network settle.
await utils.sleep(5);
return doRequest(request, false);
}
}
};
try {
const result = await doRequest<zh.LQI>(async () => device.zh.lqi());
const result = await requestWithRetry<LQITableEntry[]>(async () => await device.zh.lqi());
lqis.set(device, result);
logger.debug(`LQI succeeded for '${device.name}'`);
} catch (error) {
failed.get(device).push('lqi');
deviceFailures.push("lqi"); // set above
logger.error(`Failed to execute LQI for '${device.name}'`);
logger.debug(error.stack);
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((error as Error).stack!);
}
if (includeRoutes) {
try {
const result = await doRequest(async () => device.zh.routingTable());
const result = await requestWithRetry<RoutingTableEntry[]>(async () => await device.zh.routingTable());
routingTables.set(device, result);
logger.debug(`Routing table succeeded for '${device.name}'`);
} catch (error) {
failed.get(device).push('routingTable');
deviceFailures.push("routingTable"); // set above
logger.error(`Failed to execute routing table for '${device.name}'`);
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((error as Error).stack!);
}
}
}
logger.info(`Network scan finished`);
logger.info("Network scan finished");
const topology: Topology = {nodes: [], links: []};
// Add nodes
for (const device of devices) {
const definition = device.definition ? {
model: device.definition.model,
vendor: device.definition.vendor,
description: device.definition.description,
supports: Array.from(new Set((device.exposes()).map((e) => {
return e.hasOwnProperty('name') ? e.name :
`${e.type} (${e.features.map((f) => f.name).join(', ')})`;
}))).join(', '),
} : null;
const topology: Zigbee2MQTTNetworkMap = {nodes: [], links: []};
// XXX: display GP/disabled devices in the map, better feedback than just hiding them?
for (const device of this.zigbee.devicesIterator((d) => d.type !== "GreenPower")) {
if (device.options.disabled) {
continue;
}
// Add nodes
const definition = device.definition
? {
model: device.definition.model,
vendor: device.definition.vendor,
description: device.definition.description,
supports: Array.from(
new Set(
device.exposes().map((e) => {
return e.name ?? `${e.type} (${e.features?.map((f) => f.name).join(", ")})`;
}),
),
).join(", "),
}
: undefined;
topology.nodes.push({
ieeeAddr: device.ieeeAddr, friendlyName: device.name, type: device.zh.type,
networkAddress: device.zh.networkAddress, manufacturerName: device.zh.manufacturerName,
modelID: device.zh.modelID, failed: failed.get(device), lastSeen: device.zh.lastSeen,
ieeeAddr: device.ieeeAddr,
friendlyName: device.name,
type: device.zh.type,
networkAddress: device.zh.networkAddress,
manufacturerName: device.zh.manufacturerName,
modelID: device.zh.modelID,
failed: failed.get(device),
lastSeen: device.zh.lastSeen,
definition,
});
}
// Add links
lqis.forEach((lqi, device) => {
for (const neighbor of lqi.neighbors) {
for (const [device, table] of lqis) {
for (const neighbor of table) {
if (neighbor.relationship > 3) {
// Relationship is not active, skip it
continue;
}
let neighborEui64 = neighbor.eui64;
// Some Xiaomi devices return 0x00 as the neighbor ieeeAddr (obviously not correct).
// Determine the correct ieeeAddr based on the networkAddress.
const neighborDevice = this.zigbee.deviceByNetworkAddress(neighbor.networkAddress);
if (neighbor.ieeeAddr === '0x0000000000000000' && neighborDevice) {
neighbor.ieeeAddr = neighborDevice.ieeeAddr;
if (neighborEui64 === "0x0000000000000000") {
const neighborDevice = this.zigbee.deviceByNetworkAddress(neighbor.nwkAddress);
if (neighborDevice) {
neighborEui64 = neighborDevice.ieeeAddr as Eui64;
}
}
const link: Link = {
source: {ieeeAddr: neighbor.ieeeAddr, networkAddress: neighbor.networkAddress},
const link: Zigbee2MQTTNetworkMap["links"][number] = {
source: {ieeeAddr: neighborEui64, networkAddress: neighbor.nwkAddress},
target: {ieeeAddr: device.ieeeAddr, networkAddress: device.zh.networkAddress},
linkquality: neighbor.linkquality, depth: neighbor.depth, routes: [],
// DEPRECATED:
sourceIeeeAddr: neighbor.ieeeAddr, targetIeeeAddr: device.ieeeAddr,
sourceNwkAddr: neighbor.networkAddress, lqi: neighbor.linkquality,
deviceType: neighbor.deviceType,
rxOnWhenIdle: neighbor.rxOnWhenIdle,
relationship: neighbor.relationship,
permitJoining: neighbor.permitJoining,
depth: neighbor.depth,
lqi: neighbor.lqi,
routes: [],
// below are @deprecated
sourceIeeeAddr: neighborEui64,
targetIeeeAddr: device.ieeeAddr,
sourceNwkAddr: neighbor.nwkAddress,
linkquality: neighbor.lqi,
};
const routingTable = routingTables.get(device);
if (routingTable) {
link.routes = routingTable.table
.filter((t) => t.status === 'ACTIVE' && t.nextHop === neighbor.networkAddress);
for (const entry of routingTable) {
if (entry.nextHopAddress === neighbor.nwkAddress) {
link.routes.push(entry);
}
}
}
topology.links.push(link);
}
});
}
return topology;
}
+73 -32
View File
@@ -1,50 +1,91 @@
import zhc from 'zigbee-herdsman-converters';
import Extension from './extension';
import {onEvent, type OnEvent as ZhcOnEvent} from "zigbee-herdsman-converters";
import utils from "../util/utils";
import Extension from "./extension";
/**
* This extension calls the zigbee-herdsman-converters onEvent.
*/
export default class OnEvent extends Extension {
override async start(): Promise<void> {
for (const device of this.zigbee.devices(false)) {
this.callOnEvent(device, 'start', {});
}
readonly #startCalled: Set<string> = new Set();
this.eventBus.onDeviceMessage(this, (data) => this.callOnEvent(data.device, 'message', this.convertData(data)));
this.eventBus.onDeviceJoined(this,
(data) => this.callOnEvent(data.device, 'deviceJoined', this.convertData(data)));
this.eventBus.onDeviceInterview(this,
(data) => this.callOnEvent(data.device, 'deviceInterview', this.convertData(data)));
this.eventBus.onDeviceAnnounce(this,
(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());
}
});
#getOnEventBaseData(device: Device): ZhcOnEvent.BaseData {
const deviceExposesChanged = (): void => this.eventBus.emitExposesAndDevicesChanged(device);
const state = this.state.get(device);
const options = device.options as KeyValue;
return {deviceExposesChanged, device: device.zh, state, options};
}
private convertData(data: KeyValue): KeyValue {
return {...data, device: data.device.zh};
// biome-ignore lint/suspicious/useAwait: API
override async start(): Promise<void> {
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
// don't await, in case of repeated failures this would hold startup
this.callOnEvent(device, {type: "start", data: this.#getOnEventBaseData(device)}).catch(utils.noop);
}
this.eventBus.onDeviceJoined(this, async (data) => {
await this.callOnEvent(data.device, {type: "deviceJoined", data: this.#getOnEventBaseData(data.device)});
});
this.eventBus.onDeviceLeave(this, async (data) => {
if (data.device) {
await this.callOnEvent(data.device, {type: "stop", data: {ieeeAddr: data.device.ieeeAddr}});
}
});
this.eventBus.onEntityRemoved(this, async (data) => {
if (data.entity.isDevice()) {
await this.callOnEvent(data.entity, {type: "stop", data: {ieeeAddr: data.entity.ieeeAddr}});
}
});
this.eventBus.onDeviceInterview(this, async (data) => {
// TODO: this is triggering for any `data.status`, any use outside `successful`?
// ZHC definition would skip from `device.definition?` but OnEvent from ZHC index wouldn't => triple triggering?
await this.callOnEvent(data.device, {type: "deviceInterview", data: {...this.#getOnEventBaseData(data.device), status: data.status}});
});
this.eventBus.onDeviceAnnounce(this, async (data) => {
await this.callOnEvent(data.device, {type: "deviceAnnounce", data: this.#getOnEventBaseData(data.device)});
});
this.eventBus.onDeviceNetworkAddressChanged(this, async (data) => {
await this.callOnEvent(data.device, {type: "deviceNetworkAddressChanged", data: this.#getOnEventBaseData(data.device)});
});
this.eventBus.onEntityOptionsChanged(this, async (data) => {
if (data.entity.isDevice()) {
await this.callOnEvent(data.entity, {
type: "deviceOptionsChanged",
data: {...this.#getOnEventBaseData(data.entity), from: data.from, to: data.to},
});
this.eventBus.emitDevicesChanged();
}
});
}
override async stop(): Promise<void> {
super.stop();
for (const device of this.zigbee.devices(false)) {
await this.callOnEvent(device, 'stop', {});
await super.stop();
// ensure properly started, else this throws undesired errors (e.g. SIGINT during startup)
if (this.zigbee.zhController !== undefined) {
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
await this.callOnEvent(device, {type: "stop", data: {ieeeAddr: device.ieeeAddr}});
}
}
}
private async callOnEvent(device: Device, type: string, data: KeyValue): Promise<void> {
const state = this.state.get(device);
zhc.onEvent(type, data, device.zh, device.options, state);
private async callOnEvent(device: Device, event: ZhcOnEvent.Event): Promise<void> {
if (device.options.disabled) {
return;
}
if (device.definition?.onEvent) {
await device.definition.onEvent(type, data, device.zh, device.options, state);
if (event.type === "start") {
this.#startCalled.add(device.ieeeAddr);
} else if (event.type !== "stop" && !this.#startCalled.has(device.ieeeAddr) && device.definition?.onEvent) {
this.#startCalled.add(device.ieeeAddr);
await device.definition.onEvent({type: "start", data: this.#getOnEventBaseData(device)});
}
await onEvent(event);
await device.definition?.onEvent?.(event);
if (event.type === "stop") {
this.#startCalled.delete(device.ieeeAddr);
}
}
}
+463 -239
View File
@@ -1,317 +1,541 @@
import * as settings from '../util/settings';
import logger from '../util/logger';
import stringify from 'json-stable-stringify-without-jsonify';
import utils from '../util/utils';
import 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';
import assert from "node:assert";
import {existsSync, mkdirSync, rmSync, writeFileSync} from "node:fs";
import {join} from "node:path";
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import {setOtaConfiguration, Zcl} from "zigbee-herdsman";
import type {OtaDataSettings, OtaSource, OtaUpdateAvailableResult} from "zigbee-herdsman/dist/controller/tstype";
import Device from "../model/device";
import type {Zigbee2MQTTAPI} from "../types/api";
import dataDir from "../util/data";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
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" | "scheduled";
type UpdateState = 'updating' | 'idle' | 'available';
interface UpdatePayload {
update_available?: boolean
// eslint-disable-next-line camelcase
export interface UpdatePayload {
update: {
progress?: number, remaining?: number, state: UpdateState,
installed_version: number | null, latest_version: number | null
}
progress?: number;
remaining?: number;
state: UpdateState;
installed_version: number | null;
latest_version: number | null;
latest_source: string | null;
latest_release_notes: string | null;
};
}
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/ota_update/.+$`);
const topicRegex =
new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check)`, 'i');
/**
* Write to `dataDir` and return created path
*/
function writeFirmwareHexToDataDir(hex: string, fileName: string | undefined, deviceIeee: string): string {
if (!fileName) {
fileName = `${deviceIeee}_${Date.now()}`;
}
const baseDir = dataDir.joinPath("ota");
if (!existsSync(baseDir)) {
mkdirSync(baseDir, {recursive: true});
}
const filePath = join(baseDir, fileName);
writeFileSync(filePath, Buffer.from(hex, "hex"));
return filePath;
}
export default class OTAUpdate extends Extension {
private inProgress = new Set();
private lastChecked: {[s: string]: number} = {};
private legacyApi = settings.get().advanced.legacy_api;
#topicRegex = new RegExp(
`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check|schedule|unschedule)/?(downgrade|abort)?`,
"i",
);
#inProgress = new Set<string>();
#lastChecked = new Map<string, number>();
// biome-ignore lint/suspicious/useAwait: API
override async start(): Promise<void> {
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
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);
setOtaConfiguration(dataDir.getPath(), settings.get().ota.zigbee_ota_override_index_location);
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state, remove them.
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
this.#removeProgressAndRemainingFromState(device);
// Reset update state, e.g. when Z2M restarted during update.
if (this.state.get(device).update?.state === "updating") {
this.state.get(device).update.state = "idle";
}
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)) {
this.removeProgressAndRemainingFromState(device);
}
}
private removeProgressAndRemainingFromState(device: Device): void {
delete this.state.get(device).update?.progress;
delete this.state.get(device).update?.remaining;
// mostly intended for testing
clearState(): void {
this.#inProgress.clear();
this.#lastChecked.clear();
}
#removeProgressAndRemainingFromState(device: Device): void {
const deviceState = this.state.get(device);
if (deviceState.update) {
delete deviceState.update.progress;
delete deviceState.update.remaining;
}
}
@bind private async onZigbeeEvent(data: eventdata.DeviceMessage): Promise<void> {
if (data.type !== 'commandQueryNextImageRequest' || !data.device.definition ||
this.inProgress.has(data.device.ieeeAddr)) return;
if (data.type !== "commandQueryNextImageRequest" || !data.device.definition || this.#inProgress.has(data.device.ieeeAddr)) {
return;
}
// `commandQueryNextImageRequest` check above should ensures this is valid but...
assert(
data.meta.zclTransactionSequenceNumber !== undefined,
"Missing 'queryNextImageRequest' transaction sequence number (cannot match reply)",
);
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 && !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) return;
if (data.device.zh.scheduledOta) {
// allow custom source to override check for definition `ota`
if (data.device.zh.scheduledOta?.url !== undefined || data.device.definition.ota) {
this.#inProgress.add(data.device.ieeeAddr);
this.lastChecked[data.device.ieeeAddr] = Date.now();
let availableResult: zhc.OtaUpdateAvailableResult = null;
try {
availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, logger, data.data);
} catch (e) {
supportsOTA = false;
logger.debug(`Failed to check if update available for '${data.device.name}' (${e.message})`);
}
logger.info(`Updating '${data.device.name}' to latest firmware`);
const payload = this.getEntityPublishPayload(data.device, availableResult ?? 'idle');
this.publishEntityState(data.device, payload);
if (availableResult?.available) {
const message = `Update available for '${data.device.name}'`;
logger.info(message);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: 'available', device: data.device.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `ota_update`, message, meta}),
try {
const otaSettings = settings.get().ota;
const [, toVersion] = await this.#update(
undefined, // uses internally registered schedule
data.device,
data.data as Zcl.ClustersTypes.TClusterCommandPayload<"genOta", "queryNextImageRequest">,
data.meta.zclTransactionSequenceNumber,
{
// fallbacks are only to satisfy typing, should always be defined from settings defaults
requestTimeout: otaSettings.image_block_request_timeout ?? /* v8 ignore next */ 150000,
responseDelay: otaSettings.image_block_response_delay ?? /* v8 ignore next */ 250,
baseSize: otaSettings.default_maximum_data_size ?? /* v8 ignore next */ 50,
},
data.endpoint,
);
if (toVersion === undefined) {
logger.info(`No OTA image currently available for '${data.device.name}'. Unscheduled.`);
}
} catch (e) {
logger.debug(`OTA update of '${data.device.name}' failed (${e}). Retry scheduled for next request.`);
this.#removeProgressAndRemainingFromState(data.device);
await this.publishEntityState(data.device, this.#getEntityPublishPayload(data.device, "scheduled"));
}
this.#inProgress.delete(data.device.ieeeAddr);
return; // we're done
}
}
if (data.device.definition.ota) {
if (!data.device.options.disable_automatic_update_check && !settings.get().ota.disable_automatic_update_check) {
// 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 deviceLastChecked = this.#lastChecked.get(data.device.ieeeAddr);
const check = deviceLastChecked !== undefined ? Date.now() - deviceLastChecked > updateCheckInterval : true;
if (!check) {
return;
}
this.#inProgress.add(data.device.ieeeAddr);
this.#lastChecked.set(data.device.ieeeAddr, Date.now());
let availableResult: OtaUpdateAvailableResult | undefined;
try {
// auto-check defaults to zigbee-OTA + potential local index, and never `downgrade`
availableResult = await data.device.zh.checkOta(
{downgrade: false},
data.data as Zcl.ClustersTypes.TClusterCommandPayload<"genOta", "queryNextImageRequest">,
data.device.otaExtraMetas,
data.endpoint,
);
} catch (error) {
logger.debug(`Failed to check if OTA update available for '${data.device.name}' (${error})`);
}
await this.publishEntityState(data.device, this.#getEntityPublishPayload(data.device, availableResult ?? "idle"));
if (availableResult?.available) {
logger.info(`OTA update available for '${data.device.name}'`);
}
}
}
// Respond to the OTA request: respond with NO_IMAGE_AVAILABLE (0x98) (so the client stops requesting OTAs)
const endpoint = data.device.zh.endpoints.find((e) => e.supportsOutputCluster('genOta')) || data.endpoint;
await endpoint.commandResponse('genOta', 'queryNextImageResponse', {status: 0x98});
// Respond to stop the client from requesting OTAs
await data.endpoint.commandResponse(
"genOta",
"queryNextImageResponse",
{status: Zcl.Status.NO_IMAGE_AVAILABLE},
undefined,
data.meta.zclTransactionSequenceNumber,
);
logger.debug(`Responded to OTA request of '${data.device.name}' with 'NO_IMAGE_AVAILABLE'`);
this.#inProgress.delete(data.device.ieeeAddr);
}
private async readSoftwareBuildIDAndDateCode(device: Device, sendWhen: 'active' | 'immediate'):
Promise<{softwareBuildID: string, dateCode: string}> {
async #readSoftwareBuildIDAndDateCode(
device: Device,
sendPolicy?: "immediate",
): Promise<{softwareBuildID: string; dateCode: string} | undefined> {
try {
const endpoint = device.zh.endpoints.find((e) => e.supportsInputCluster('genBasic'));
const result = await endpoint.read('genBasic', ['dateCode', 'swBuildId'], {sendWhen});
const endpoint = device.zh.endpoints.find((e) => e.supportsInputCluster("genBasic"));
assert(endpoint);
const result = await endpoint.read("genBasic", ["dateCode", "swBuildId"], {sendPolicy});
return {softwareBuildID: result.swBuildId, dateCode: result.dateCode};
} catch (e) {
return null;
} catch {
return undefined;
}
}
private getEntityPublishPayload(device: Device, state: zhc.OtaUpdateAvailableResult | UpdateState,
progress: number=null, remaining: number=null): UpdatePayload {
const deviceUpdateState = this.state.get(device).update;
const payload: UpdatePayload = {update: {
state: typeof state === 'string' ? state : (state.available ? 'available' : 'idle'),
installed_version: typeof state === 'string' ?
deviceUpdateState?.installed_version : state.currentFileVersion,
latest_version: typeof state === 'string' ?
deviceUpdateState?.latest_version : state.otaFileVersion,
}};
if (progress !== null) payload.update.progress = progress;
if (remaining !== null) payload.update.remaining = Math.round(remaining);
#getEntityPublishPayload(device: Device, state: OtaUpdateAvailableResult | UpdateState, progress?: number, remaining?: number): UpdatePayload {
const deviceUpdateState = this.state.get(device).update as UpdatePayload["update"];
const update: UpdatePayload["update"] =
typeof state === "string"
? {
state,
installed_version: deviceUpdateState?.installed_version,
latest_version: deviceUpdateState?.latest_version,
latest_source: deviceUpdateState?.latest_source,
latest_release_notes: deviceUpdateState?.latest_release_notes,
}
: {
state: state.available ? "available" : "idle",
installed_version: state.current.fileVersion,
latest_version: state.availableMeta?.fileVersion ?? state.current.fileVersion,
latest_source: state.availableMeta?.url || null,
latest_release_notes: state.availableMeta?.releaseNotes || null,
};
/* istanbul ignore else */
if (this.legacyApi) {
payload.update_available = typeof state === 'string' ? state === 'available' : state.available;
if (progress !== undefined) {
update.progress = progress;
}
return payload;
if (remaining !== undefined) {
update.remaining = Math.round(remaining);
}
return {update};
}
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
if ((!this.legacyApi || !data.topic.match(legacyTopicRegex)) && !data.topic.match(topicRegex)) {
return null;
const topicMatch = data.topic.match(this.#topicRegex);
if (!topicMatch) {
return;
}
const message = utils.parseJSON(data.message, data.message);
const ID = (typeof message === 'object' && message.hasOwnProperty('id') ? message.id : message) as string;
const device = this.zigbee.resolveEntity(ID);
const type = data.topic.substring(data.topic.lastIndexOf('/') + 1);
const responseData: {id: string, updateAvailable?: boolean, from?: string, to?: string}= {id: ID};
let error = null;
let errorStack = null;
const message = utils.parseJSON(data.message, data.message) as
| Zigbee2MQTTAPI["bridge/request/device/ota_update/check"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/check/downgrade"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update/downgrade"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update/abort"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule/downgrade"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/unschedule"];
// TODO: deprecated 3.0 should remove string payload, enforce object
const messageObject = typeof message === "object";
if (messageObject) {
assert(message.id, "Invalid payload");
}
const id = (messageObject ? message.id : message) as string;
const device = this.zigbee.resolveEntity(id);
const type = topicMatch[1] as "check" | "update" | "schedule" | "unschedule";
const downgrade = topicMatch[2] === "downgrade";
const abort = topicMatch[2] === "abort";
let error: string | undefined;
let errorStack: string | undefined;
if (!(device instanceof Device)) {
error = `Device '${ID}' does not exist`;
} else if (!device.definition || !device.definition.ota) {
error = `Device '${device.name}' does not support OTA updates`;
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `not_supported`, device: device.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `ota_update`, message: error, meta}),
error = `Device '${id}' does not exist`;
} else if (this.#inProgress.has(device.ieeeAddr)) {
if (abort) {
device.zh.abortOta();
this.#inProgress.delete(device.ieeeAddr);
// cleanup same as a fail
this.#removeProgressAndRemainingFromState(device);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "available"));
await this.mqtt.publish(
"bridge/response/device/ota_update/update/abort",
stringify(utils.getResponse<"bridge/response/device/ota_update/update/abort">(message, {id})),
);
} else {
// also guards against scheduling while check/update op in progress that could result in undesired OTA state
error = `OTA update or check for update already in progress for '${device.name}'`;
}
} else if (this.inProgress.has(device.ieeeAddr)) {
error = `Update or check for update already in progress for '${device.name}'`;
} else {
this.inProgress.add(device.ieeeAddr);
switch (type) {
case "check": {
this.#inProgress.add(device.ieeeAddr);
if (type === 'check') {
const msg = `Checking if update available for '${device.name}'`;
logger.info(msg);
const source: OtaSource = {downgrade};
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `checking_if_available`, device: device.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `ota_update`, message: msg, meta}),
);
}
if (messageObject) {
const payload = message as
| Zigbee2MQTTAPI["bridge/request/device/ota_update/check"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/check/downgrade"];
try {
const availableResult = await device.definition.ota.isUpdateAvailable(device.zh, logger);
const msg = `${availableResult.available ? 'Update' : 'No update'} available for '${device.name}'`;
logger.info(msg);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {
status: availableResult.available ? 'available' : 'not_available', device: device.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `ota_update`, message: msg, meta}),
);
if (payload.url) {
source.url = payload.url;
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
const payload = this.getEntityPublishPayload(device, availableResult);
this.publishEntityState(device, payload);
this.lastChecked[device.ieeeAddr] = Date.now();
responseData.updateAvailable = availableResult.available;
} catch (e) {
error = `Failed to check if update available for '${device.name}' (${e.message})`;
errorStack = e.stack;
logger.info(`Checking if OTA update available for '${device.name}'`);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `check_failed`, device: device.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `ota_update`, message: error, meta}),
);
try {
const availableResult = await device.zh.checkOta(source, undefined, device.otaExtraMetas);
logger.info(`${availableResult.available ? "" : "No "}OTA update available for '${device.name}'`);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, availableResult));
this.#lastChecked.set(device.ieeeAddr, Date.now());
const response = utils.getResponse<"bridge/response/device/ota_update/check">(message, {
id,
update_available: availableResult.available,
downgrade: source.downgrade,
source: availableResult.availableMeta?.url,
release_notes: availableResult.availableMeta?.releaseNotes,
});
await this.mqtt.publish("bridge/response/device/ota_update/check", stringify(response));
} catch (e) {
error = `Failed to check if OTA update available for '${device.name}' (${(e as Error).message})`;
errorStack = (e as Error).stack;
}
}
} else { // type === 'update'
const msg = `Updating '${device.name}' to latest firmware`;
logger.info(msg);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `update_in_progress`, device: device.name};
this.mqtt.publish(
'bridge/log',
stringify({type: `ota_update`, message: msg, meta}),
);
break;
}
try {
const onProgress = (progress: number, remaining: number): void => {
let msg = `Update of '${device.name}' at ${progress.toFixed(2)}%`;
if (remaining) {
msg += `, ≈ ${Math.round(remaining / 60)} minutes remaining`;
}
case "update": {
if (abort) {
error = `No OTA in progress to abort for device '${device.name}'`;
break;
}
logger.info(msg);
this.#inProgress.add(device.ieeeAddr);
const payload = this.getEntityPublishPayload(device, 'updating', progress, remaining);
this.publishEntityState(device, payload);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `update_progress`, device: device.name, progress};
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: msg, meta}));
}
const otaSettings = settings.get().ota;
const source: OtaSource = {downgrade};
const dataSettings: OtaDataSettings = {
// fallbacks are only to satisfy typing, should always be defined from settings defaults
requestTimeout: otaSettings.image_block_request_timeout ?? /* v8 ignore next */ 150000,
responseDelay: otaSettings.image_block_response_delay ?? /* v8 ignore next */ 250,
baseSize: otaSettings.default_maximum_data_size ?? /* v8 ignore next */ 50,
};
const from_ = await this.readSoftwareBuildIDAndDateCode(device, 'immediate');
const fileVersion = await device.definition.ota.updateToLatest(device.zh, logger, onProgress);
logger.info(`Finished update of '${device.name}'`);
this.eventBus.emitReconfigure({device});
this.removeProgressAndRemainingFromState(device);
const payload = this.getEntityPublishPayload(device,
{available: false, currentFileVersion: fileVersion, otaFileVersion: fileVersion});
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();
if (messageObject) {
const payload = message as
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update/downgrade"];
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `update_succeeded`, device: device.name, from: from_, to};
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message, meta}));
if (payload.hex) {
assert(payload.hex.data);
// write to `dataDir` and pass created path as source URL
source.url = writeFirmwareHexToDataDir(payload.hex.data, payload.hex.file_name, device.ieeeAddr);
} else if (payload.url) {
source.url = payload.url;
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
if (payload.image_block_request_timeout) {
dataSettings.requestTimeout = payload.image_block_request_timeout;
}
if (payload.image_block_response_delay) {
dataSettings.responseDelay = payload.image_block_response_delay;
}
if (payload.default_maximum_data_size) {
dataSettings.baseSize = payload.default_maximum_data_size;
}
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
} catch (e) {
logger.debug(`Update of '${device.name}' failed (${e})`);
error = `Update of '${device.name}' failed (${e.message})`;
errorStack = e.stack;
this.removeProgressAndRemainingFromState(device);
const payload = this.getEntityPublishPayload(device, 'available');
this.publishEntityState(device, payload);
logger.info(`OTA updating '${device.name}' to ${downgrade ? "previous" : "latest"} firmware`);
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
const meta = {status: `update_failed`, device: device.name};
this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: error, meta}));
try {
const firmwareFrom = await this.#readSoftwareBuildIDAndDateCode(device, "immediate");
const [fromVersion, toVersion] = await this.#update(source, device, undefined, undefined, dataSettings);
if (toVersion === undefined) {
error = `Update of '${device.name}' failed (No image currently available)`;
break;
}
const firmwareTo = await this.#readSoftwareBuildIDAndDateCode(device);
const response = utils.getResponse<"bridge/response/device/ota_update/update">(message, {
id,
from: {
file_version: fromVersion,
software_build_id: firmwareFrom?.softwareBuildID,
date_code: firmwareFrom?.dateCode,
},
to: {file_version: toVersion, software_build_id: firmwareTo?.softwareBuildID, date_code: firmwareTo?.dateCode},
});
await this.mqtt.publish("bridge/response/device/ota_update/update", stringify(response));
} catch (e) {
logger.debug(`OTA update of '${device.name}' failed (${e})`);
error = `OTA update of '${device.name}' failed (${(e as Error).message})`;
errorStack = (e as Error).stack;
this.#removeProgressAndRemainingFromState(device);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "available"));
}
break;
}
case "schedule": {
const source: OtaSource = {downgrade};
if (messageObject) {
const payload = message as
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule/downgrade"];
if (payload.hex) {
assert(payload.hex.data);
// write to `dataDir` and pass created path as source URL
source.url = writeFirmwareHexToDataDir(payload.hex.data, payload.hex.file_name, device.ieeeAddr);
} else if (payload.url) {
source.url = payload.url;
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
device.zh.scheduleOta(source);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "scheduled"));
const response = utils.getResponse<"bridge/response/device/ota_update/schedule">(message, {id, url: source.url});
await this.mqtt.publish("bridge/response/device/ota_update/schedule", stringify(response));
break;
}
case "unschedule": {
if (device.zh.scheduledOta?.url?.startsWith(dataDir.joinPath("ota"))) {
rmSync(device.zh.scheduledOta.url, {force: true});
}
device.zh.unscheduleOta();
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "idle"));
const response = utils.getResponse<"bridge/response/device/ota_update/unschedule">(message, {id});
await this.mqtt.publish("bridge/response/device/ota_update/unschedule", stringify(response));
break;
}
}
this.inProgress.delete(device.ieeeAddr);
}
const triggeredViaLegacyApi = data.topic.match(legacyTopicRegex);
if (!triggeredViaLegacyApi) {
const response = utils.getResponse(message, responseData, error);
await this.mqtt.publish(`bridge/response/device/ota_update/${type}`, stringify(response));
this.#inProgress.delete(device.ieeeAddr);
}
if (error) {
const response = utils.getResponse(message, {}, error);
await this.mqtt.publish(`bridge/response/device/ota_update/${type}`, stringify(response));
logger.error(error);
errorStack && logger.debug(errorStack);
if (errorStack) {
logger.debug(errorStack);
}
}
}
/**
* Do the bulk of the update work (hand over to zigbee-herdsman, then re-interview).
*
* `dataSettings` object may be mutated by zigbee-herdsman to fit request (e.g. known manuf quirk)
*/
async #update(
source: OtaSource | undefined,
device: Device,
requestPayload: Zcl.ClustersTypes.TClusterCommandPayload<"genOta", "queryNextImageRequest"> | undefined,
requestTsn: number | undefined,
dataSettings: OtaDataSettings,
endpoint?: zh.Endpoint,
): Promise<[from: number, to: number | undefined]> {
const [from, to] = await device.zh.updateOta(
source,
requestPayload,
requestTsn,
device.otaExtraMetas,
async (progress, remaining) => {
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "updating", progress, remaining));
},
dataSettings,
endpoint,
);
if (to === undefined) {
this.#removeProgressAndRemainingFromState(device);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, {available: false, current: from}));
return [from.fileVersion, undefined];
}
logger.info(`Finished update of '${device.name}'`);
this.#removeProgressAndRemainingFromState(device);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, {available: false, current: to}));
logger.info(() => `Device '${device.name}' was OTA updated from '${from.fileVersion}' to '${to.fileVersion}'`);
// OTA update can bring new features & co, force full re-interview and re-configure, same as a "device joined"
if (device.zh.meta.configured !== undefined) {
delete device.zh.meta.configured;
device.zh.save();
}
setTimeout(() => {
device.reInterview(this.eventBus).catch((error) => {
logger.error(`${error.message}. Re-try manually after some time.`);
});
}, 5000);
return [from.fileVersion, to.fileVersion];
}
}
+150 -142
View File
@@ -1,165 +1,145 @@
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import type * as zhc from "zigbee-herdsman-converters";
import * as settings from '../util/settings';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import philips from 'zigbee-herdsman-converters/lib/philips';
import logger from '../util/logger';
import utils from '../util/utils';
import Extension from './extension';
import stringify from 'json-stable-stringify-without-jsonify';
import Group from '../model/group';
import Device from '../model/device';
import bind from 'bind-decorator';
import Device from "../model/device";
import Group from "../model/group";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
const topicRegex = new RegExp(`^(.+?)(?:/(${utils.endpointNames.join('|')}|\\d+))?/(get|set)(?:/(.+))?`);
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'];
// TODO: get rid of this, use class member
let topicGetSetRegex: RegExp;
// Used by `publish.test.ts` to reload regex when changing `mqtt.base_topic`.
export const loadTopicGetSetRegex = (): void => {
topicGetSetRegex = new RegExp(`^${settings.get().mqtt.base_topic}/(?!bridge)(.+?)/(get|set)(?:/(.+))?$`);
};
// Legacy: don't provide default converters anymore, this is required by older z2m installs not saving group members
const defaultGroupConverters = [
zigbeeHerdsmanConverters.toZigbeeConverters.light_onoff_brightness,
zigbeeHerdsmanConverters.toZigbeeConverters.light_color_colortemp,
philips.tz.effect, // Support Hue effects for groups
zigbeeHerdsmanConverters.toZigbeeConverters.ignore_transition,
zigbeeHerdsmanConverters.toZigbeeConverters.cover_position_tilt,
zigbeeHerdsmanConverters.toZigbeeConverters.thermostat_occupied_heating_setpoint,
zigbeeHerdsmanConverters.toZigbeeConverters.tint_scene,
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_step,
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_step,
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_move,
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_step,
];
const STATE_VALUES: ReadonlyArray<string> = ["on", "off", "toggle", "open", "close", "stop", "lock", "unlock"];
const SCENE_CONVERTER_KEYS: ReadonlyArray<string> = ["scene_store", "scene_add", "scene_remove", "scene_remove_all", "scene_rename"];
interface ParsedTopic {ID: string, endpoint: string, attribute: string, type: 'get' | 'set'}
interface ParsedTopic {
ID: string;
endpoint: string | undefined;
attribute: string;
type: "get" | "set";
}
export default class Publish extends Extension {
async start(): Promise<void> {
// biome-ignore lint/suspicious/useAwait: API
override async start(): Promise<void> {
loadTopicGetSetRegex();
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
}
parseTopic(topic: string): ParsedTopic | null {
const match = topic.match(topicRegex);
parseTopic(topic: string): ParsedTopic | undefined {
// The function supports the following topic formats (below are for 'set'. 'get' will look the same):
// - <base_topic>/device_name/set (auto-matches endpoint and attribute is defined in the payload)
// - <base_topic>/device_name/set/attribute (default endpoint used)
// - <base_topic>/device_name/endpoint/set (attribute is defined in the payload)
// - <base_topic>/device_name/endpoint/set/attribute (payload is the value)
// Make the rough split on get/set keyword.
// Before the get/set is the device name and optional endpoint name.
// After it there will be an optional attribute name.
const match = topic.match(topicGetSetRegex);
if (!match) {
return null;
return undefined;
}
const ID = match[1].replace(`${settings.get().mqtt.base_topic}/`, '');
// If we didn't replace base_topic we received something we don't care about
if (ID === match[1] || ID.match(/bridge/)) {
return null;
}
const deviceNameAndEndpoint = match[1];
const attribute = match[3];
return {ID: ID, endpoint: match[2], type: match[3] as 'get' | 'set', attribute: match[4]};
// Now parse the device/group name, and endpoint name
const entity = this.zigbee.resolveEntityAndEndpoint(deviceNameAndEndpoint);
return {ID: entity.ID, endpoint: entity.endpointID, type: match[2] as "get" | "set", attribute: attribute};
}
parseMessage(parsedTopic: ParsedTopic, data: eventdata.MQTTMessage): KeyValue | null {
parseMessage(parsedTopic: ParsedTopic, data: eventdata.MQTTMessage): KeyValue | undefined {
if (parsedTopic.attribute) {
try {
return {[parsedTopic.attribute]: JSON.parse(data.message)};
} catch (e) {
} catch {
return {[parsedTopic.attribute]: data.message};
}
} else {
try {
return JSON.parse(data.message);
} catch (e) {
if (stateValues.includes(data.message.toLowerCase())) {
return {state: data.message};
} else {
return null;
}
} catch {
return STATE_VALUES.includes(data.message.toLowerCase()) ? {state: data.message} : undefined;
}
}
}
legacyLog(payload: KeyValue): void {
/* istanbul ignore else */
if (settings.get().advanced.legacy_api) {
this.mqtt.publish('bridge/log', stringify(payload));
}
}
legacyRetrieveState(re: Device | Group, converter: zhc.ToZigbeeConverter, result: zhc.ToZigbeeConverterResult,
target: zh.Endpoint | zh.Group, key: string, meta: zhc.ToZigbeeConverterGetMeta): void {
// It's possible for devices to get out of sync when writing an attribute that's not reportable.
// So here we re-read the value after a specified timeout, this timeout could for example be the
// transition time of a color change or for forcing a state read for devices that don't
// automatically report a new state when set.
// When reporting is requested for a device (report: true in device-specific settings) we won't
// ever issue a read here, as we assume the device will properly report changes.
// Only do this when the retrieve_state option is enabled for this device.
// retrieve_state == deprecated
if (re instanceof Device && result && result.hasOwnProperty('readAfterWriteTime') &&
re.options.retrieve_state
) {
setTimeout(() => converter.convertGet(target, key, meta), result.readAfterWriteTime);
}
}
updateMessageHomeAssistant(message: KeyValue, entityState: KeyValue): void {
/**
* Home Assistant always publishes 'state', even when e.g. only setting
* the color temperature. This would lead to 2 zigbee publishes, where the first one
* (state) is probably unnecessary.
*/
if (settings.get().homeassistant) {
const hasColorTemp = message.hasOwnProperty('color_temp');
const hasColor = message.hasOwnProperty('color');
const hasBrightness = message.hasOwnProperty('brightness');
const isOn = entityState.state === 'ON' ? true : false;
if (isOn && (hasColorTemp || hasColor) && !hasBrightness) {
if (settings.get().homeassistant.enabled) {
const hasColorTemp = message.color_temp !== undefined;
const hasColor = message.color !== undefined;
const hasBrightness = message.brightness !== undefined;
if (entityState.state === "ON" && (hasColorTemp || hasColor) && !hasBrightness) {
delete message.state;
logger.debug('Skipping state because of Home Assistant');
logger.debug("Skipping state because of Home Assistant");
}
}
}
@bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise<void> {
const parsedTopic = this.parseTopic(data.topic);
if (!parsedTopic) return;
if (!parsedTopic) {
return;
}
const re = this.zigbee.resolveEntity(parsedTopic.ID);
if (re == null) {
this.legacyLog({type: `entity_not_found`, message: {friendly_name: parsedTopic.ID}});
if (!re) {
logger.error(`Entity '${parsedTopic.ID}' is unknown`);
return;
}
// Get entity details
const definition = re instanceof Device ? re.definition : re.membersDefinitions();
let definition: zhc.Definition | zhc.Definition[];
if (re instanceof Device) {
if (!re.definition) {
logger.error(`Cannot publish to unsupported device '${re.name}'`);
return;
}
definition = re.definition;
} else {
definition = re.membersDefinitions();
}
const target = re instanceof Group ? re.zh : re.endpoint(parsedTopic.endpoint);
if (target == null) {
if (!target) {
logger.error(`Device '${re.name}' has no endpoint '${parsedTopic.endpoint}'`);
return;
}
const device = re instanceof Device ? re.zh : null;
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;
let converters: zhc.ToZigbeeConverter[];
{
if (Array.isArray(definition)) {
const c = new Set(definition.map((d) => d.toZigbee).flat());
if (c.size == 0) converters = defaultGroupConverters;
else converters = Array.from(c);
} else if (definition) {
converters = definition.toZigbee;
} else {
converters = [zigbeeHerdsmanConverters.toZigbeeConverters.read,
zigbeeHerdsmanConverters.toZigbeeConverters.write];
}
}
// Convert the MQTT message to a Zigbee message.
const message = this.parseMessage(parsedTopic, data);
if (message == null) {
if (!message) {
logger.error(`Invalid message '${message}', skipping...`);
return;
}
const device = re instanceof Device ? re.zh : undefined;
const entitySettings = re.options;
const entityState = this.state.get(re);
const membersState =
re instanceof Group
? // biome-ignore lint/style/noNonNullAssertion: TODO: biome migration: might be a bit much assumed here?
Object.fromEntries(re.zh.members.map((e) => [e.deviceIeeeAddress, this.state.get(this.zigbee.resolveEntity(e.deviceIeeeAddress)!)]))
: undefined;
const converters = this.getDefinitionConverters(definition);
this.updateMessageHomeAssistant(message, entityState);
/**
@@ -172,63 +152,82 @@ export default class Publish extends Extension {
* bulb off => move state & brightness to the front
*/
const entries = Object.entries(message);
const sorter = typeof message.state === 'string' && message.state.toLowerCase() === 'off' ? 1 : -1;
entries.sort((a) => (['state', 'brightness', 'brightness_percent'].includes(a[0]) ? sorter : sorter * -1));
const sorter = typeof message.state === "string" && message.state.toLowerCase() === "off" ? 1 : -1;
entries.sort((a) => (["state", "brightness", "brightness_percent"].includes(a[0]) ? sorter : sorter * -1));
// For each attribute call the corresponding converter
const usedConverters: {[s: number]: zhc.ToZigbeeConverter[]} = {};
const usedConverters: {[s: number]: zhc.Tz.Converter[]} = {};
const toPublish: {[s: number | string]: KeyValue} = {};
const toPublishEntity: {[s: number | string]: Device | Group} = {};
const addToToPublish = (entity: Device | Group, payload: KeyValue): void => {
const ID = entity.ID;
if (!(ID in toPublish)) {
toPublish[ID] = {};
toPublishEntity[ID] = entity;
}
toPublish[ID] = {...toPublish[ID], ...payload};
};
for (let [key, value] of entries) {
const endpointNames = re instanceof Device ? re.getEndpointNames() : [];
const propertyEndpointRegex = new RegExp(`^(.*?)_(${endpointNames.join("|")})$`);
let scenesChanged = false;
for (const entry of entries) {
let key = entry[0];
const value = entry[1];
let endpointName = parsedTopic.endpoint;
let localTarget = target;
let endpointOrGroupID = utils.isEndpoint(target) ? target.ID : target.groupID;
let endpointOrGroupID = utils.isZHEndpoint(target) ? target.ID : target.groupID;
// When the key has a endpointName included (e.g. state_right), this will override the target.
const propertyEndpointMatch = key.match(propertyEndpointRegex);
if (re instanceof Device && propertyEndpointMatch) {
endpointName = propertyEndpointMatch[2];
key = propertyEndpointMatch[1];
localTarget = re.endpoint(endpointName);
if (localTarget == null) {
logger.error(`Device '${re.name}' has no endpoint '${endpointName}'`);
continue;
}
// biome-ignore lint/style/noNonNullAssertion: endpointName is always matched to an existing endpoint of the device since `propertyEndpointRegex` only contains valid endpoints for this device
localTarget = re.endpoint(endpointName)!;
endpointOrGroupID = localTarget.ID;
}
if (!usedConverters.hasOwnProperty(endpointOrGroupID)) usedConverters[endpointOrGroupID] = [];
const converter = converters.find((c) => c.key.includes(key));
if (usedConverters[endpointOrGroupID] === undefined) usedConverters[endpointOrGroupID] = [];
// Match any key if the toZigbee converter defines no key.
const converter = converters.find(
(c) =>
(!c.key || c.key.includes(key)) && (re instanceof Group || !c.endpoints || (endpointName && c.endpoints.includes(endpointName))),
);
if (parsedTopic.type === 'set' && usedConverters[endpointOrGroupID].includes(converter)) {
if (parsedTopic.type === "set" && converter && usedConverters[endpointOrGroupID].includes(converter)) {
// Use a converter for set only once
// (e.g. light_onoff_brightness converters can convert state and brightness)
continue;
}
if (!converter) {
logger.error(`No converter available for '${key}' (${stringify(message[key])})`);
logger.error(`No converter available for '${key}' on '${re.name}': (${stringify(message[key])})`);
continue;
}
// If the endpoint_name name is a number, try to map it to a friendlyName
if (!isNaN(Number(endpointName)) && re.isDevice() && utils.isEndpoint(localTarget) &&
re.endpointName(localTarget)) {
if (!Number.isNaN(Number(endpointName)) && re.isDevice() && utils.isZHEndpoint(localTarget) && re.endpointName(localTarget)) {
endpointName = re.endpointName(localTarget);
}
// Converter didn't return a result, skip
const meta = {endpoint_name: endpointName, options: entitySettings, message: {...message}, logger, device,
state: entityState, membersState, mapped: definition};
const entitySettingsKeyValue: KeyValue = entitySettings;
const meta: zhc.Tz.Meta = {
endpoint_name: endpointName,
options: entitySettingsKeyValue,
message: {...message},
device,
state: entityState,
membersState,
mapped: definition,
/* v8 ignore next */
publish: (payload: KeyValue) => this.publishEntityState(re, payload),
};
// Strip endpoint name from meta.message properties.
if (endpointName) {
@@ -242,11 +241,12 @@ export default class Publish extends Extension {
}
try {
if (parsedTopic.type === 'set' && converter.convertSet) {
if (parsedTopic.type === "set" && converter.convertSet) {
logger.debug(`Publishing '${parsedTopic.type}' '${key}' to '${re.name}'`);
const result = await converter.convertSet(localTarget, key, value, meta);
const optimistic = !entitySettings.hasOwnProperty('optimistic') || entitySettings.optimistic;
if (result && result.state && optimistic) {
const optimistic = entitySettings.optimistic === undefined || entitySettings.optimistic;
if (result?.state && optimistic) {
const msg = result.state;
if (endpointName) {
@@ -262,14 +262,13 @@ export default class Publish extends Extension {
addToToPublish(re, msg);
}
if (result && result.membersState && optimistic) {
if (result?.membersState && optimistic) {
for (const [ieeeAddr, state] of Object.entries(result.membersState)) {
addToToPublish(this.zigbee.resolveEntity(ieeeAddr), state);
// biome-ignore lint/style/noNonNullAssertion: might be a bit much assumed here?
addToToPublish(this.zigbee.resolveEntity(ieeeAddr)!, state);
}
}
this.legacyRetrieveState(re, converter, result, localTarget, key, meta);
} else if (parsedTopic.type === 'get' && converter.convertGet) {
} else if (parsedTopic.type === "get" && converter.convertGet) {
logger.debug(`Publishing get '${parsedTopic.type}' '${key}' to '${re.name}'`);
await converter.convertGet(localTarget, key, meta);
} else {
@@ -277,26 +276,35 @@ export default class Publish extends Extension {
continue;
}
} catch (error) {
const message =
`Publish '${parsedTopic.type}' '${key}' to '${re.name}' failed: '${error}'`;
const message = `Publish '${parsedTopic.type}' '${key}' to '${re.name}' failed: '${error}'`;
logger.error(message);
logger.debug(error.stack);
this.legacyLog({type: `zigbee_publish_error`, message, meta: {friendly_name: re.name}});
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((error as Error).stack!);
}
usedConverters[endpointOrGroupID].push(converter);
}
for (const [ID, payload] of Object.entries(toPublish)) {
if (Object.keys(payload).length != 0) {
this.publishEntityState(toPublishEntity[ID], payload);
if (!scenesChanged && converter.key) {
scenesChanged = converter.key.some((k) => SCENE_CONVERTER_KEYS.includes(k));
}
}
for (const [ID, payload] of Object.entries(toPublish)) {
if (!utils.objectIsEmpty(payload)) {
await this.publishEntityState(toPublishEntity[ID], payload);
}
}
const scenesChanged = Object.values(usedConverters)
.some((cl) => cl.some((c) => c.key.some((k) => sceneConverterKeys.includes(k))));
if (scenesChanged) {
this.eventBus.emitScenesChanged();
this.eventBus.emitScenesChanged({entity: re});
}
}
private getDefinitionConverters(definition: zhc.Definition | zhc.Definition[]): ReadonlyArray<zhc.Tz.Converter> {
if (Array.isArray(definition)) {
return definition.length ? Array.from(new Set(definition.flatMap((d) => d.toZigbee))) : [];
}
return definition?.toZigbee;
}
}
+89 -67
View File
@@ -1,42 +1,55 @@
import * as settings from '../util/settings';
import logger from '../util/logger';
import debounce from 'debounce';
import Extension from './extension';
import stringify from 'json-stable-stringify-without-jsonify';
import bind from 'bind-decorator';
import utils from '../util/utils';
import assert from "node:assert";
type DebounceFunction = (() => void) & { clear(): void; } & { flush(): void; };
import bind from "bind-decorator";
import debounce from "debounce";
import stringify from "json-stable-stringify-without-jsonify";
import throttle from "throttleit";
import * as zhc from "zigbee-herdsman-converters";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
type DebounceFunction = (() => void) & {clear(): void} & {flush(): void};
export default class Receive extends Extension {
// TODO: move all to `Map`
private elapsed: {[s: string]: number} = {};
private debouncers: {[s: string]: {payload: KeyValue, publish: DebounceFunction }} = {};
private debouncers: {[s: string]: {payload: KeyValue; publish: DebounceFunction}} = {};
private throttlers: {[s: string]: {publish: PublishEntityState}} = {};
async start(): Promise<void> {
// biome-ignore lint/suspicious/useAwait: API
override async start(): Promise<void> {
this.eventBus.onPublishEntityState(this, this.onPublishEntityState);
this.eventBus.onDeviceMessage(this, this.onDeviceMessage);
}
@bind async onPublishEntityState(data: eventdata.PublishEntityState): Promise<void> {
@bind onPublishEntityState(data: eventdata.PublishEntityState): void {
/**
* Prevent that outdated properties are being published.
* In case that e.g. the state is currently held back by a debounce and a new state is published
* remove it from the to be send debounced message.
*/
if (data.entity.isDevice() && this.debouncers[data.entity.ieeeAddr] &&
data.stateChangeReason !== 'publishDebounce' && data.stateChangeReason !== 'lastSeenChanged') {
if (
data.entity.isDevice() &&
this.debouncers[data.entity.ieeeAddr] &&
data.stateChangeReason !== "publishDebounce" &&
data.stateChangeReason !== "lastSeenChanged"
) {
for (const key of Object.keys(data.payload)) {
delete this.debouncers[data.entity.ieeeAddr].payload[key];
}
}
}
publishDebounce(device: Device, payload: KeyValue, time: number, debounceIgnore: string[]): void {
publishDebounce(device: Device, payload: KeyValue, time: number, debounceIgnore: string[] | undefined): void {
if (!this.debouncers[device.ieeeAddr]) {
this.debouncers[device.ieeeAddr] = {
payload: {},
publish: debounce(() => {
this.publishEntityState(device, this.debouncers[device.ieeeAddr].payload, 'publishDebounce');
publish: debounce(async () => {
await this.publishEntityState(device, this.debouncers[device.ieeeAddr].payload, "publishDebounce");
this.debouncers[device.ieeeAddr].payload = {};
}, time * 1000),
};
@@ -59,48 +72,43 @@ export default class Receive extends Extension {
this.debouncers[device.ieeeAddr].publish();
}
async publishThrottle(device: Device, payload: KeyValue, time: number): Promise<void> {
if (!this.throttlers[device.ieeeAddr]) {
this.throttlers[device.ieeeAddr] = {
publish: throttle(this.publishEntityState, time * 1000),
};
}
// Update state cache right away. This makes sure that during throttling cached state is always up to date.
// By updating cache we make sure that state cache is always up-to-date.
this.state.set(device, payload);
await this.throttlers[device.ieeeAddr].publish(device, payload, "publishThrottle");
}
// if debounce_ignore are specified (Array of strings)
// then all newPayload values with key present in debounce_ignore
// should equal or be undefined in oldPayload
// otherwise payload is conflicted
isPayloadConflicted(newPayload: KeyValue, oldPayload: KeyValue, debounceIgnore: string[] | null): boolean {
isPayloadConflicted(newPayload: KeyValue, oldPayload: KeyValue, debounceIgnore: string[] | undefined): boolean {
let result = false;
Object.keys(oldPayload)
.filter((key) => (debounceIgnore || []).includes(key))
.forEach((key) => {
if (typeof newPayload[key] !== 'undefined' && newPayload[key] !== oldPayload[key]) {
result = true;
}
});
for (const key in oldPayload) {
if (debounceIgnore?.includes(key) && typeof newPayload[key] !== "undefined" && newPayload[key] !== oldPayload[key]) {
result = true;
}
}
return result;
}
shouldProcess(data: eventdata.DeviceMessage): boolean {
if (!data.device.definition) {
if (data.device.zh.interviewing) {
logger.debug(`Skipping message, definition is undefined and still interviewing`);
} else {
logger.warn(
`Received message from unsupported device with Zigbee model '${data.device.zh.modelID}' ` +
`and manufacturer name '${data.device.zh.manufacturerName}'`);
// eslint-disable-next-line max-len
logger.warn(`Please see: https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html`);
}
return false;
}
return true;
}
@bind async onDeviceMessage(data: eventdata.DeviceMessage): Promise<void> {
/* istanbul ignore next */
/* v8 ignore next */
if (!data.device) return;
if (!this.shouldProcess(data)) {
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
settings.get(), true, this.publishEntityState);
if (!data.device.definition || !data.device.interviewed) {
logger.debug("Skipping message, still interviewing");
await utils.publishLastSeen({device: data.device, reason: "messageEmitted"}, settings.get(), true, this.publishEntityState);
return;
}
@@ -110,12 +118,13 @@ export default class Receive extends Extension {
});
// Check if there is an available converter, genOta messages are not interesting.
const ignoreClusters: (string | number)[] = ['genOta', 'genTime', 'genBasic', 'genPollCtrl'];
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);
const ignoreClusters: (string | number)[] = ["genOta", "genTime", "genBasic", "genPollCtrl"];
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)}'`,
);
await utils.publishLastSeen({device: data.device, reason: "messageEmitted"}, settings.get(), true, this.publishEntityState);
return;
}
@@ -124,7 +133,11 @@ export default class Receive extends Extension {
// - If a payload is returned publish it to the MQTT broker
// - If NO payload is returned do nothing. This is for non-standard behaviour
// for e.g. click switches where we need to count number of clicks and detect long presses.
const publish = (payload: KeyValue): void => {
const publish = async (payload: KeyValue): Promise<void> => {
assert(data.device.definition);
const options: KeyValue = data.device.options;
zhc.postProcessConvertedFromZigbeeMessage(data.device.definition, payload, options, data.device.zh);
if (settings.get().advanced.elapsed) {
const now = Date.now();
if (this.elapsed[data.device.ieeeAddr]) {
@@ -134,35 +147,44 @@ export default class Receive extends Extension {
this.elapsed[data.device.ieeeAddr] = now;
}
// Check if we have to debounce
// Check if we have to debounce or throttle
if (data.device.options.debounce) {
this.publishDebounce(data.device, payload, data.device.options.debounce,
data.device.options.debounce_ignore);
this.publishDebounce(data.device, payload, data.device.options.debounce, data.device.options.debounce_ignore);
} else if (data.device.options.throttle) {
await this.publishThrottle(data.device, payload, data.device.options.throttle);
} else {
this.publishEntityState(data.device, payload);
await this.publishEntityState(data.device, payload);
}
};
const meta = {device: data.device.zh, logger, state: this.state.get(data.device)};
const meta = {
device: data.device.zh,
logger,
state: this.state.get(data.device),
deviceExposesChanged: (): void => this.eventBus.emitExposesAndDevicesChanged(data.device),
};
let payload: KeyValue = {};
for (const converter of converters) {
try {
const converted = await converter.convert(
data.device.definition, data, publish, data.device.options, meta);
const convertData = {...data, device: data.device.zh};
const options: KeyValue = data.device.options;
const converted = await converter.convert(data.device.definition, convertData, publish, options, meta);
if (converted) {
payload = {...payload, ...converted};
}
} catch (error) /* istanbul ignore next */ {
logger.error(`Exception while calling fromZigbee converter: ${error.message}}`);
logger.debug(error.stack);
/* v8 ignore start */
} catch (error) {
logger.error(`Exception while calling fromZigbee converter: ${(error as Error).message}}`);
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((error as Error).stack!);
}
/* v8 ignore stop */
}
if (Object.keys(payload).length) {
publish(payload);
if (!utils.objectIsEmpty(payload)) {
await publish(payload);
} else {
utils.publishLastSeen({device: data.device, reason: 'messageEmitted'},
settings.get(), true, this.publishEntityState);
await utils.publishLastSeen({device: data.device, reason: "messageEmitted"}, settings.get(), true, this.publishEntityState);
}
}
}
+125 -51
View File
@@ -1,79 +1,153 @@
/* eslint-disable brace-style */
import * as settings from '../util/settings';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import assert from "node:assert";
import {InterviewState} from "zigbee-herdsman/dist/controller/model/device";
import type {OtaExtraMetas} from "zigbee-herdsman/dist/controller/tstype";
import type {CustomClusters} from "zigbee-herdsman/dist/zspec/zcl/definition/tstype";
import * as zhc from "zigbee-herdsman-converters";
import {access, Numeric} from "zigbee-herdsman-converters";
import logger from "../util/logger";
import * as settings from "../util/settings";
const LINKQUALITY = new Numeric("linkquality", access.STATE)
.withUnit("lqi")
.withDescription("Link quality (signal strength)")
.withValueMin(0)
.withValueMax(255)
.withCategory("diagnostic");
export default class Device {
public zh: zh.Device;
private _definition: zhc.Definition;
private _definitionModelID: string;
public definition?: zhc.Definition;
private _definitionModelID?: string;
get ieeeAddr(): string {return this.zh.ieeeAddr;}
get ID(): string {return this.zh.ieeeAddr;}
get options(): DeviceOptions {return {...settings.get().device_options, ...settings.getDevice(this.ieeeAddr)};}
get name(): string {
return this.zh.type === 'Coordinator' ? 'Coordinator' : this.options?.friendly_name || this.ieeeAddr;
get ieeeAddr(): string {
return this.zh.ieeeAddr;
}
get definition(): zhc.Definition {
// Some devices can change modelID, reconsider the definition in that case.
// https://github.com/Koenkk/zigbee-herdsman-converters/issues/3016
if (!this.zh.interviewing && (!this._definition || this._definitionModelID !== this.zh.modelID)) {
this._definition = zigbeeHerdsmanConverters.findByDevice(this.zh);
this._definitionModelID = this.zh.modelID;
}
return this._definition;
// biome-ignore lint/style/useNamingConvention: API
get ID(): string {
return this.zh.ieeeAddr;
}
get options(): DeviceOptionsWithId {
const deviceOptions = settings.getDevice(this.ieeeAddr) ?? {friendly_name: this.ieeeAddr, ID: this.ieeeAddr};
return {...settings.get().device_options, ...deviceOptions};
}
get name(): string {
return this.zh.type === "Coordinator" ? "Coordinator" : this.options?.friendly_name;
}
get isSupported(): boolean {
return this.zh.type === "Coordinator" || Boolean(this.definition && !this.definition.generated);
}
get customClusters(): CustomClusters {
return this.zh.customClusters;
}
get otaExtraMetas(): OtaExtraMetas {
return typeof this.definition?.ota === "object" ? this.definition.ota : {};
}
get interviewed(): boolean {
return this.zh.interviewState === InterviewState.Successful || this.zh.interviewState === InterviewState.Failed;
}
constructor(device: zh.Device) {
this.zh = device;
}
exposes(): zhc.DefinitionExpose[] {
/* istanbul ignore if */
if (typeof this.definition.exposes == 'function') {
return this.definition.exposes(this.zh, this.options);
exposes(): zhc.Expose[] {
const exposes: zhc.Expose[] = [];
assert(this.definition, "Cannot retreive exposes before definition is resolved");
if (typeof this.definition.exposes === "function") {
const options: KeyValue = this.options;
exposes.push(...this.definition.exposes(this.zh, options));
} else {
return this.definition.exposes;
exposes.push(...this.definition.exposes);
}
exposes.push(LINKQUALITY);
return exposes;
}
async resolveDefinition(ignoreCache = false): Promise<void> {
if (this.interviewed && (!this.definition || this._definitionModelID !== this.zh.modelID || ignoreCache)) {
this.definition = await zhc.findByDevice(this.zh, true);
this._definitionModelID = this.zh.modelID;
}
}
async reInterview(eventBus: EventBus): Promise<void> {
// logic follows that of receiving `deviceInterview` event from ZH layer
logger.info(`Interviewing '${this.name}'`);
eventBus.emitDeviceInterview({status: "started", device: this});
try {
await this.zh.interview(true);
// A re-interview can for example result in a different modelId, therefore reconsider the definition.
await this.resolveDefinition(true);
logger.info(`Successfully interviewed '${this.name}'`);
eventBus.emitDeviceInterview({status: "successful", device: this});
} catch (error) {
eventBus.emitDeviceInterview({status: "failed", device: this});
throw new Error(`Interview of '${this.name}' (${this.ieeeAddr}) failed: ${error}`);
}
}
ensureInSettings(): void {
if (this.zh.type !== 'Coordinator' && !settings.getDevice(this.zh.ieeeAddr)) {
if (this.zh.type !== "Coordinator" && !settings.getDevice(this.zh.ieeeAddr)) {
settings.addDevice(this.zh.ieeeAddr);
}
}
endpoint(key?: string | number): zh.Endpoint {
let endpoint: zh.Endpoint;
if (key == null || key == '') key = 'default';
if (!isNaN(Number(key))) {
endpoint = this.zh.getEndpoint(Number(key));
} else if (this.definition?.endpoint) {
const ID = this.definition?.endpoint?.(this.zh)[key];
if (ID) endpoint = this.zh.getEndpoint(ID);
else if (key === 'default') endpoint = this.zh.endpoints[0];
else return null;
} else {
/* istanbul ignore next */
if (key !== 'default') return null;
endpoint = this.zh.endpoints[0];
endpoint(key?: string | number): zh.Endpoint | undefined {
if (!key) {
key = "default";
} else if (!Number.isNaN(Number(key))) {
return this.zh.getEndpoint(Number(key));
}
return endpoint;
}
endpointName(endpoint: zh.Endpoint): string {
let name = null;
if (this.definition?.endpoint) {
name = Object.entries(this.definition?.endpoint(this.zh)).find((e) => e[1] == endpoint.ID)[0];
const ID = this.definition.endpoint(this.zh)[key];
if (ID) {
return this.zh.getEndpoint(ID);
}
}
/* istanbul ignore next */
return name === 'default' ? null : name;
return key === "default" ? this.zh.endpoints[0] : undefined;
}
isIkeaTradfri(): boolean {return this.zh.manufacturerID === 4476;}
endpointName(endpoint: zh.Endpoint): string | undefined {
let epName: string | undefined;
isDevice(): this is Device {return true;}
/* istanbul ignore next */
isGroup(): this is Group {return false;}
if (this.definition?.endpoint) {
const mapping = this.definition.endpoint(this.zh);
for (const name in mapping) {
if (mapping[name] === endpoint.ID) {
epName = name;
break;
}
}
}
/* v8 ignore next */
return epName === "default" ? undefined : epName;
}
getEndpointNames(): string[] {
const names: string[] = [];
if (this.definition?.endpoint) {
for (const name in this.definition.endpoint(this.zh)) {
if (name !== "default") {
names.push(name);
}
}
}
return names;
}
isDevice(): this is Device {
return true;
}
isGroup(): this is Group {
return false;
}
}
+45 -14
View File
@@ -1,33 +1,64 @@
/* eslint-disable brace-style */
import * as settings from '../util/settings';
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
import type * as zhc from "zigbee-herdsman-converters";
import * as settings from "../util/settings";
import {DEFAULT_BIND_GROUP_ID} from "../util/utils";
export default class Group {
public zh: zh.Group;
private resolveDevice: (ieeeAddr: string) => Device;
private resolveDevice: (ieeeAddr: string) => Device | undefined;
get ID(): number {return this.zh.groupID;}
get options(): GroupOptions {return {...settings.getGroup(this.ID)};}
get name(): string {return this.options?.friendly_name || this.ID.toString();}
// biome-ignore lint/style/useNamingConvention: API
get ID(): number {
return this.zh.groupID;
}
get options(): GroupOptions {
// biome-ignore lint/style/noNonNullAssertion: Group always exists in settings
return {...settings.getGroup(this.ID)!};
}
get name(): string {
return this.options?.friendly_name || this.ID.toString();
}
constructor(group: zh.Group, resolveDevice: (ieeeAddr: string) => Device) {
constructor(group: zh.Group, resolveDevice: (ieeeAddr: string) => Device | undefined) {
this.zh = group;
this.resolveDevice = resolveDevice;
}
ensureInSettings(): void {
if (this.ID !== DEFAULT_BIND_GROUP_ID && !settings.getGroup(this.ID)) {
settings.addGroup(this.name, this.ID.toString());
}
}
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);
*membersDevices(): Generator<Device> {
for (const member of this.zh.members) {
const resolvedDevice = this.resolveDevice(member.deviceIeeeAddress);
if (resolvedDevice) {
yield resolvedDevice;
}
}
}
membersDefinitions(): zhc.Definition[] {
return this.zh.members.map((m) =>
zigbeeHerdsmanConverters.findByDevice(m.getDevice())).filter((d) => d) as zhc.Definition[];
const definitions: zhc.Definition[] = [];
for (const member of this.membersDevices()) {
if (member.definition) {
definitions.push(member.definition);
}
}
return definitions;
}
isDevice(): this is Device {return false;}
isGroup(): this is Group {return true;}
isDevice(): this is Device {
return false;
}
isGroup(): this is Group {
return true;
}
}
+154 -98
View File
@@ -1,35 +1,70 @@
import mqtt from 'mqtt';
import logger from './util/logger';
import * as settings from './util/settings';
import utils from './util/utils';
import fs from 'fs';
import bind from 'bind-decorator';
import fs from "node:fs";
import bind from "bind-decorator";
import type {IClientOptions, IClientPublishOptions, MqttClient} from "mqtt";
import {connectAsync} from "mqtt";
import type {Zigbee2MQTTAPI} from "./types/api";
export default class MQTT {
private publishedTopics: Set<string> = new Set();
private connectionTimer: NodeJS.Timeout;
private client: mqtt.MqttClient;
import logger from "./util/logger";
import * as settings from "./util/settings";
import utils from "./util/utils";
const NS = "z2m:mqtt";
export interface MqttPublishOptions {
clientOptions: IClientPublishOptions;
baseTopic: string;
skipLog: boolean;
skipReceive: boolean;
meta: {isEntityState?: boolean};
}
export default class Mqtt {
private publishedTopics = new Set<string>();
private connectionTimer?: NodeJS.Timeout;
private client!: MqttClient;
private eventBus: EventBus;
private initialConnect = true;
private republishRetainedTimer: NodeJS.Timer;
private retainedMessages: {[s: string]: {payload: string, options: MQTTOptions,
skipLog: boolean, skipReceive: boolean, topic: string, base: string}} = {};
private republishRetainedTimer?: NodeJS.Timeout;
private defaultPublishOptions: MqttPublishOptions;
public retainedMessages: {[s: string]: {topic: string; payload: string; options: MqttPublishOptions}} = {};
get info() {
return {
version: this.client.options.protocolVersion,
server: `${this.client.options.protocol}://${this.client.options.host}:${this.client.options.port}`,
};
}
get stats() {
return {
connected: this.isConnected(),
queued: this.client.queue.length,
};
}
constructor(eventBus: EventBus) {
this.eventBus = eventBus;
this.defaultPublishOptions = {
clientOptions: {},
baseTopic: settings.get().mqtt.base_topic,
skipLog: false,
skipReceive: true,
meta: {},
};
}
async connect(): Promise<void> {
const mqttSettings = settings.get().mqtt;
logger.info(`Connecting to MQTT server at ${mqttSettings.server}`);
const options: mqtt.IClientOptions = {
const options: IClientOptions = {
will: {
topic: `${settings.get().mqtt.base_topic}/bridge/state`,
payload: utils.availabilityPayload('offline', settings.get()),
retain: settings.get().mqtt.force_disable_retain ? false : true,
payload: Buffer.from(JSON.stringify({state: "offline"})),
retain: !settings.get().mqtt.force_disable_retain,
qos: 1,
},
properties: {maximumPacketSize: mqttSettings.maximum_packet_size},
};
if (mqttSettings.version) {
@@ -57,8 +92,11 @@ export default class MQTT {
logger.debug(`Using MQTT login with username: ${mqttSettings.user}`);
options.username = mqttSettings.user;
options.password = mqttSettings.password;
} else if (mqttSettings.user) {
logger.debug(`Using MQTT login with username only: ${mqttSettings.user}`);
options.username = mqttSettings.user;
} else {
logger.debug(`Using MQTT anonymous login`);
logger.debug("Using MQTT anonymous login");
}
if (mqttSettings.client_id) {
@@ -66,130 +104,148 @@ export default class MQTT {
options.clientId = mqttSettings.client_id;
}
if (mqttSettings.hasOwnProperty('reject_unauthorized') && !mqttSettings.reject_unauthorized) {
logger.debug(`MQTT reject_unauthorized set false, ignoring certificate warnings.`);
if (mqttSettings.reject_unauthorized !== undefined && !mqttSettings.reject_unauthorized) {
logger.debug("MQTT reject_unauthorized set false, ignoring certificate warnings.");
options.rejectUnauthorized = false;
}
return new Promise((resolve, reject) => {
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());
this.client = await connectAsync(mqttSettings.server, options);
const onConnect = this.onConnect;
this.client.on('connect', async () => {
await onConnect();
resolve();
});
// https://github.com/Koenkk/zigbee2mqtt/issues/9822
this.client.stream.setMaxListeners(0);
this.client.on('error', (err) => {
logger.error(`MQTT error: ${err.message}`);
reject(err);
});
this.client.on('message', this.onMessage);
this.client.on("error", (err) => {
logger.error(`MQTT error: ${err.message}`);
});
}
@bind private async onConnect(): Promise<void> {
// Set timer at interval to check if connected to MQTT server.
clearTimeout(this.connectionTimer);
this.connectionTimer = setInterval(() => {
if (this.client.reconnecting) {
logger.error('Not connected to MQTT server!');
}
}, utils.seconds(10));
logger.info('Connected to MQTT server');
await this.publishStateOnline();
if (!this.initialConnect) {
this.republishRetainedTimer = setTimeout(() => {
// Republish retained messages in case MQTT broker does not persist them.
// https://github.com/Koenkk/zigbee2mqtt/issues/9629
Object.values(this.retainedMessages).forEach((e) =>
this.publish(e.topic, e.payload, e.options, e.base, e.skipLog, e.skipReceive));
}, 2000);
if (mqttSettings.version != null && mqttSettings.version >= 5) {
this.client.on("disconnect", (packet) => {
logger.error(`MQTT disconnect: reason ${packet.reasonCode} (${packet.properties?.reasonString})`);
});
}
this.initialConnect = false;
this.subscribe(`${settings.get().mqtt.base_topic}/#`);
}
this.client.on("message", this.onMessage);
async publishStateOnline(): Promise<void> {
await this.publish('bridge/state', utils.availabilityPayload('online', settings.get()), {retain: true, qos: 0});
await this.onConnect();
this.client.on("connect", this.onConnect);
this.republishRetainedTimer = setTimeout(async () => {
// Republish retained messages in case MQTT broker does not persist them.
// https://github.com/Koenkk/zigbee2mqtt/issues/9629
for (const msg of Object.values(this.retainedMessages)) {
await this.publish(msg.topic, msg.payload, msg.options);
}
}, 2000);
// Set timer at interval to check if connected to MQTT server.
this.connectionTimer = setInterval(() => {
if (!this.isConnected()) {
logger.error("Not connected to MQTT server!");
}
}, utils.seconds(10));
}
async disconnect(): Promise<void> {
clearTimeout(this.connectionTimer);
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();
}
clearTimeout(this.republishRetainedTimer);
subscribe(topic: string): void {
this.client.subscribe(topic);
}
const stateData: Zigbee2MQTTAPI["bridge/state"] = {state: "offline"};
@bind public onMessage(topic: string, message: string): void {
// Since we subscribe to zigbee2mqtt/# we also receive the message we send ourselves, skip these.
if (!this.publishedTopics.has(topic)) {
logger.debug(`Received MQTT message on '${topic}' with data '${message}'`);
this.eventBus.emitMQTTMessage({topic, message: message + ''});
// prevent undesirable error when receiving SIGTERM/SIGINT during startup
if (this.client) {
await this.publish("bridge/state", JSON.stringify(stateData), {clientOptions: {retain: true}});
}
if (this.republishRetainedTimer && topic == `${settings.get().mqtt.base_topic}/bridge/state`) {
this.eventBus.removeListeners(this);
logger.info("Disconnecting from MQTT server");
await this.client?.endAsync();
}
async subscribe(topic: string): Promise<void> {
await this.client.subscribeAsync(topic);
}
async unsubscribe(topic: string): Promise<void> {
await this.client.unsubscribeAsync(topic);
}
@bind private async onConnect(): Promise<void> {
logger.info("Connected to MQTT server");
const stateData: Zigbee2MQTTAPI["bridge/state"] = {state: "online"};
await this.publish("bridge/state", JSON.stringify(stateData), {clientOptions: {retain: true, qos: 1}});
await this.subscribe(`${settings.get().mqtt.base_topic}/#`);
}
@bind public onMessage(topic: string, message: Buffer): void {
// Since we subscribe to zigbee2mqtt/# we also receive the message we send ourselves, skip these.
if (!this.publishedTopics.has(topic)) {
logger.debug(() => `Received MQTT message on '${topic}' with data '${message.toString()}'`, NS);
this.eventBus.emitMQTTMessage({topic, message: message.toString()});
}
if (this.republishRetainedTimer && topic === `${settings.get().mqtt.base_topic}/bridge/info`) {
clearTimeout(this.republishRetainedTimer);
this.republishRetainedTimer = null;
this.republishRetainedTimer = undefined;
}
}
isConnected(): boolean {
return this.client && !this.client.reconnecting;
return this.client && !this.client.reconnecting && !this.client.disconnecting && !this.client.disconnected;
}
async publish(topic: string, payload: string, options: MQTTOptions={},
base=settings.get().mqtt.base_topic, skipLog=false, skipReceive=true,
): Promise<void> {
const defaultOptions: {qos: mqtt.QoS, retain: boolean} = {qos: 0, retain: false};
topic = `${base}/${topic}`;
async publish(topic: string, payload: string, options: Partial<MqttPublishOptions> = {}): Promise<void> {
// TODO: add `options.validateTopic: boolean` to bypass these checks when topic is "controlled"
if (topic.includes("+") || topic.includes("#")) {
// https://github.com/Koenkk/zigbee2mqtt/issues/26939#issuecomment-2772309646
logger.error(`Topic '${topic}' includes wildcard characters, skipping publish.`);
return;
}
if (skipReceive) {
const finalOptions = {...this.defaultPublishOptions, ...options};
topic = `${finalOptions.baseTopic}/${topic}`;
if (finalOptions.skipReceive) {
this.publishedTopics.add(topic);
}
if (options.retain) {
if (finalOptions.clientOptions.retain) {
if (payload) {
this.retainedMessages[topic] =
{payload, options, skipReceive, skipLog, topic: topic.substring(base.length + 1), base};
this.retainedMessages[topic] = {payload, options: finalOptions, topic: topic.substring(finalOptions.baseTopic.length + 1)};
} else {
delete this.retainedMessages[topic];
}
}
this.eventBus.emitMQTTMessagePublished({topic, payload, options: {...defaultOptions, ...options}});
this.eventBus.emitMQTTMessagePublished({topic, payload, options: finalOptions});
if (!this.isConnected()) {
if (!skipLog) {
logger.error(`Not connected to MQTT server!`);
if (!finalOptions.skipLog) {
logger.error("Not connected to MQTT server!");
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
}
return;
}
if (!skipLog) {
logger.info(`MQTT publish: topic '${topic}', payload '${payload}'`);
}
const actualOptions: mqtt.IClientPublishOptions = {...defaultOptions, ...options};
let clientOptions: IClientPublishOptions = finalOptions.clientOptions;
if (settings.get().mqtt.force_disable_retain) {
actualOptions.retain = false;
clientOptions = {...finalOptions.clientOptions, retain: false};
}
return new Promise((resolve) => {
this.client.publish(topic, payload, actualOptions, () => resolve());
});
if (!finalOptions.skipLog) {
logger.info(() => `MQTT publish: topic '${topic}', payload '${payload}'`, NS);
}
try {
await this.client.publishAsync(topic, payload, clientOptions);
} catch (error) {
if (!finalOptions.skipLog) {
logger.error(`MQTT server error: ${(error as Error).message}`);
logger.error(`Could not send message: topic: '${topic}', payload: '${payload}`);
}
}
}
}
+76 -37
View File
@@ -1,24 +1,44 @@
import logger from './util/logger';
import data from './util/data';
import * as settings from './util/settings';
import utils from './util/utils';
import fs from 'fs';
import objectAssignDeep from 'object-assign-deep';
import {existsSync, readFileSync, writeFileSync} from "node:fs";
const saveInterval = 1000 * 60 * 5; // 5 minutes
import objectAssignDeep from "object-assign-deep";
const dontCacheProperties = [
'action', 'action_.*', 'button', 'button_left', 'button_right', 'click', 'forgotten', 'keyerror',
'step_size', 'transition_time', 'group_list', 'group_capacity', 'no_occupancy_since',
'step_mode', 'transition_time', 'duration', 'elapsed', 'from_side', 'to_side',
import data from "./util/data";
import logger from "./util/logger";
import * as settings from "./util/settings";
import utils from "./util/utils";
const SAVE_INTERVAL = 1000 * 60 * 5; // 5 minutes
const CACHE_IGNORE_PROPERTIES = [
"action",
"action_.*",
"button",
"button_left",
"button_right",
"forgotten",
"keyerror",
"step_size",
"transition_time",
"group_list",
"group_capacity",
"no_occupancy_since",
"step_mode",
"transition_time",
"duration",
"elapsed",
"from_side",
"to_side",
"illuminance_lux", // removed in z2m 2.0.0
];
class State {
private state: {[s: string | number]: KeyValue} = {};
private file = data.joinPath('state.json');
private timer: NodeJS.Timer = null;
private readonly state = new Map<string | number, KeyValue>();
private readonly file = data.joinPath("state.json");
private timer?: NodeJS.Timeout;
constructor(private readonly eventBus: EventBus, private readonly zigbee: Zigbee) {
constructor(
private readonly eventBus: EventBus,
private readonly zigbee: Zigbee,
) {
this.eventBus = eventBus;
this.zigbee = zigbee;
}
@@ -27,26 +47,43 @@ class State {
this.load();
// Save the state on every interval
this.timer = setInterval(() => this.save(), saveInterval);
this.timer = setInterval(() => this.save(), SAVE_INTERVAL);
}
stop(): void {
// Remove any invalid states (ie when the device has left the network) when the system is stopped
Object.keys(this.state)
.filter((k) => typeof k === 'string' && !this.zigbee.resolveEntity(k)) // string key = ieeeAddr
.forEach((k) => delete this.state[k]);
// ensure properly started, else this throws undesired errors (e.g. SIGINT during startup)
if (this.zigbee.zhController !== undefined) {
// Remove any invalid states (ie when the device has left the network) when the system is stopped
for (const [key] of this.state) {
if (typeof key === "string" && key.startsWith("0x") && !this.zigbee.resolveEntity(key)) {
// string key = ieeeAddr
this.state.delete(key);
}
}
}
clearTimeout(this.timer);
this.save();
}
clear(): void {
this.state.clear();
}
private load(): void {
if (fs.existsSync(this.file)) {
this.state.clear();
if (existsSync(this.file)) {
try {
this.state = JSON.parse(fs.readFileSync(this.file, 'utf8'));
const stateObj = JSON.parse(readFileSync(this.file, "utf8")) as KeyValue;
for (const key in stateObj) {
this.state.set(key.startsWith("0x") ? key : Number.parseInt(key, 10), stateObj[key]);
}
logger.debug(`Loaded state from file ${this.file}`);
} catch (e) {
logger.debug(`Failed to load state from file ${this.file} (corrupt file?)`);
} catch (error) {
logger.debug(`Failed to load state from file ${this.file} (corrupt file?) (${(error as Error).message})`);
}
} else {
logger.debug(`Can't load state from file ${this.file} (doesn't exist)`);
@@ -56,40 +93,42 @@ class State {
private save(): void {
if (settings.get().advanced.cache_state_persistent) {
logger.debug(`Saving state to file ${this.file}`);
const json = JSON.stringify(this.state, null, 4);
const json = JSON.stringify(Object.fromEntries(this.state), null, 4);
try {
fs.writeFileSync(this.file, json, 'utf8');
} catch (e) {
logger.error(`Failed to write state to '${this.file}' (${e.message})`);
writeFileSync(this.file, json, "utf8");
} catch (error) {
logger.error(`Failed to write state to '${this.file}' (${error})`);
}
} else {
logger.debug(`Not saving state`);
logger.debug("Not saving state");
}
}
exists(entity: Device | Group): boolean {
return this.state.hasOwnProperty(entity.ID);
return this.state.has(entity.ID);
}
get(entity: Group | Device): KeyValue {
return this.state[entity.ID] || {};
return this.state.get(entity.ID) || {};
}
set(entity: Group | Device, update: KeyValue, reason: string=null): KeyValue {
const fromState = this.state[entity.ID] || {};
set(entity: Group | Device, update: KeyValue, reason?: string): KeyValue {
const fromState = this.state.get(entity.ID) || {};
const toState = objectAssignDeep({}, fromState, update);
const newCache = {...toState};
const entityDontCacheProperties = entity.options.filtered_cache || [];
utils.filterProperties(dontCacheProperties.concat(entityDontCacheProperties), newCache);
utils.filterProperties(CACHE_IGNORE_PROPERTIES.concat(entityDontCacheProperties), newCache);
this.state[entity.ID] = newCache;
this.state.set(entity.ID, newCache);
this.eventBus.emitStateChange({entity, from: fromState, to: toState, reason, update});
return toState;
}
remove(ID: string | number): void {
delete this.state[ID];
remove(id: string | number): boolean {
return this.state.delete(id);
}
}
+1100
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
// minimal required because of sub-deps in mqtt >= 5.14.0 to avoid requiring `dom` type
declare global {
// map to node, doesn't really matter, just needs to be there, and the right "type vs value" to avoid lib check problems
/** @deprecated DOM SHIM, DO NOT USE */
type MessagePort = import("node:worker_threads").MessagePort;
/** @deprecated DOM SHIM, DO NOT USE */
type Worker = import("node:worker_threads").Worker;
/** @deprecated DOM SHIM, DO NOT USE */
type Transferable = import("node:worker_threads").Transferable;
/** @deprecated DOM SHIM, DO NOT USE */
const addEventListener: import("node:events").EventEmitter["addListener"];
/** @deprecated DOM SHIM, DO NOT USE */
const removeEventListener: import("node:events").EventEmitter["removeListener"];
/** @deprecated DOM SHIM, DO NOT USE */
const postMessage: import("node:worker_threads").MessagePort["postMessage"];
}
export {};
+1 -1
View File
@@ -1,3 +1,3 @@
declare module 'json-stable-stringify-without-jsonify' {
declare module "json-stable-stringify-without-jsonify" {
export default function (obj: unknown): string;
}
-3
View File
@@ -1,3 +0,0 @@
declare module 'mkdir-recursive' {
export function mkdirSync(path: string): void;
}
+60 -278
View File
@@ -1,38 +1,20 @@
/* eslint-disable camelcase */
import type {
Device as ZHDevice,
Group as ZHGroup,
Endpoint as ZHEndpoint,
} from 'zigbee-herdsman/dist/controller/model';
import type {AdapterTypes as ZHAdapterTypes, Events as ZHEvents, Models as ZHModels} from "zigbee-herdsman";
import type {Cluster as ZHCluster, FrameControl as ZHFrameControl} from "zigbee-herdsman/dist/zspec/zcl/definition/tstype";
import type {
NetworkParameters as ZHNetworkParameters,
CoordinatorVersion as ZHCoordinatorVersion,
LQI as ZHLQI,
RoutingTable as ZHRoutingTable,
RoutingTableEntry as ZHRoutingTableEntry,
} from 'zigbee-herdsman/dist/adapter/tstype';
import type {
Cluster as ZHCluster,
} from 'zigbee-herdsman/dist/zcl/tstype';
import type * as ZHEvents from 'zigbee-herdsman/dist/controller/events';
import type TypeEventBus from 'lib/eventBus';
import type TypeMQTT from 'lib/mqtt';
import type TypeState from 'lib/state';
import type TypeZigbee from 'lib/zigbee';
import type TypeDevice from 'lib/model/device';
import type TypeGroup from 'lib/model/group';
import type TypeExtension from 'lib/extension/extension';
import type mqtt from 'mqtt';
import type TypeEventBus from "../eventBus";
import type TypeExtension from "../extension/extension";
import type TypeDevice from "../model/device";
import type TypeGroup from "../model/group";
import type TypeMqtt from "../mqtt";
import type {MqttPublishOptions} from "../mqtt";
import type TypeState from "../state";
import type TypeZigbee from "../zigbee";
import type {Zigbee2MQTTDeviceOptions, Zigbee2MQTTGroupOptions, Zigbee2MQTTSettings} from "./api";
declare global {
// Define some class types as global
type EventBus = TypeEventBus;
type MQTT = TypeMQTT;
type Mqtt = TypeMqtt;
type Zigbee = TypeZigbee;
type Group = TypeGroup;
type Device = TypeDevice;
@@ -40,276 +22,76 @@ declare global {
type Extension = TypeExtension;
// Types
interface MQTTResponse {data: KeyValue, status: 'error' | 'ok', error?: string, transaction?: string}
interface MQTTOptions {qos?: mqtt.QoS, retain?: boolean, properties?: {messageExpiryInterval: number}}
type StateChangeReason = 'publishDebounce' | 'groupOptimistic' | 'lastSeenChanged' | 'publishCached';
type PublishEntityState = (entity: Device | Group, payload: KeyValue,
stateChangeReason?: StateChangeReason) => Promise<void>;
type RecursivePartial<T> = {[P in keyof T]?: RecursivePartial<T[P]>;};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface KeyValue {[s: string]: any}
type StateChangeReason = "publishDebounce" | "groupOptimistic" | "lastSeenChanged" | "publishCached" | "publishThrottle";
type PublishEntityState = (entity: Device | Group, payload: KeyValue, stateChangeReason?: StateChangeReason) => Promise<void>;
type RecursivePartial<T> = {[P in keyof T]?: RecursivePartial<T[P]>};
type MakePartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>;
interface KeyValue {
// biome-ignore lint/suspicious/noExplicitAny: API
[s: string]: any;
}
// zigbee-herdsman
namespace zh {
type Endpoint = ZHEndpoint;
type Device = ZHDevice;
type Group = ZHGroup;
type LQI = ZHLQI;
type RoutingTable = ZHRoutingTable;
type RoutingTableEntry = ZHRoutingTableEntry;
type CoordinatorVersion = ZHCoordinatorVersion;
type NetworkParameters = ZHNetworkParameters;
type Cluster = ZHCluster;
type Endpoint = ZHModels.Endpoint;
type Device = ZHModels.Device;
type Group = ZHModels.Group;
type CoordinatorVersion = ZHAdapterTypes.CoordinatorVersion;
type NetworkParameters = ZHAdapterTypes.NetworkParameters;
interface Bind {
cluster: zh.Cluster;
cluster: ZHCluster;
target: zh.Endpoint | zh.Group;
}
}
// zigbee-herdsman-converters
namespace zhc {
interface Logger {
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
debug: (message: string) => void;
}
interface ToZigbeeConverterGetMeta {message?: KeyValue, mapped?: Definition | Definition[]}
interface ToZigbeeConverterResult {state: KeyValue,
membersState: {[s: string]: KeyValue}, readAfterWriteTime?: number}
interface ToZigbeeConverter {
key: string[],
convertGet?: (entity: zh.Endpoint | zh.Group, key: string, meta: ToZigbeeConverterGetMeta) => Promise<void>
convertSet?: (entity: zh.Endpoint | zh.Group, key: string, value: KeyValue | string | number,
meta: {state: KeyValue}) => Promise<ToZigbeeConverterResult>
}
interface FromZigbeeConverter {
cluster: string,
type: string[] | string,
convert: (model: Definition, msg: KeyValue, publish: (payload: KeyValue) => void, options: KeyValue,
meta: {state: KeyValue, logger: Logger, device: zh.Device}) => Promise<KeyValue>,
}
interface DefinitionExposeFeature {name: string, endpoint?: string,
property: string, value_max?: number, value_min?: number, unit?: string,
value_off?: string, value_on?: string, value_step?: number, values: string[], access: number}
interface DefinitionExpose {
type: string, name?: string, features?: DefinitionExposeFeature[],
endpoint?: string, values?: string[], value_off?: string, value_on?: string, value_step?: number,
access: number, property: string, unit?: string,
value_min?: number, value_max?: number}
interface OtaUpdateAvailableResult {available: boolean, currentFileVersion: number, otaFileVersion: number}
interface Definition {
model: string,
zigbeeModel: string[],
endpoint?: (device: zh.Device) => {[s: string]: number}
toZigbee: ToZigbeeConverter[]
fromZigbee: FromZigbeeConverter[]
icon?: string
description: string
options: zhc.DefinitionExpose[],
vendor: string
exposes: DefinitionExpose[] | ((device: zh.Device, options: KeyValue) => DefinitionExpose[])
configure?: (device: zh.Device, coordinatorEndpoint: zh.Endpoint, logger: Logger,
options?: DeviceOptions) => Promise<void>;
onEvent?: (type: string, data: KeyValue, device: zh.Device,
settings: KeyValue, state: KeyValue) => Promise<void>;
ota?: {
isUpdateAvailable: (device: zh.Device, logger: Logger, data?: KeyValue)
=> Promise<OtaUpdateAvailableResult>;
updateToLatest: (device: zh.Device, logger: Logger,
onProgress: (progress: number, remaining: number) => void) => Promise<number>;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ExternalDefinition = Definition & {homeassistant: any};
}
namespace eventdata {
type EntityRenamed = { entity: Device | Group, homeAssisantRename: boolean, from: string, to: string };
type DeviceRemoved = { ieeeAddr: string, name: string };
type MQTTMessage = { topic: string, message: string };
type MQTTMessagePublished = { topic: string, payload: string, options: {retain: boolean, qos: number} };
type EntityRenamed = {entity: Device | Group; homeAssisantRename: boolean; from: string; to: string};
type EntityRemoved = {entity: Device | Group; name: string};
type MQTTMessage = {topic: string; message: string};
type MQTTMessagePublished = {topic: string; payload: string; options: MqttPublishOptions};
type StateChange = {
entity: Device | Group, from: KeyValue, to: KeyValue, reason: string | null, update: KeyValue };
entity: Device | Group;
from: KeyValue;
to: KeyValue;
reason?: string;
update: KeyValue;
};
type PermitJoinChanged = ZHEvents.PermitJoinChangedPayload;
type LastSeenChanged = { device: Device,
reason: 'deviceAnnounce' | 'networkAddress' | 'deviceJoined' | 'messageEmitted' | 'messageNonEmitted'; };
type DeviceNetworkAddressChanged = { device: Device };
type DeviceAnnounce = { device: Device };
type DeviceInterview = { device: Device, status: 'started' | 'successful' | 'failed' };
type DeviceJoined = { 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 };
type PublishEntityState = {entity: Group | Device, message: KeyValue, stateChangeReason: StateChangeReason,
payload: KeyValue};
type LastSeenChanged = {
device: Device;
reason: "deviceAnnounce" | "networkAddress" | "deviceJoined" | "messageEmitted" | "messageNonEmitted";
};
type DeviceNetworkAddressChanged = {device: Device};
type DeviceAnnounce = {device: Device};
type DeviceInterview = {device: Device; status: "started" | "successful" | "failed"};
type DeviceJoined = {device: Device};
type EntityOptionsChanged = {entity: Device | Group; from: KeyValue; to: KeyValue};
type ExposesChanged = {device: Device};
type Reconfigure = {device: Device};
type DeviceLeave = {ieeeAddr: string; name: string; device?: Device};
type GroupMembersChanged = {group: Group; action: "remove" | "add" | "remove_all"; endpoint: zh.Endpoint; skipDisableReporting: boolean};
type PublishEntityState = {entity: Group | Device; message: KeyValue; stateChangeReason?: StateChangeReason; payload: KeyValue};
type DeviceMessage = {
type: ZHEvents.MessagePayloadType;
device: Device;
endpoint: zh.Endpoint;
linkquality: number;
groupID: number;
groupID: number; // XXX: should this be `?`
cluster: string | number;
data: KeyValue | Array<string | number>;
meta: {zclTransactionSequenceNumber?: number;};
meta: {zclTransactionSequenceNumber?: number; manufacturerCode?: number; frameControl?: ZHFrameControl; rawData: Buffer};
};
type ScenesChanged = {entity: Device | Group};
}
// Settings
// eslint-disable camelcase
interface Settings {
homeassistant?: {
discovery_topic: string,
status_topic: string,
legacy_entity_attributes: boolean,
legacy_triggers: boolean,
},
permit_join?: boolean,
availability?: {
active: {timeout: number},
passive: {timeout: number}
},
external_converters: string[],
mqtt: {
base_topic: string,
include_device_information: boolean,
force_disable_retain: boolean
version?: number,
user?: string,
password?: string,
server: string,
ca?: string,
keepalive?: number,
key?: string,
cert?: string,
client_id?: string,
reject_unauthorized?: boolean,
},
serial: {
disable_led: boolean,
port?: string,
adapter?: 'deconz' | 'zstack' | 'ezsp' | 'zigate',
baudrate?: number,
rtscts?: boolean,
},
passlist: string[],
blocklist: string[],
map_options: {
graphviz: {
colors: {
fill: {
enddevice: string,
coordinator: string,
router: string,
},
font: {
coordinator: string,
router: string,
enddevice: string,
},
line: {
active: string,
inactive: string,
},
},
},
},
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,
ssl_cert?: string,
ssl_key?: 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')[],
log_directory: string,
log_file: string,
log_level: 'debug' | 'info' | 'error' | 'warn',
log_syslog: KeyValue,
pan_id: number | 'GENERATE',
ext_pan_id: number[],
channel: number,
adapter_concurrent: number | null,
adapter_delay: number | null,
cache_state: boolean,
cache_state_persistent: boolean,
cache_state_send_on_startup: boolean,
last_seen: 'disable' | 'ISO_8601' | 'ISO_8601_local' | 'epoch',
elapsed: boolean,
network_key: number[] | 'GENERATE',
timestamp_format: string,
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,
},
type Settings = Zigbee2MQTTSettings;
type DeviceOptions = Zigbee2MQTTDeviceOptions;
interface DeviceOptionsWithId extends DeviceOptions {
ID: string;
}
interface DeviceOptions {
ID?: string,
disabled?: boolean,
retention?: number,
availability?: boolean | {timeout: number},
optimistic?: boolean,
retrieve_state?: boolean,
debounce?: number,
debounce_ignore?: string[],
filtered_attributes?: string[],
filtered_cache?: string[],
filtered_optimistic?: string[],
icon?: string,
homeassistant?: KeyValue,
legacy?: boolean,
friendly_name: string,
description?: string,
qos?: 0 | 1 | 2,
}
interface GroupOptions {
devices?: string[],
ID?: number,
optimistic?: boolean,
off_state?: 'all_members_off' | 'last_member_state'
filtered_attributes?: string[],
filtered_cache?: string[],
filtered_optimistic?: string[],
retrieve_state?: boolean,
homeassistant?: KeyValue,
friendly_name: string,
description?: string,
qos?: 0 | 1 | 2,
}
type GroupOptions = Zigbee2MQTTGroupOptions;
}
+13
View File
@@ -0,0 +1,13 @@
declare module "unix-dgram" {
import {EventEmitter} from "node:events";
export class UnixDgramSocket extends EventEmitter {
send(buf: Buffer, callback?: (err?: Error) => void): void;
send(buf: Buffer, offset: number, length: number, path: string, callback?: (err?: Error) => void): void;
bind(path: string): void;
connect(remotePath: string): void;
close(): void;
}
export function createSocket(type: "unix_dgram", listener?: (msg: Buffer) => void): UnixDgramSocket;
}
-22
View File
@@ -1,22 +0,0 @@
declare module 'zigbee-herdsman-converters' {
export function findByDevice(device: zh.Device): zhc.Definition;
export function getConfigureKey(definition: zhc.Definition): string | number;
export const toZigbeeConverters: {[s: string]: zhc.ToZigbeeConverter};
export const definitions: zhc.Definition[];
export function addDeviceDefinition(definition: zhc.Definition): Promise<void>;
export function onEvent(type: string, data: KeyValue, device: zh.Device, settings: KeyValue,
state: KeyValue): Promise<void>;
}
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;
}
declare module 'zigbee-herdsman-converters/lib/philips' {
export const tz: {[s: string]: zhc.ToZigbeeConverter};
}
+11 -6
View File
@@ -1,9 +1,14 @@
declare module 'zigbee2mqtt-frontend' {
export function getPath(): string;
declare module "zigbee2mqtt-frontend" {
const frontend: {
getPath: () => string;
};
export default frontend;
}
declare module 'connect-gzip-static' {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type RequestHandler = (req: any, res: any) => void;
export default function gzipStatic(root: string, options?: Record<string, unknown>): RequestHandler;
declare module "http" {
interface IncomingMessage {
originalUrl?: string;
path?: string;
}
}
+8 -15
View File
@@ -1,17 +1,10 @@
import path from 'path';
import path from "node:path";
let dataPath: string = null;
function load(): void {
if (process.env.ZIGBEE2MQTT_DATA) {
dataPath = process.env.ZIGBEE2MQTT_DATA;
} else {
dataPath = path.join(__dirname, '..', '..', 'data');
dataPath = path.normalize(dataPath);
}
function setPath(): string {
return process.env.ZIGBEE2MQTT_DATA ? process.env.ZIGBEE2MQTT_DATA : path.normalize(path.join(__dirname, "..", "..", "data"));
}
load();
let dataPath = setPath();
function joinPath(file: string): string {
return path.resolve(dataPath, file);
@@ -21,9 +14,9 @@ function getPath(): string {
return dataPath;
}
// eslint-disable-next-line camelcase
function testingOnlyReload(): void {
load();
function _testReload(): void {
dataPath = setPath();
}
export default {joinPath, getPath, testingOnlyReload};
// biome-ignore lint/style/useNamingConvention: test
export default {joinPath, getPath, _testReload};
+246 -198
View File
@@ -1,225 +1,273 @@
import winston from 'winston';
import moment from 'moment';
import * as settings from './settings';
import path from 'path';
import fs from 'fs';
import fx from 'mkdir-recursive';
import {rimrafSync} from 'rimraf';
import assert from 'assert';
import assert from "node:assert";
import fs from "node:fs";
import path from "node:path";
const colorizer = winston.format.colorize();
import {rimrafSync} from "rimraf";
import winston from "winston";
import * as settings from "./settings";
type Z2MLogLevel = 'warn' | 'debug' | 'info' | 'error';
type WinstonLogLevel = 'warning' | 'debug' | 'info' | 'error';
const NAMESPACE_SEPARATOR = ":";
const z2mToWinstonLevel = (level: Z2MLogLevel): WinstonLogLevel => level === 'warn' ? 'warning' : level;
const winstonToZ2mLevel = (level: WinstonLogLevel): Z2MLogLevel => level === 'warning' ? 'warn' : level;
class Logger {
private level!: settings.LogLevel;
private output!: string[];
private directory!: string;
private logger!: winston.Logger;
private fileTransport: winston.transports.FileTransportInstance | undefined;
private debugNamespaceIgnoreRegex?: RegExp;
private namespacedLevels!: Record<string, settings.LogLevel>;
private cachedNamespacedLevels!: Record<string, settings.LogLevel>;
const levelWithCompensatedLength: {[s: string]: string} = {
'info': 'info ',
'error': 'error',
'warn': 'warn ',
'debug': 'debug',
};
public init(): void {
// What transports to enable
this.output = settings.get().advanced.log_output;
const date = new Date();
// offset UTC by current timezone, ISO keeps "Z" (UTC) which is then wrong but we strip it
const timestamp = new Date(date.getTime() - date.getTimezoneOffset() * 60000)
.toISOString()
.slice(0, 19)
.replace("T", ".")
.replaceAll(":", "-");
this.directory = settings.get().advanced.log_directory.replace("%TIMESTAMP%", timestamp);
const logFilename = settings.get().advanced.log_file.replace("%TIMESTAMP%", timestamp);
this.level = settings.get().advanced.log_level;
this.namespacedLevels = settings.get().advanced.log_namespaced_levels;
this.cachedNamespacedLevels = Object.assign({}, this.namespacedLevels);
let logger: winston.Logger;
let fileTransport : winston.transport;
let output: string[];
let directory: string;
let logFilename: string;
let transportsToUse: winston.transport[];
assert(settings.LOG_LEVELS.includes(this.level), `'${this.level}' is not valid log_level, use one of '${settings.LOG_LEVELS.join(", ")}'`);
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 directory exists 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'),
this.logger = winston.createLogger({
level: "debug",
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}`;
}),
winston.format.errors({stack: true}),
winston.format.timestamp({format: settings.get().advanced.timestamp_format}),
),
}),
];
// 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')) {
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 {
if (settings.get().advanced.log_directory.includes('%TIMESTAMP%')) {
const rootDirectory = path.join(directory, '..');
let directories = fs.readdirSync(rootDirectory).map((d) => {
d = path.join(rootDirectory, d);
return {path: d, birth: fs.statSync(d).mtime};
levels: winston.config.syslog.levels,
});
directories.sort((a: KeyValue, b: KeyValue) => b.birth - a.birth);
directories = directories.slice(10, directories.length);
directories.forEach((dir) => {
logger.debug(`Removing old log directory '${dir.path}'`);
rimrafSync(dir.path);
});
}
}
const consoleSilenced = !this.output.includes("console");
// Print to user what logging is active
let logging = `Logging to console${consoleSilenced ? " (silenced)" : ""}`;
// Print to user what logging is enabled
function logOutput(): void {
if (output.includes('file')) {
if (output.includes('console')) {
logger.info(`Logging to console and directory: '${directory}' filename: ${logFilename}`);
} else {
logger.info(`Logging to directory: '${directory}' filename: ${logFilename}`);
// Setup default console logger
this.logger.add(
new winston.transports.Console({
silent: consoleSilenced,
format: settings.get().advanced.log_console_json
? winston.format.json()
: winston.format.combine(
// winston.config.syslog.levels sets 'warning' as 'red'
winston.format.colorize({colors: {debug: "blue", info: "green", warning: "yellow", error: "red"}}),
winston.format.printf((info) => {
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
}),
),
}),
);
if (this.output.includes("file")) {
logging += `, file (filename: ${logFilename})`;
// Make sure that log directory exists when not logging to stdout only
fs.mkdirSync(this.directory, {recursive: true});
if (settings.get().advanced.log_symlink_current) {
const current = settings.get().advanced.log_directory.replace("%TIMESTAMP%", "current");
const actual = `./${timestamp}`;
/* v8 ignore start */
if (fs.existsSync(current)) {
fs.unlinkSync(current);
}
/* v8 ignore stop */
fs.symlinkSync(actual, current);
}
// 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: winston.transports.FileTransportOptions = {
filename: path.join(this.directory, logFilename),
format: winston.format.printf((info) => {
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
}),
};
if (settings.get().advanced.log_rotation) {
transportFileOptions.tailable = true;
transportFileOptions.maxFiles = 3; // Keep last 3 files
transportFileOptions.maxsize = 10000000; // 10MB
}
this.fileTransport = new winston.transports.File(transportFileOptions);
this.logger.add(this.fileTransport);
this.cleanup();
}
cleanup();
} else if (output.includes('console')) {
logger.info(`Logging to console only'`);
/* v8 ignore start */
if (this.output.includes("syslog")) {
logging += ", syslog";
require("winston-syslog").Syslog;
const options: KeyValue = {
app_name: "Zigbee2MQTT",
format: winston.format.printf((info) => info.message as string),
...settings.get().advanced.log_syslog,
};
if (options.type !== undefined) {
options.type = options.type.toString();
}
// @ts-expect-error untyped transport
this.logger.add(new winston.transports.Syslog(options));
}
/* v8 ignore stop */
this.setDebugNamespaceIgnore(settings.get().advanced.log_debug_namespace_ignore);
this.info(logging);
}
}
function addTransport(transport: winston.transport): void {
transport.level = transportsToUse[0].level;
logger.add(transport);
}
get winston(): winston.Logger {
return this.logger;
}
function getLevel(): Z2MLogLevel {
return winstonToZ2mLevel(transportsToUse[0].level as WinstonLogLevel);
}
public addTransport(transport: winston.transport): void {
this.logger.add(transport);
}
function setLevel(level: Z2MLogLevel): void {
logger.transports.forEach((transport) => transport.level = z2mToWinstonLevel(level as Z2MLogLevel));
}
public removeTransport(transport: winston.transport): void {
this.logger.remove(transport);
}
function warn(message: string): void {
// winston.config.syslog.levels doesn't have warn, but is required for syslog.
logger.warning(message);
}
public getDebugNamespaceIgnore(): string {
return this.debugNamespaceIgnoreRegex?.toString().slice(1, -1) /* remove slashes */ ?? "";
}
function warning(message: string): void {
logger.warning(message);
}
public setDebugNamespaceIgnore(value: string): void {
this.debugNamespaceIgnoreRegex = value !== "" ? new RegExp(value) : undefined;
}
function info(message: string): void {
logger.info(message);
}
public getLevel(): settings.LogLevel {
return this.level;
}
function debug(message: string): void {
logger.debug(message);
}
public setLevel(level: settings.LogLevel): void {
this.level = level;
this.resetCachedNamespacedLevels();
}
function error(message: string): void {
logger.error(message);
}
public getNamespacedLevels(): Record<string, settings.LogLevel> {
return this.namespacedLevels;
}
// 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();
public setNamespacedLevels(nsLevels: Record<string, settings.LogLevel>): void {
this.namespacedLevels = nsLevels;
this.resetCachedNamespacedLevels();
}
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));
private resetCachedNamespacedLevels(): void {
this.cachedNamespacedLevels = Object.assign({}, this.namespacedLevels);
}
private cacheNamespacedLevel(namespace: string): string {
let cached = namespace;
while (this.cachedNamespacedLevels[namespace] === undefined) {
const sep = cached.lastIndexOf(NAMESPACE_SEPARATOR);
if (sep === -1) {
this.cachedNamespacedLevels[namespace] = this.level;
return this.level;
}
cached = cached.slice(0, sep);
this.cachedNamespacedLevels[namespace] = this.cachedNamespacedLevels[cached];
}
return this.cachedNamespacedLevels[namespace];
}
private log(level: settings.LogLevel, messageOrLambda: string | (() => string), namespace: string): void {
const nsLevel = this.cacheNamespacedLevel(namespace);
if (settings.LOG_LEVELS.indexOf(level) <= settings.LOG_LEVELS.indexOf(nsLevel)) {
const message: string = messageOrLambda instanceof Function ? messageOrLambda() : messageOrLambda;
this.logger.log(level, `${namespace}: ${message}`);
}
}
public error(messageOrLambda: string | (() => string), namespace = "z2m"): void {
this.log("error", messageOrLambda, namespace);
}
public warning(messageOrLambda: string | (() => string), namespace = "z2m"): void {
this.log("warning", messageOrLambda, namespace);
}
public info(messageOrLambda: string | (() => string), namespace = "z2m"): void {
this.log("info", messageOrLambda, namespace);
}
public debug(messageOrLambda: string | (() => string), namespace = "z2m"): void {
if (this.debugNamespaceIgnoreRegex?.test(namespace)) {
return;
}
this.log("debug", messageOrLambda, namespace);
}
// Cleanup any old log directory.
private cleanup(): void {
if (settings.get().advanced.log_directory.includes("%TIMESTAMP%")) {
const rootDirectory = path.join(this.directory, "..");
let directories = fs.readdirSync(rootDirectory).map((d) => {
d = path.join(rootDirectory, d);
return {path: d, birth: fs.statSync(d).mtime};
});
directories.sort((a: KeyValue, b: KeyValue) => b.birth - a.birth);
directories = directories.slice(settings.get().advanced.log_directories_to_keep, directories.length);
for (const dir of directories) {
this.debug(`Removing old log directory '${dir.path}'`);
try {
rimrafSync(dir.path);
} catch (e) {
this.error(`Failed to remove old log directory '${dir.path}': ${e}`);
}
}
}
});
}
// Workaround for https://github.com/winstonjs/winston/issues/1629.
// https://github.com/Koenkk/zigbee2mqtt/pull/10905
/* v8 ignore start */
public async end(): Promise<void> {
// Only flush the file transport, don't end logger itself as log() might still be called
// causing a UnhandledPromiseRejection (`Error: write after end`). Flushing the file transport
// ensures the log files are written before stopping.
if (this.fileTransport) {
await new Promise<void>((resolve) => {
// @ts-expect-error workaround
if (this.fileTransport._dest) {
// @ts-expect-error workaround
this.fileTransport._dest.on("finish", resolve);
} else {
// @ts-expect-error workaround
this.fileTransport.on("open", () => this.fileTransport._dest.on("finish", resolve));
}
if (this.fileTransport) {
this.fileTransport.end();
}
});
}
}
/* v8 ignore stop */
}
export default {
init, logOutput, warn, warning, error, info, debug, setLevel, getLevel, cleanup, addTransport, end,
winston: (): winston.Logger => logger,
};
export default new Logger();
+339
View File
@@ -0,0 +1,339 @@
import {existsSync, mkdirSync, writeFileSync} from "node:fs";
import type {ServerResponse} from "node:http";
import {createServer} from "node:http";
import path from "node:path";
import expressStaticGzip from "express-static-gzip";
import finalhandler from "finalhandler";
import stringify from "json-stable-stringify-without-jsonify";
import JSZip from "jszip";
import {findAllDevices} from "zigbee-herdsman/dist/adapter/adapterDiscovery";
import type {OnboardData, OnboardFailureData, OnboardSubmitResponse, Zigbee2MQTTSettings} from "../types/api";
import data from "./data";
import * as settings from "./settings";
import {YAMLFileException} from "./yaml";
/** same as extension/frontend */
const FILE_SERVER_OPTIONS: expressStaticGzip.ExpressStaticGzipOptions = {
enableBrotli: true,
serveStatic: {
/* v8 ignore start */
setHeaders: (res: ServerResponse, path: string): void => {
if (path.endsWith("index.html")) {
res.setHeader("Cache-Control", "no-store");
}
},
/* v8 ignore stop */
},
};
function getServerUrl(): URL {
return new URL(process.env.Z2M_ONBOARD_URL ?? "http://0.0.0.0:8080");
}
function getZipEntryTargetPath(entryName: string): string {
const normalizedEntry = entryName.replace(/\\/g, "/");
if (!normalizedEntry || normalizedEntry.startsWith("/") || normalizedEntry.includes("\0")) {
throw new Error(`Invalid ZIP entry path '${entryName}'`);
}
const basePath = path.resolve(data.getPath());
const targetPath = path.resolve(basePath, normalizedEntry);
const relativePath = path.relative(basePath, targetPath);
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
throw new Error(`Unsafe ZIP entry path '${entryName}'`);
}
return targetPath;
}
async function extractZipDataToDataPath(zipContent: Buffer): Promise<void> {
const zip = await JSZip.loadAsync(zipContent);
for (const key in zip.files) {
const entry = zip.files[key];
const targetPath = getZipEntryTargetPath(entry.name);
if (entry.dir) {
mkdirSync(targetPath, {recursive: true});
continue;
}
mkdirSync(path.dirname(targetPath), {recursive: true});
writeFileSync(targetPath, await entry.async("nodebuffer"));
}
}
async function startOnboardingServer(): Promise<boolean> {
const currentSettings = settings.get();
const serverUrl = getServerUrl();
let server: ReturnType<typeof createServer> | undefined;
const fileServer = expressStaticGzip((await import("zigbee2mqtt-windfront")).default.getOnboardingPath(), FILE_SERVER_OPTIONS);
const success = await new Promise<boolean>((resolve) => {
server = createServer(async (req, res) => {
const pathname = new URL(req.url /* v8 ignore next */ ?? "/", serverUrl).pathname;
if (req.method === "GET" && pathname === "/data") {
const payload: OnboardData = {
page: "form",
settings: currentSettings,
settingsSchema: settings.schemaJson,
devices: await findAllDevices(),
};
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(stringify(payload));
return;
}
if (req.method === "POST") {
if (pathname === "/submit") {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
try {
const result = (body ? JSON.parse(body) : {}) as RecursivePartial<Zigbee2MQTTSettings>;
settings.apply(result);
const appliedSettings = settings.get();
const redirect =
!process.env.Z2M_ONBOARD_NO_REDIRECT &&
appliedSettings.frontend.enabled &&
!appliedSettings.frontend.host?.startsWith("/");
const protocol = appliedSettings.frontend.ssl_cert && appliedSettings.frontend.ssl_key ? "https" : "http";
const frontendUrl = redirect
? `${protocol}://${appliedSettings.frontend.host ?? "localhost"}:${appliedSettings.frontend.port}${appliedSettings.frontend.base_url}`
: null;
const payload: OnboardSubmitResponse = {success: true, frontendUrl};
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(stringify(payload), () => {
resolve(true);
});
} catch (error) {
console.error(`Failed to apply configuration: ${(error as Error).message}`);
const payload: OnboardSubmitResponse = {success: false, error: (error as Error).message};
res.setHeader("Content-Type", "application/json");
res.writeHead(406);
res.end(stringify(payload));
}
});
req.on("error", (error: Error) => {
console.error(`Failed to parse request body: ${error.message}`);
const payload: OnboardSubmitResponse = {success: false, error: error.message};
res.setHeader("Content-Type", "application/json");
res.writeHead(406);
res.end(stringify(payload));
});
return;
}
if (pathname === "/submit-zip") {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", async () => {
try {
if (!body) {
throw new Error("Invalid ZIP payload: missing content");
}
const zipContent = Buffer.from(body, "base64");
await extractZipDataToDataPath(zipContent);
const payload: OnboardSubmitResponse = {success: true, frontendUrl: null};
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(stringify(payload), () => {
resolve(true);
});
} catch (error) {
console.error(`Failed to apply ZIP data: ${(error as Error).message}`);
const payload: OnboardSubmitResponse = {success: false, error: (error as Error).message};
res.setHeader("Content-Type", "application/json");
res.writeHead(406);
res.end(stringify(payload));
}
});
req.on("error", (error: Error) => {
console.error(`Failed to parse ZIP request body: ${error.message}`);
const payload: OnboardSubmitResponse = {success: false, error: error.message};
res.setHeader("Content-Type", "application/json");
res.writeHead(406);
res.end(stringify(payload));
});
return;
}
}
const next = finalhandler(req, res);
fileServer(req, res, next);
});
server.on("error", (error: Error) => {
console.error("Failed to start onboarding server", error);
resolve(false);
});
server.listen(Number.parseInt(serverUrl.port, 10), serverUrl.hostname, () => {
console.log(`Onboarding page is available at ${serverUrl.href}`);
});
});
await new Promise((resolve) => server?.close(resolve));
return success;
}
async function startFailureServer(errors: string[]): Promise<void> {
const serverUrl = getServerUrl();
let server: ReturnType<typeof createServer> | undefined;
const fileServer = expressStaticGzip((await import("zigbee2mqtt-windfront")).default.getOnboardingPath(), FILE_SERVER_OPTIONS);
await new Promise<void>((resolve) => {
server = createServer((req, res) => {
const pathname = new URL(req.url /* v8 ignore next */ ?? "/", serverUrl).pathname;
if (req.method === "GET" && pathname === "/data") {
const payload: OnboardFailureData = {page: "failure", errors};
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(stringify(payload));
return;
}
if (req.method === "POST" && pathname === "/submit") {
res.writeHead(200);
res.end(() => {
resolve();
});
return;
}
const next = finalhandler(req, res);
fileServer(req, res, next);
});
server.listen(Number.parseInt(serverUrl.port, 10), serverUrl.hostname, () => {
console.error(`Failure page is available at ${serverUrl.href}`);
});
});
await new Promise((resolve) => server?.close(resolve));
}
async function onSettingsErrors(errors: string[]): Promise<void> {
console.error("\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
console.error(" READ THIS CAREFULLY\n");
console.error("Refusing to start because configuration is not valid, found the following errors:");
for (const error of errors) {
console.error(`- ${error}`);
}
console.error("\nIf you don't know how to solve this, read https://www.zigbee2mqtt.io/guide/configuration");
console.error("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
if (!process.env.Z2M_ONBOARD_NO_SERVER && !process.env.Z2M_ONBOARD_NO_FAILURE_PAGE) {
await startFailureServer(errors);
}
}
export async function onboard(): Promise<boolean> {
if (!existsSync(data.getPath())) {
mkdirSync(data.getPath(), {recursive: true});
}
const confExists = existsSync(data.joinPath("configuration.yaml"));
if (confExists) {
// initial caching, ensure file is valid yaml first
try {
settings.getPersistedSettings();
} catch (error) {
await onSettingsErrors(
error instanceof YAMLFileException
? [`Your configuration file: '${error.file}' is invalid (use https://jsonformatter.org/yaml-validator to find and fix the issue)`]
: [`${error}`],
);
return false;
}
// migrate first
const {migrateIfNecessary} = await import("./settingsMigration.js");
migrateIfNecessary();
// make sure existing settings are valid before applying envs
const errors = settings.validateNonRequired();
if (errors.length > 0) {
await onSettingsErrors(errors);
return false;
}
// trigger initial writing of `ZIGBEE2MQTT_CONFIG_*` ENVs
settings.write();
} else {
settings.writeMinimalDefaults();
}
// use `configuration.yaml` file to detect "brand new install"
// env allows to re-run onboard even with existing install
if (!process.env.Z2M_ONBOARD_NO_SERVER && (process.env.Z2M_ONBOARD_FORCE_RUN || !confExists || settings.get().onboarding)) {
settings.setOnboarding(true);
const success = await startOnboardingServer();
if (!success) {
return false;
}
}
settings.reRead();
const errors = settings.validate();
if (errors.length > 0) {
await onSettingsErrors(errors);
return false;
}
return true;
}
+69
View File
@@ -0,0 +1,69 @@
import {platform} from "node:os";
import type {UnixDgramSocket} from "unix-dgram";
import logger from "./logger";
/**
* Handle sd_notify protocol, @see https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html
* No-op if running on unsupported platforms or without Type=notify
* Soft-fails if improperly setup (this is not necessary for Zigbee2MQTT to function properly)
*/
export async function initSdNotify(): Promise<{notifyStopping: () => void; stop: () => void} | undefined> {
if (!process.env.NOTIFY_SOCKET) {
return;
}
let socket: UnixDgramSocket | undefined;
try {
const {createSocket} = await import("unix-dgram");
socket = createSocket("unix_dgram");
} catch (error) {
if (platform() !== "win32" || process.env.WSL_DISTRO_NAME) {
// not on plain Windows
logger.error(`Could not init sd_notify: ${(error as Error).message}`);
// biome-ignore lint/style/noNonNullAssertion: always Error
logger.debug((error as Error).stack!);
} else {
// this should not happen
logger.warning(`NOTIFY_SOCKET env is set: ${(error as Error).message}`);
}
return;
}
const sendToSystemd = (msg: string): void => {
const buffer = Buffer.from(msg);
// biome-ignore lint/style/noNonNullAssertion: valid from start of function
socket.send(buffer, 0, buffer.byteLength, process.env.NOTIFY_SOCKET!, (err) => {
if (err) {
logger.warning(`Failed to send "${msg}" to systemd: ${err.message}`);
}
});
};
const notifyStopping = (): void => sendToSystemd("STOPPING=1");
sendToSystemd("READY=1");
const wdUSec = process.env.WATCHDOG_USEC !== undefined ? Math.max(0, Number.parseInt(process.env.WATCHDOG_USEC, 10)) : -1;
if (wdUSec > 0) {
// Convert us to ms, send twice as frequently as the timeout
const watchdogInterval = setInterval(() => sendToSystemd("WATCHDOG=1"), wdUSec / 1000 / 2);
return {
notifyStopping,
stop: (): void => clearInterval(watchdogInterval),
};
}
if (wdUSec !== -1) {
logger.warning(`WATCHDOG_USEC invalid: "${process.env.WATCHDOG_USEC}", parsed to "${wdUSec}"`);
}
return {
notifyStopping,
stop: (): void => {},
};
}
File diff suppressed because it is too large Load Diff
+362 -374
View File
File diff suppressed because it is too large Load Diff
+644
View File
@@ -0,0 +1,644 @@
import {copyFileSync, existsSync, readFileSync, writeFileSync} from "node:fs";
import data from "./data";
import * as settings from "./settings";
import utils from "./utils";
interface SettingsMigration {
path: string[];
note: string;
noteIf?: (previousValue: unknown) => boolean;
}
interface SettingsAdd extends Omit<SettingsMigration, "noteIf"> {
value: unknown;
}
type SettingsRemove = SettingsMigration;
interface SettingsChange extends SettingsMigration {
previousValueAnyOf?: unknown[];
newValue: unknown;
}
interface SettingsTransfer extends SettingsMigration {
newPath: string[];
}
interface SettingsCustomHandler extends Omit<SettingsMigration, "path"> {
execute: (currentSettings: Partial<Settings>) => [validPath: boolean, previousValue: unknown, changed: boolean];
}
const SUPPORTED_VERSIONS: Settings["version"][] = [undefined, 2, 3, 4, settings.CURRENT_VERSION];
function backupSettings(version: number): void {
const filePath = data.joinPath("configuration.yaml");
copyFileSync(filePath, filePath.replace(".yaml", `_backup_v${version}.yaml`));
}
/**
* Set the given path in given settings to given value. If requested, create path.
*
* @param currentSettings
* @param path
* @param value
* @param createPathIfNotExist
* @returns Returns true if value was set, false if not.
*/
// biome-ignore lint/suspicious/noExplicitAny: auto-parsing
function setValue(currentSettings: any, path: string[], value: unknown, createPathIfNotExist = false): boolean {
for (let i = 0; i < path.length; i++) {
const key = path[i];
if (i === path.length - 1) {
currentSettings[key] = value;
} else {
if (!currentSettings[key]) {
if (createPathIfNotExist) {
currentSettings[key] = {};
/* v8 ignore start */
} else {
// invalid path
// ignored in test since currently call is always guarded by get-validated path, so this is never reached
return false;
}
/* v8 ignore stop */
}
currentSettings = currentSettings[key];
}
}
return true;
}
/**
* Get the value at the given path in given settings.
*
* @param currentSettings
* @param path
* @returns
* - true if path was valid
* - the value at path
*/
// biome-ignore lint/suspicious/noExplicitAny: auto-parsing
function getValue(currentSettings: any, path: string[]): [validPath: boolean, value: unknown] {
for (let i = 0; i < path.length; i++) {
const key = path[i];
const value = currentSettings[key];
if (i === path.length - 1) {
return [value !== undefined, value];
}
if (!value) {
// invalid path
break;
}
currentSettings = value;
}
return [false, undefined];
}
/**
* Add a value at given path, path is created as needed.
* @param currentSettings
* @param addition
*/
function addValue(currentSettings: Partial<Settings>, addition: SettingsAdd): void {
setValue(currentSettings, addition.path, addition.value, true);
}
/**
* Remove value at given path, if path is valid.
* Value is actually set to undefined, which triggers removal when `settings.apply` is called.
* @param currentSettings
* @param removal
* @returns
*/
function removeValue(currentSettings: Partial<Settings>, removal: SettingsRemove): [validPath: boolean, previousValue: unknown] {
const [validPath, previousValue] = getValue(currentSettings, removal.path);
if (validPath && previousValue != null) {
setValue(currentSettings, removal.path, undefined);
}
return [validPath, previousValue];
}
/**
* Change value at given path, if path is valid, and value matched one of the defined values (if any).
* @param currentSettings
* @param change
* @returns
*/
function changeValue(currentSettings: Partial<Settings>, change: SettingsChange): [validPath: boolean, previousValue: unknown, changed: boolean] {
const [validPath, previousValue] = getValue(currentSettings, change.path);
let changed = false;
if (validPath && previousValue !== change.newValue) {
if (!change.previousValueAnyOf || change.previousValueAnyOf.includes(previousValue)) {
setValue(currentSettings, change.path, change.newValue);
changed = true;
}
}
return [validPath, previousValue, changed];
}
/**
* Transfer value at given path, to new path.
* Given path must be valid.
* New path must not be valid or new path value must be nullish, otherwise given path is removed only.
* Value at given path is actually set to undefined, which triggers removal when `settings.apply` is called.
* New path is created as needed.
* @param currentSettings
* @param transfer
* @returns
*/
function transferValue(
currentSettings: Partial<Settings>,
transfer: SettingsTransfer,
): [validPath: boolean, previousValue: unknown, transfered: boolean] {
const [validPath, previousValue] = getValue(currentSettings, transfer.path);
const [destValidPath, destValue] = getValue(currentSettings, transfer.newPath);
const transfered = validPath && previousValue != null && (!destValidPath || destValue == null || Array.isArray(destValue));
// no point in set if already undefined
if (validPath && previousValue != null) {
setValue(currentSettings, transfer.path, undefined);
}
if (transfered) {
if (Array.isArray(previousValue) && Array.isArray(destValue)) {
setValue(currentSettings, transfer.newPath, [...previousValue, ...destValue], true);
} else {
setValue(currentSettings, transfer.newPath, previousValue, true);
}
}
return [validPath, previousValue, transfered];
}
const noteIfWasTrue = (previousValue: unknown): boolean => previousValue === true;
const noteIfWasDefined = (previousValue: unknown): boolean => previousValue != null;
const noteIfWasNonEmptyArray = (previousValue: unknown): boolean => Array.isArray(previousValue) && previousValue.length > 0;
function migrateToTwo(
currentSettings: Partial<Settings>,
transfers: SettingsTransfer[],
changes: SettingsChange[],
additions: SettingsAdd[],
removals: SettingsRemove[],
customHandlers: SettingsCustomHandler[],
): void {
transfers.push(
{
path: ["advanced", "homeassistant_discovery_topic"],
note: "HA discovery_topic was moved from advanced.homeassistant_discovery_topic to homeassistant.discovery_topic.",
noteIf: noteIfWasDefined,
newPath: ["homeassistant", "discovery_topic"],
},
{
path: ["advanced", "homeassistant_status_topic"],
note: "HA status_topic was moved from advanced.homeassistant_status_topic to homeassistant.status_topic.",
noteIf: noteIfWasDefined,
newPath: ["homeassistant", "status_topic"],
},
{
path: ["advanced", "baudrate"],
note: "Baudrate was moved from advanced.baudrate to serial.baudrate.",
noteIf: noteIfWasDefined,
newPath: ["serial", "baudrate"],
},
{
path: ["advanced", "rtscts"],
note: "RTSCTS was moved from advanced.rtscts to serial.rtscts.",
noteIf: noteIfWasDefined,
newPath: ["serial", "rtscts"],
},
{
path: ["experimental", "transmit_power"],
note: "Transmit power was moved from experimental.transmit_power to advanced.transmit_power.",
noteIf: noteIfWasDefined,
newPath: ["advanced", "transmit_power"],
},
{
path: ["experimental", "output"],
note: "Output was moved from experimental.output to advanced.output.",
noteIf: noteIfWasDefined,
newPath: ["advanced", "output"],
},
{
path: ["ban"],
note: "ban was renamed to passlist.",
noteIf: noteIfWasDefined,
newPath: ["blocklist"],
},
{
path: ["whitelist"],
note: "whitelist was renamed to passlist.",
noteIf: noteIfWasDefined,
newPath: ["passlist"],
},
);
changes.push({
path: ["advanced", "log_level"],
note: `Log level 'warn' has been renamed to 'warning'.`,
noteIf: (previousValue): boolean => previousValue === "warn",
previousValueAnyOf: ["warn"],
newValue: "warning",
});
additions.push({
path: ["version"],
note: "Migrated settings to version 2",
value: 2,
});
const haLegacyTriggers: SettingsRemove = {
path: ["homeassistant", "legacy_triggers"],
note: "Action and click sensors have been removed (homeassistant.legacy_triggers setting). This means all sensor.*_action and sensor.*_click entities are removed. Use the MQTT device trigger instead.",
noteIf: noteIfWasTrue,
};
const haLegacyEntityAttrs: SettingsRemove = {
path: ["homeassistant", "legacy_entity_attributes"],
note: "Entity attributes (homeassistant.legacy_entity_attributes setting) has been removed. This means that entities discovered by Zigbee2MQTT will no longer have entity attributes (Home Assistant entity attributes are accessed via e.g. states.binary_sensor.my_sensor.attributes).",
noteIf: noteIfWasTrue,
};
const otaIkeaUseTestUrl: SettingsRemove = {
path: ["ota", "ikea_ota_use_test_url"],
note: "Due to the OTA rework, the ota.ikea_ota_use_test_url option has been removed.",
noteIf: noteIfWasTrue,
};
removals.push(
haLegacyTriggers,
haLegacyEntityAttrs,
{
path: ["advanced", "homeassistant_legacy_triggers"],
note: haLegacyTriggers.note,
noteIf: haLegacyTriggers.noteIf,
},
{
path: ["advanced", "homeassistant_legacy_entity_attributes"],
note: haLegacyEntityAttrs.note,
noteIf: haLegacyEntityAttrs.noteIf,
},
{
path: ["permit_join"],
note: "The permit_join setting has been removed, use the frontend or MQTT to permit joining.",
noteIf: noteIfWasTrue,
},
otaIkeaUseTestUrl,
{
path: ["advanced", "ikea_ota_use_test_url"],
note: otaIkeaUseTestUrl.note,
noteIf: otaIkeaUseTestUrl.noteIf,
},
{
path: ["advanced", "legacy_api"],
note: "The MQTT legacy API has been removed (advanced.legacy_api setting). See link below for affected topics.",
noteIf: noteIfWasTrue,
},
{
path: ["advanced", "legacy_availability_payload"],
note: 'Due to the removal of advanced.legacy_availability_payload, zigbee2mqtt/bridge/state will now always be a JSON object ({"state":"online"} or {"state":"offline"})',
noteIf: noteIfWasTrue,
},
{
path: ["advanced", "soft_reset_timeout"],
note: "Removed deprecated: Soft reset feature (advanced.soft_reset_timeout setting)",
noteIf: noteIfWasDefined,
},
{
path: ["advanced", "report"],
note: "Removed deprecated: Report feature (advanced.report setting)",
noteIf: noteIfWasTrue,
},
{
path: ["advanced", "availability_timeout"],
note: "Removed deprecated: advanced.availability_timeout availability settings",
noteIf: noteIfWasDefined,
},
{
path: ["advanced", "availability_blocklist"],
note: "Removed deprecated: advanced.availability_blocklist availability settings",
noteIf: noteIfWasNonEmptyArray,
},
{
path: ["advanced", "availability_passlist"],
note: "Removed deprecated: advanced.availability_passlist availability settings",
noteIf: noteIfWasNonEmptyArray,
},
{
path: ["advanced", "availability_blacklist"],
note: "Removed deprecated: advanced.availability_blacklist availability settings",
noteIf: noteIfWasNonEmptyArray,
},
{
path: ["advanced", "availability_whitelist"],
note: "Removed deprecated: advanced.availability_whitelist availability settings",
noteIf: noteIfWasNonEmptyArray,
},
{
path: ["device_options", "legacy"],
note: "Removed everything that was enabled through device_options.legacy. See link below for affected devices.",
noteIf: noteIfWasTrue,
},
{
path: ["experimental"],
note: "The entire experimental section was removed.",
noteIf: noteIfWasDefined,
},
{
path: ["external_converters"],
note: "External converters are now automatically loaded from the 'data/external_converters' directory without requiring settings to be set. Make sure your external converters are still needed (might be supported out-of-the-box now), and if so, move them to that directory.",
noteIf: noteIfWasNonEmptyArray,
},
);
// note only once
const noteEntityOptionsRetrieveState = "Retrieve state option ((devices|groups).xyz.retrieve_state setting)";
for (const deviceKey in currentSettings.devices) {
removals.push({
path: ["devices", deviceKey, "retrieve_state"],
note: noteEntityOptionsRetrieveState,
noteIf: noteIfWasTrue,
});
}
for (const groupKey in currentSettings.groups) {
removals.push({
path: ["groups", groupKey, "retrieve_state"],
note: noteEntityOptionsRetrieveState,
noteIf: noteIfWasTrue,
});
removals.push({
path: ["groups", groupKey, "devices"],
note: "Removed configuring group members through configuration.yaml (groups.xyz.devices setting). This will not impact current group members; however, you will no longer be able to add or remove devices from a group through the configuration.yaml.",
noteIf: noteIfWasDefined,
});
}
customHandlers.push();
}
function migrateToThree(
_currentSettings: Partial<Settings>,
transfers: SettingsTransfer[],
changes: SettingsChange[],
additions: SettingsAdd[],
removals: SettingsRemove[],
customHandlers: SettingsCustomHandler[],
): void {
transfers.push();
changes.push({
path: ["version"],
note: "Migrated settings to version 3",
newValue: 3,
});
additions.push();
removals.push();
const changeToObject = (currentSettings: Partial<Settings>, path: string[]): ReturnType<SettingsCustomHandler["execute"]> => {
const [validPath, previousValue] = getValue(currentSettings, path);
if (validPath) {
if (typeof previousValue === "boolean") {
setValue(currentSettings, path, {enabled: previousValue});
} else {
setValue(currentSettings, path, {enabled: true, ...(previousValue as object)});
}
}
return [validPath, previousValue, validPath];
};
customHandlers.push(
{
note: `Property 'homeassistant' is now always an object.`,
noteIf: () => true,
execute: (currentSettings) => changeToObject(currentSettings, ["homeassistant"]),
},
{
note: `Property 'frontend' is now always an object.`,
noteIf: () => true,
execute: (currentSettings) => changeToObject(currentSettings, ["frontend"]),
},
{
note: `Property 'availability' is now always an object.`,
noteIf: () => true,
execute: (currentSettings) => changeToObject(currentSettings, ["availability"]),
},
);
}
function migrateToFour(
_currentSettings: Partial<Settings>,
transfers: SettingsTransfer[],
changes: SettingsChange[],
additions: SettingsAdd[],
removals: SettingsRemove[],
customHandlers: SettingsCustomHandler[],
): void {
transfers.push();
changes.push({
path: ["version"],
note: "Migrated settings to version 4",
newValue: 4,
});
additions.push();
removals.push();
const saveBase64DeviceIconsAsImage = (currentSettings: Partial<Settings>): ReturnType<SettingsCustomHandler["execute"]> => {
const [validPath, previousValue] = getValue(currentSettings, ["devices"]);
let changed = false;
if (validPath) {
for (const deviceKey in currentSettings.devices) {
const base64Match = utils.matchBase64File(currentSettings.devices[deviceKey].icon);
if (base64Match) {
changed = true;
currentSettings.devices[deviceKey].icon = utils.saveBase64DeviceIcon(base64Match);
}
}
}
return [validPath, previousValue, changed];
};
customHandlers.push({
note: "Device icons are now saved as images.",
noteIf: () => true,
execute: (currentSettings) => saveBase64DeviceIconsAsImage(currentSettings),
});
}
function migrateToFive(
_currentSettings: Partial<Settings>,
transfers: SettingsTransfer[],
changes: SettingsChange[],
additions: SettingsAdd[],
removals: SettingsRemove[],
customHandlers: SettingsCustomHandler[],
): void {
transfers.push();
changes.push({
path: ["version"],
note: "Migrated settings to version 5",
newValue: 5,
});
additions.push();
removals.push();
const resetOtaStates = (_currentSettings: Partial<Settings>): ReturnType<SettingsCustomHandler["execute"]> => {
const filePath = data.joinPath("state.json");
if (existsSync(filePath)) {
try {
const stateFile = readFileSync(filePath, "utf8");
const stateObj = JSON.parse(stateFile) as KeyValue;
for (const key in stateObj) {
const entry = stateObj[key];
if (entry.update) {
delete entry.update;
}
}
writeFileSync(filePath, JSON.stringify(stateObj), "utf8");
// hack, this does not actually affect settings, we just want to ensure the migration note triggers
return [true, undefined, true];
} catch (error) {
console.error(`Failed to write state to '${filePath}' (${error})`);
}
}
return [false, undefined, false];
};
customHandlers.push({
note: "Reset all cached OTA states due to internal changes.",
noteIf: () => true,
execute: (currentSettings) => resetOtaStates(currentSettings),
});
}
/**
* Order of execution:
* - Transfer
* - Change
* - Add
* - Remove
*
* Should allow the most flexibility whenever combination of migrations is necessary (e.g. Transfer + Change)
*/
export function migrateIfNecessary(): void {
const currentSettings = settings.getPersistedSettings();
if (!SUPPORTED_VERSIONS.includes(currentSettings.version)) {
throw new Error(
`Your configuration.yaml has an unsupported version ${currentSettings.version}, expected one of ${SUPPORTED_VERSIONS.map((v) => String(v)).join(",")}.`,
);
}
/* v8 ignore next */
const finalVersion = process.env.VITEST_WORKER_ID ? settings.testing.CURRENT_VERSION : settings.CURRENT_VERSION;
if (currentSettings.version === finalVersion) {
// when same version as current, nothing to do
return;
}
while (currentSettings.version !== finalVersion) {
let migrationNotesFileName: string | undefined;
// don't duplicate outputs
const migrationNotes = new Set<string>();
const transfers: SettingsTransfer[] = [];
const changes: SettingsChange[] = [];
const additions: SettingsAdd[] = [];
const removals: SettingsRemove[] = [];
const customHandlers: SettingsCustomHandler[] = [];
backupSettings(currentSettings.version || 1);
// each version should only bump to the next version so as to gradually migrate if necessary
if (currentSettings.version == null) {
// migrating from 1 (`version` did not exist) to 2
migrationNotesFileName = "migration-1-to-2.log";
migrateToTwo(currentSettings, transfers, changes, additions, removals, customHandlers);
} else if (currentSettings.version === 2) {
migrationNotesFileName = "migration-2-to-3.log";
migrateToThree(currentSettings, transfers, changes, additions, removals, customHandlers);
} else if (currentSettings.version === 3) {
migrationNotesFileName = "migration-3-to-4.log";
migrateToFour(currentSettings, transfers, changes, additions, removals, customHandlers);
} else if (currentSettings.version === 4) {
migrationNotesFileName = "migration-4-to-5.log";
migrateToFive(currentSettings, transfers, changes, additions, removals, customHandlers);
}
for (const transfer of transfers) {
const [validPath, previousValue, transfered] = transferValue(currentSettings, transfer);
if (validPath && (!transfer.noteIf || transfer.noteIf(previousValue))) {
migrationNotes.add(`[${transfered ? "TRANSFER" : "REMOVAL"}] ${transfer.note}`);
}
}
for (const change of changes) {
const [validPath, previousValue, changed] = changeValue(currentSettings, change);
if (validPath && changed && (!change.noteIf || change.noteIf(previousValue))) {
migrationNotes.add(`[CHANGE] ${change.note}`);
}
}
for (const addition of additions) {
addValue(currentSettings, addition);
migrationNotes.add(`[ADDITION] ${addition.note}`);
}
for (const removal of removals) {
const [validPath, previousValue] = removeValue(currentSettings, removal);
if (validPath && (!removal.noteIf || removal.noteIf(previousValue))) {
migrationNotes.add(`[REMOVAL] ${removal.note}`);
}
}
for (const customHandler of customHandlers) {
const [validPath, previousValue, changed] = customHandler.execute(currentSettings);
if (validPath && changed && (!customHandler.noteIf || customHandler.noteIf(previousValue))) {
migrationNotes.add(`[SPECIAL] ${customHandler.note}`);
}
}
if (migrationNotesFileName && migrationNotes.size > 0) {
migrationNotes.add("For more details, see https://github.com/Koenkk/zigbee2mqtt/discussions/24198");
const migrationNotesFilePath = data.joinPath(migrationNotesFileName);
writeFileSync(migrationNotesFilePath, Array.from(migrationNotes).join("\r\n\r\n"), "utf8");
console.log(`Migration notes written in ${migrationNotesFilePath}`);
}
}
// don't throw, onboarding will validate at end of process
settings.apply(currentSettings as unknown as Record<string, unknown>, false);
settings.reRead();
}
+262 -240
View File
@@ -1,11 +1,23 @@
import equals from 'fast-deep-equal/es6';
import humanizeDuration from 'humanize-duration';
import data from './data';
import vm from 'vm';
import fs from 'fs';
import path from 'path';
import {detailedDiff} from 'deep-object-diff';
import objectAssignDeep from 'object-assign-deep';
import assert from "node:assert";
import {exec} from "node:child_process";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import equals from "fast-deep-equal/es6";
import humanizeDuration from "humanize-duration";
import type * as zhc from "zigbee-herdsman-converters";
import type {Zigbee2MQTTAPI, Zigbee2MQTTResponse, Zigbee2MQTTResponseEndpoints, Zigbee2MQTTScene} from "../types/api";
import data from "./data";
const BASE64_IMAGE_REGEX = /data:image\/(?<extension>.+);base64,(?<data>.+)/;
export const DEFAULT_BIND_GROUP_ID = 901;
function pad(num: number): string {
const norm = Math.floor(Math.abs(num));
return (norm < 10 ? "0" : "") + norm;
}
// construct a local ISO8601 string (instead of UTC-based)
// Example:
@@ -13,89 +25,74 @@ import objectAssignDeep from 'object-assign-deep';
// - ISO8601 (local) = 2019-03-01T16:32:45.941+0100 (for timezone GMT+1)
function toLocalISOString(date: Date): string {
const tzOffset = -date.getTimezoneOffset();
const plusOrMinus = tzOffset >= 0 ? '+' : '-';
const pad = (num: number): string => {
const norm = Math.floor(Math.abs(num));
return (norm < 10 ? '0' : '') + norm;
};
const plusOrMinus = tzOffset >= 0 ? "+" : "-";
return date.getFullYear() +
'-' + pad(date.getMonth() + 1) +
'-' + pad(date.getDate()) +
'T' + pad(date.getHours()) +
':' + pad(date.getMinutes()) +
':' + pad(date.getSeconds()) +
plusOrMinus + pad(tzOffset / 60) +
':' + pad(tzOffset % 60);
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${plusOrMinus}${pad(tzOffset / 60)}:${pad(tzOffset % 60)}`;
}
const endpointNames = [
'left', 'right', 'center', 'bottom_left', 'bottom_right', 'default',
'top_left', 'top_right', 'white', 'rgb', 'cct', 'system', 'top', 'bottom', 'center_left', 'center_right',
'ep1', 'ep2', 'row_1', 'row_2', 'row_3', 'row_4', 'relay', 'usb',
'l1', 'l2', 'l3', 'l4', 'l5', 'l6', 'l7', 'l8',
'l9', 'l10', 'l11', 'l12', 'l13', 'l14', 'l15', 'l16',
'button_1', 'button_2', 'button_3', 'button_4', 'button_5',
'button_6', 'button_7', 'button_8', 'button_9', 'button_10',
'button_11', 'button_12', 'button_13', 'button_14', 'button_15',
'button_16', 'button_17', 'button_18', 'button_19', 'button_20',
'button_light', 'button_fan_high', 'button_fan_med', 'button_fan_low',
'heat', 'cool', 'water', 'meter', 'wifi', 'no_occupancy_since',
];
function capitalize(s: string): string {
return s[0].toUpperCase() + s.slice(1);
}
async function getZigbee2MQTTVersion(includeCommitHash=true): Promise<{commitHash: string, version: string}> {
const git = await import('git-last-commit');
const packageJSON = await import('../..' + '/package.json');
export async function getZigbee2MQTTVersion(includeCommitHash = true): Promise<{commitHash?: string; version: string}> {
const packageJSON = (await import("../../package.json", {with: {type: "json"}})).default;
const version = packageJSON.version;
let commitHash: string | undefined;
if (!includeCommitHash) {
return {version: packageJSON.version, commitHash: null};
return {version, commitHash};
}
return new Promise((resolve) => {
const version = packageJSON.version;
return await new Promise((resolve) => {
exec("git rev-parse --short=8 HEAD", (error, stdout) => {
commitHash = stdout.trim();
git.getLastCommit((err: Error, commit: {shortHash: string}) => {
let commitHash = null;
if (err) {
if (error || commitHash === "") {
try {
commitHash = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', '.hash'), 'utf-8');
} catch (error) {
/* istanbul ignore next */
commitHash = 'unknown';
commitHash = fs.readFileSync(path.join(__dirname, "..", "..", "dist", ".hash"), "utf-8");
} catch {
commitHash = "unknown";
}
} else {
commitHash = commit.shortHash;
}
commitHash = commitHash.trim();
resolve({commitHash, version});
});
});
}
async function getDependencyVersion(depend: string): Promise<{version: string}> {
const packageJSON = await import(path.join(require.resolve(depend), '..', '..', 'package.json'));
const version = packageJSON.version;
return {version};
const packageJSON = (await import(`${depend}/package.json`, {with: {type: "json"}})).default;
return {version: packageJSON.version};
}
function formatDate(time: number, type: 'ISO_8601' | 'ISO_8601_local' | 'epoch' | 'relative'): string | number {
if (type === 'ISO_8601') return new Date(time).toISOString();
else if (type === 'ISO_8601_local') return toLocalISOString(new Date(time));
else if (type === 'epoch') return time;
else { // relative
return humanizeDuration(Date.now() - time, {language: 'en', largest: 2, round: true}) + ' ago';
function formatDate(time: number, type: "ISO_8601" | "ISO_8601_local" | "epoch" | "relative"): string | number {
switch (type) {
case "ISO_8601":
// ISO8601 (UTC) = 2019-03-01T15:32:45.941Z
return new Date(time).toISOString();
case "ISO_8601_local":
// ISO8601 (local) = 2019-03-01T16:32:45.941+01:00 (for timezone GMT+1)
return toLocalISOString(new Date(time));
case "epoch":
return time;
default:
// relative
return `${humanizeDuration(Date.now() - time, {language: "en", largest: 2, round: true})} ago`;
}
}
function objectIsEmpty(object: object): boolean {
// much faster than checking `Object.keys(object).length`
for (const _k in object) return false;
return true;
}
function objectHasProperties(object: {[s: string]: unknown}, properties: string[]): boolean {
for (const property of properties) {
if (!object.hasOwnProperty(property)) {
if (object[property] === undefined) {
return false;
}
}
@@ -113,123 +110,90 @@ function equalsPartial(object: KeyValue, expected: KeyValue): boolean {
return true;
}
function getObjectProperty(object: KeyValue, key: string, defaultValue: unknown): unknown {
return object && object.hasOwnProperty(key) ? object[key] : defaultValue;
function getObjectProperty<T>(object: KeyValue, key: string, defaultValue: NoInfer<T>): T {
return object && object[key] !== undefined ? object[key] : defaultValue;
}
function getResponse(request: KeyValue | string, data: KeyValue, error: string): MQTTResponse {
const response: MQTTResponse = {data, status: error ? 'error' : 'ok'};
if (error) response.error = error;
if (typeof request === 'object' && request.hasOwnProperty('transaction')) {
function getResponse<T extends Zigbee2MQTTResponseEndpoints>(
request: KeyValue | string,
data: Zigbee2MQTTAPI[T],
error?: string,
): Zigbee2MQTTResponse<T> {
if (error !== undefined) {
const response: Zigbee2MQTTResponse<T> = {
data: {}, // always return an empty `data` payload on error
status: "error",
error: error,
};
if (typeof request === "object" && request.transaction !== undefined) {
response.transaction = request.transaction;
}
return response;
}
const response: Zigbee2MQTTResponse<T> = {
data, // valid from error check
status: "ok",
};
if (typeof request === "object" && request.transaction !== undefined) {
response.transaction = request.transaction;
}
return response;
}
function parseJSON(value: string, fallback: string): KeyValue | string {
try {
return JSON.parse(value);
} catch (e) {
} catch {
return fallback;
}
}
function loadModuleFromText(moduleCode: string, name?: string): unknown {
const moduleFakePath = path.join(__dirname, '..', '..', 'data', 'extension', name || 'externally-loaded.js');
const sandbox = {
require: require,
module: {},
console,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
setImmediate,
clearImmediate,
};
vm.runInNewContext(moduleCode, sandbox, moduleFakePath);
/* eslint-disable-line */ // @ts-ignore
return sandbox.module.exports;
}
function loadModuleFromFile(modulePath: string): unknown {
const moduleCode = fs.readFileSync(modulePath, {encoding: 'utf8'});
return loadModuleFromText(moduleCode);
}
function* getExternalConvertersDefinitions(settings: Settings): Generator<zhc.ExternalDefinition> {
const externalConverters = settings.external_converters;
for (const moduleName of externalConverters) {
let converter;
if (moduleName.endsWith('.js')) {
converter = loadModuleFromFile(data.joinPath(moduleName));
} else {
converter = require(moduleName);
}
if (Array.isArray(converter)) {
for (const item of converter) {
yield item;
}
} else {
yield converter;
}
}
}
function removeNullPropertiesFromObject(obj: KeyValue): void {
/**
* Delete all keys from passed object that have null/undefined values.
*
* @param {KeyValue} obj Object to process (in-place)
* @param {string[]} [ignoreKeys] Recursively ignore these keys in the object (keep null/undefined values).
*/
function removeNullPropertiesFromObject(obj: KeyValue, ignoreKeys: string[] = []): void {
for (const key of Object.keys(obj)) {
if (ignoreKeys.includes(key)) {
continue;
}
const value = obj[key];
if (value == null) {
delete obj[key];
} else if (typeof value === 'object') {
removeNullPropertiesFromObject(value);
} else if (typeof value === "object") {
removeNullPropertiesFromObject(value, ignoreKeys);
}
}
}
function toNetworkAddressHex(value: number): string {
const hex = value.toString(16);
return `0x${'0'.repeat(4 - hex.length)}${hex}`;
}
// eslint-disable-next-line
function toSnakeCase(value: string | KeyValue): any {
if (typeof value === 'object') {
value = {...value};
for (const key of Object.keys(value)) {
const keySnakeCase = toSnakeCase(key);
if (key !== keySnakeCase) {
value[keySnakeCase] = value[key];
delete value[key];
}
}
return value;
} else {
return value.replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()).replace(/^_/, '').replace('_i_d', '_id');
}
return `0x${"0".repeat(4 - hex.length)}${hex}`;
}
function charRange(start: string, stop: string): number[] {
const result = [];
for (let idx=start.charCodeAt(0), end=stop.charCodeAt(0); idx <=end; ++idx) {
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'),
];
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)) {
if (controlCharacters.includes(ch) || [0xfffe, 0xffff].includes(ch & 0xffff)) {
return true;
}
}
@@ -238,31 +202,32 @@ function containsControlCharacter(str: string): boolean {
function getAllFiles(path_: string): string[] {
const result = [];
for (let item of fs.readdirSync(path_)) {
item = path.join(path_, item);
if (fs.lstatSync(item).isFile()) {
result.push(item);
for (const item of fs.readdirSync(path_, {withFileTypes: true})) {
if (item.isSymbolicLink()) {
continue;
}
const fileName = path.join(path_, item.name);
if (fs.lstatSync(fileName).isFile()) {
result.push(fileName);
} else {
result.push(...getAllFiles(item));
result.push(...getAllFiles(fileName));
}
}
return result;
}
function validateFriendlyName(name: string, throwFirstError=false): string[] {
function validateFriendlyName(name: string, throwFirstError = false): string[] {
const errors = [];
for (const endpointName of endpointNames) {
if (name.toLowerCase().endsWith('/' + endpointName)) {
errors.push(`friendly_name is not allowed to end with: '/${endpointName}'`);
}
}
if (name.length === 0) errors.push(`friendly_name must be at least 1 char long`);
if (name.endsWith('/') || name.startsWith('/')) errors.push(`friendly_name is not allowed to end or start with /`);
if (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.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 (containsControlCharacter(name)) errors.push("friendly_name is not allowed to contain control char");
if (name.match(/.*\/\d*$/)) errors.push(`Friendly name cannot end with a "/DIGIT" ('${name}')`);
if (name.includes('#') || name.includes('+')) {
if (name.includes("#") || name.includes("+")) {
errors.push(`MQTT wildcard (+ and #) not allowed in friendly_name ('${name}')`);
}
@@ -278,48 +243,37 @@ function sleep(seconds: number): Promise<void> {
}
function sanitizeImageParameter(parameter: string): string {
const replaceByDash = [/\?/g, /&/g, /[^a-z\d\- _./:]/gi];
let sanitized = parameter;
replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-'));
return sanitized;
return parameter.replace(/\?|&|[^a-z\d\- _./:]/gi, "-");
}
function isAvailabilityEnabledForEntity(entity: Device | Group, settings: Settings): boolean {
if (entity.isGroup()) {
return !entity.membersDevices().map((d) => isAvailabilityEnabledForEntity(d, settings)).includes(false);
if (entity.isDevice() && entity.options.disabled) {
return false;
}
if (entity.options.hasOwnProperty('availability')) {
if (entity.isGroup()) {
for (const memberDevice of entity.membersDevices()) {
if (!isAvailabilityEnabledForEntity(memberDevice, settings)) {
return false;
}
}
return true;
}
if (entity.options.availability != null) {
return !!entity.options.availability;
}
// availability_timeout = deprecated
const enabledGlobal = settings.advanced.availability_timeout || settings.availability;
if (!enabledGlobal) return false;
if (entity.isDevice() && entity.options.disabled) return false;
const passlist = settings.advanced.availability_passlist.concat(settings.advanced.availability_whitelist);
if (passlist.length > 0) {
return passlist.includes(entity.name) || passlist.includes(entity.ieeeAddr);
}
const blocklist = settings.advanced.availability_blacklist.concat(settings.advanced.availability_blocklist);
return !blocklist.includes(entity.name) && !blocklist.includes(entity.ieeeAddr);
return settings.availability.enabled;
}
const entityIDRegex = new RegExp(`^(.+?)(?:/(${endpointNames.join('|')}|\\d+))?$`);
function parseEntityID(ID: string): {ID: string, endpoint: string} {
const match = ID.match(entityIDRegex);
return match && {ID: match[1], endpoint: match[2]};
}
function isEndpoint(obj: unknown): obj is zh.Endpoint {
return obj.constructor.name.toLowerCase() === 'endpoint';
function isZHEndpoint(obj: unknown): obj is zh.Endpoint {
return obj?.constructor.name.toLowerCase() === "endpoint";
}
function flatten<Type>(arr: Type[][]): Type[] {
return [].concat(...arr);
return ([] as Type[]).concat(...arr);
}
function arrayUnique<Type>(arr: Type[]): Type[] {
@@ -327,19 +281,19 @@ function arrayUnique<Type>(arr: Type[]): Type[] {
}
function isZHGroup(obj: unknown): obj is zh.Group {
return obj.constructor.name.toLowerCase() === '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});
}
export const hours = (hours: number): number => 1000 * 60 * 60 * hours;
export const minutes = (minutes: number): number => 1000 * 60 * minutes;
export const seconds = (seconds: number): number => 1000 * seconds;
const hours = (hours: number): number => 1000 * 60 * 60 * hours;
const minutes = (minutes: number): number => 1000 * 60 * minutes;
const seconds = (seconds: number): number => 1000 * seconds;
function publishLastSeen(data: eventdata.LastSeenChanged, settings: Settings, allowMessageEmitted: boolean,
publishEntityState: PublishEntityState): void {
async function publishLastSeen(
data: eventdata.LastSeenChanged,
settings: Settings,
allowMessageEmitted: boolean,
publishEntityState: PublishEntityState,
): Promise<void> {
/**
* Prevent 2 MQTT publishes when 1 message event is received;
* - In case reason == messageEmitted, receive.ts will only call this when it did not publish a
@@ -347,13 +301,13 @@ function publishLastSeen(data: eventdata.LastSeenChanged, settings: Settings, al
* - In case reason !== messageEmitted, controller.ts will call this based on the zigbee-herdsman
* lastSeenChanged event.
*/
const allow = data.reason !== 'messageEmitted' || (data.reason === 'messageEmitted' && allowMessageEmitted);
if (settings.advanced.last_seen && settings.advanced.last_seen !== 'disable' && allow) {
publishEntityState(data.device, {}, 'lastSeenChanged');
const allow = data.reason !== "messageEmitted" || (data.reason === "messageEmitted" && allowMessageEmitted);
if (settings.advanced.last_seen && settings.advanced.last_seen !== "disable" && allow) {
await publishEntityState(data.device, {}, "lastSeenChanged");
}
}
function filterProperties(filter: string[], data: KeyValue): void {
function filterProperties(filter: string[] | undefined, data: KeyValue): void {
if (filter) {
for (const property of Object.keys(data)) {
if (filter.find((p) => property.match(`^${p}$`))) {
@@ -363,48 +317,116 @@ function filterProperties(filter: string[], data: KeyValue): void {
}
}
function clone(obj: KeyValue): KeyValue {
return JSON.parse(JSON.stringify(obj));
export function isNumericExpose(expose: zhc.Expose): expose is zhc.Numeric {
return expose?.type === "numeric";
}
function computeSettingsToChange(current: KeyValue, new_: KeyValue): KeyValue {
const diff: KeyValue = detailedDiff(current, new_);
// Remove any settings that are in the deleted.diff but not in the passed options
const cleanupDeleted = (options: KeyValue, deleted: KeyValue): void => {
for (const key of Object.keys(deleted)) {
if (!(key in options)) {
delete deleted[key];
} else if (!Array.isArray(options[key])) {
cleanupDeleted(options[key], deleted[key]);
}
}
};
cleanupDeleted(new_, diff.deleted);
// objectAssignDeep requires object prototype which is missing from detailedDiff, therefore clone
const newSettings = objectAssignDeep({}, clone(diff.added), clone(diff.updated), clone(diff.deleted));
// deep-object-diff converts arrays to objects, set original array back here
const convertBackArray = (before: KeyValue, after: KeyValue): void => {
for (const [key, afterValue] of Object.entries(after)) {
const beforeValue = before[key];
if (Array.isArray(beforeValue)) {
after[key] = beforeValue;
} else if (afterValue && typeof beforeValue === 'object') {
convertBackArray(beforeValue, afterValue);
}
}
};
convertBackArray(new_, newSettings);
return newSettings;
export function assertEnumExpose(expose: zhc.Expose): asserts expose is zhc.Enum {
assert(expose?.type === "enum");
}
export function assertNumericExpose(expose: zhc.Expose): asserts expose is zhc.Numeric {
assert(expose?.type === "numeric");
}
export function assertBinaryExpose(expose: zhc.Expose): asserts expose is zhc.Binary {
assert(expose?.type === "binary");
}
export function isEnumExpose(expose: zhc.Expose): expose is zhc.Enum {
return expose?.type === "enum";
}
export function isBinaryExpose(expose: zhc.Expose): expose is zhc.Binary {
return expose?.type === "binary";
}
export function isLightExpose(expose: zhc.Expose): expose is zhc.Light {
return expose.type === "light";
}
export function assertString(value: unknown, property: string): asserts value is string {
if (typeof value !== "string") {
throw new Error(`${property} is not a string, got ${typeof value} (${value})`);
}
}
function getScenes(entity: zh.Endpoint | zh.Group): Zigbee2MQTTScene[] {
const scenes: {[id: number]: Zigbee2MQTTScene} = {};
const endpoints = isZHEndpoint(entity) ? [entity] : entity.members;
const groupID = isZHEndpoint(entity) ? 0 : entity.groupID;
for (const endpoint of endpoints) {
for (const [key, data] of Object.entries(endpoint.meta?.scenes || {})) {
const split = key.split("_");
const sceneID = Number.parseInt(split[0], 10);
const sceneGroupID = Number.parseInt(split[1], 10);
if (sceneGroupID === groupID) {
scenes[sceneID] = {id: sceneID, name: (data as KeyValue).name || `Scene ${sceneID}`};
}
}
}
return Object.values(scenes);
}
function deviceNotCoordinator(device: zh.Device): boolean {
return device.type !== "Coordinator";
}
function matchBase64File(value: string | undefined): {extension: string; data: string} | false {
if (value !== undefined) {
const match = value.match(BASE64_IMAGE_REGEX);
if (match) {
assert(match.groups?.extension && match.groups?.data);
return {extension: match.groups.extension, data: match.groups.data};
}
}
return false;
}
function saveBase64DeviceIcon(base64Match: {extension: string; data: string}): string {
const md5Hash = crypto.createHash("md5").update(base64Match.data).digest("hex");
const fileSettings = `device_icons/${md5Hash}.${base64Match.extension}`;
const file = path.join(data.getPath(), fileSettings);
fs.mkdirSync(path.dirname(file), {recursive: true});
fs.writeFileSync(file, base64Match.data, {encoding: "base64"});
return fileSettings;
}
/* v8 ignore next */
const noop = (): void => {};
export default {
endpointNames, capitalize, getZigbee2MQTTVersion, getDependencyVersion, formatDate, objectHasProperties,
equalsPartial, getObjectProperty, getResponse, parseJSON, loadModuleFromText, loadModuleFromFile,
getExternalConvertersDefinitions, removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase,
parseEntityID, isEndpoint, isZHGroup, hours, minutes, seconds, validateFriendlyName, sleep,
sanitizeImageParameter, isAvailabilityEnabledForEntity, publishLastSeen, availabilityPayload,
getAllFiles, filterProperties, flatten, arrayUnique, clone, computeSettingsToChange,
matchBase64File,
saveBase64DeviceIcon,
capitalize,
getZigbee2MQTTVersion,
getDependencyVersion,
formatDate,
objectIsEmpty,
objectHasProperties,
equalsPartial,
getObjectProperty,
getResponse,
parseJSON,
removeNullPropertiesFromObject,
toNetworkAddressHex,
isZHEndpoint,
isZHGroup,
hours,
minutes,
seconds,
validateFriendlyName,
sleep,
sanitizeImageParameter,
isAvailabilityEnabledForEntity,
publishLastSeen,
getAllFiles,
filterProperties,
flatten,
arrayUnique,
getScenes,
deviceNotCoordinator,
noop,
};
+27 -8
View File
@@ -1,25 +1,44 @@
import yaml from 'js-yaml';
import fs from 'fs';
import equals from 'fast-deep-equal/es6';
import assert from "node:assert";
import fs from "node:fs";
import equals from "fast-deep-equal/es6";
import yaml, {YAMLException} from "js-yaml";
export class YAMLFileException extends YAMLException {
file: string;
constructor(error: YAMLException, file: string) {
super(error.reason, error.mark);
this.name = "YAMLFileException";
this.cause = error.cause;
this.message = error.message;
this.stack = error.stack;
this.file = file;
}
}
function read(file: string): KeyValue {
try {
return yaml.load(fs.readFileSync(file, 'utf8'));
const result = yaml.load(fs.readFileSync(file, "utf8"));
assert(result instanceof Object, `The content of ${file} is expected to be an object`);
return result as KeyValue;
} catch (error) {
if (error.name === 'YAMLException') {
error.file = file;
if (error instanceof YAMLException) {
throw new YAMLFileException(error, file);
}
throw error;
}
}
function readIfExists(file: string, default_?: KeyValue): KeyValue {
return fs.existsSync(file) ? read(file) : default_;
function readIfExists(file: string, fallback: KeyValue = {}): KeyValue {
return fs.existsSync(file) ? read(file) : fallback;
}
function writeIfChanged(file: string, content: KeyValue): void {
const before = readIfExists(file);
if (!equals(before, content)) {
fs.writeFileSync(file, yaml.dump(content));
}
+303 -168
View File
@@ -1,40 +1,49 @@
import {Controller} from 'zigbee-herdsman';
import logger from './util/logger';
import * as settings from './util/settings';
import data from './util/data';
import utils from './util/utils';
import objectAssignDeep from 'object-assign-deep';
import stringify from 'json-stable-stringify-without-jsonify';
import Device from './model/device';
import Group from './model/group';
import * as ZHEvents from 'zigbee-herdsman/dist/controller/events';
import bind from 'bind-decorator';
import {randomInt} from "node:crypto";
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import type {Events as ZHEvents} from "zigbee-herdsman";
import {Controller} from "zigbee-herdsman";
import type {StartResult} from "zigbee-herdsman/dist/adapter/tstype";
import Device from "./model/device";
import Group from "./model/group";
import data from "./util/data";
import logger from "./util/logger";
import * as settings from "./util/settings";
import utils from "./util/utils";
const entityIDRegex = /^(.+?)(?:\/([^/]+))?$/;
export default class Zigbee {
private herdsman: Controller;
#herdsman!: Controller;
private eventBus: EventBus;
private groupLookup: {[s: number]: Group} = {};
private deviceLookup: {[s: string]: Device} = {};
private groupLookup = new Map<number /* group ID */, Group>();
private deviceLookup = new Map<string /* IEEE address */, Device>();
private coordinatorIeeeAddr!: string;
constructor(eventBus: EventBus) {
this.eventBus = eventBus;
}
async start(): Promise<'reset' | 'resumed' | 'restored'> {
const infoHerdsman = await utils.getDependencyVersion('zigbee-herdsman');
get zhController() {
return this.#herdsman;
}
async start(abortSignal: AbortSignal): Promise<boolean> {
const infoHerdsman = await utils.getDependencyVersion("zigbee-herdsman");
logger.info(`Starting zigbee-herdsman (${infoHerdsman.version})`);
const panId = settings.get().advanced.pan_id;
const extPanId = settings.get().advanced.ext_pan_id;
const networkKey = settings.get().advanced.network_key;
const herdsmanSettings = {
network: {
panID: settings.get().advanced.pan_id === 'GENERATE' ?
this.generatePanID() : settings.get().advanced.pan_id as number,
extendedPanID: settings.get().advanced.ext_pan_id,
panID: panId === "GENERATE" ? this.generatePanID() : panId,
extendedPanID: extPanId === "GENERATE" ? this.generateExtPanID() : extPanId,
channelList: [settings.get().advanced.channel],
networkKey: settings.get().advanced.network_key === 'GENERATE' ?
this.generateNetworkKey() : settings.get().advanced.network_key as number[],
networkKey: networkKey === "GENERATE" ? this.generateNetworkKey() : networkKey,
},
databasePath: data.joinPath('database.db'),
databaseBackupPath: data.joinPath('database.db.backup'),
backupPath: data.joinPath('coordinator_backup.json'),
databasePath: data.joinPath("database.db"),
databaseBackupPath: data.joinPath("database.db.backup"),
backupPath: data.joinPath("coordinator_backup.json"),
serialPort: {
baudRate: settings.get().serial.baudrate,
rtscts: settings.get().serial.rtscts,
@@ -45,238 +54,358 @@ export default class Zigbee {
concurrent: settings.get().advanced.adapter_concurrent,
delay: settings.get().advanced.adapter_delay,
disableLED: settings.get().serial.disable_led,
transmitPower: settings.get().advanced.transmit_power,
},
acceptJoiningDeviceHandler: this.acceptJoiningDeviceHandler,
};
const herdsmanSettingsLog = objectAssignDeep({}, herdsmanSettings, {network: {networkKey: 'HIDDEN'}});
logger.debug(`Using zigbee-herdsman with settings: '${stringify(herdsmanSettingsLog)}'`);
logger.debug(
() =>
`Using zigbee-herdsman with settings: '${stringify(JSON.stringify(herdsmanSettings).replaceAll(JSON.stringify(herdsmanSettings.network.networkKey), '"HIDDEN"'))}'`,
);
let startResult;
let startResult: StartResult;
try {
this.herdsman = new Controller(herdsmanSettings, logger);
startResult = await this.herdsman.start();
this.#herdsman = new Controller(herdsmanSettings);
startResult = await this.#herdsman.start(abortSignal);
} catch (error) {
logger.error(`Error while starting zigbee-herdsman`);
logger.error("Error while starting zigbee-herdsman");
throw error;
}
this.herdsman.on('adapterDisconnected', () => this.eventBus.emitAdapterDisconnected());
this.herdsman.on('lastSeenChanged', (data: ZHEvents.LastSeenChangedPayload) => {
this.eventBus.emitLastSeenChanged({device: this.resolveDevice(data.device.ieeeAddr), reason: data.reason});
this.coordinatorIeeeAddr = this.#herdsman.getDevicesByType("Coordinator")[0].ieeeAddr;
await this.resolveDevicesDefinitions(false, abortSignal);
if (abortSignal.aborted) {
return false;
}
this.#herdsman.on("adapterDisconnected", () => this.eventBus.emitAdapterDisconnected());
this.#herdsman.on("lastSeenChanged", (data: ZHEvents.LastSeenChangedPayload) => {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
this.eventBus.emitLastSeenChanged({device: this.resolveDevice(data.device.ieeeAddr)!, reason: data.reason});
});
this.herdsman.on('permitJoinChanged', (data: ZHEvents.PermitJoinChangedPayload) => {
this.#herdsman.on("permitJoinChanged", (data: ZHEvents.PermitJoinChangedPayload) => {
this.eventBus.emitPermitJoinChanged(data);
});
this.herdsman.on('deviceNetworkAddressChanged', (data: ZHEvents.DeviceNetworkAddressChangedPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
this.#herdsman.on("deviceNetworkAddressChanged", (data: ZHEvents.DeviceNetworkAddressChangedPayload) => {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
const device = this.resolveDevice(data.device.ieeeAddr)!;
logger.debug(`Device '${device.name}' changed network address`);
this.eventBus.emitDeviceNetworkAddressChanged({device});
});
this.herdsman.on('deviceAnnounce', (data: ZHEvents.DeviceAnnouncePayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
this.#herdsman.on("deviceAnnounce", (data: ZHEvents.DeviceAnnouncePayload) => {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
const device = this.resolveDevice(data.device.ieeeAddr)!;
logger.debug(`Device '${device.name}' announced itself`);
this.eventBus.emitDeviceAnnounce({device});
});
this.herdsman.on('deviceInterview', (data: ZHEvents.DeviceInterviewPayload) => {
this.#herdsman.on("deviceInterview", async (data: ZHEvents.DeviceInterviewPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
/* istanbul ignore if */ if (!device) return; // Prevent potential race
/* v8 ignore next */ if (!device) return; // Prevent potential race
await device.resolveDefinition();
const d = {device, status: data.status};
this.logDeviceInterview(d);
this.eventBus.emitDeviceInterview(d);
});
this.herdsman.on('deviceJoined', (data: ZHEvents.DeviceJoinedPayload) => {
this.#herdsman.on("deviceJoined", async (data: ZHEvents.DeviceJoinedPayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
/* istanbul ignore if */ if (!device) return; // Prevent potential race
/* v8 ignore next */ if (!device) return; // Prevent potential race
await device.resolveDefinition();
logger.info(`Device '${device.name}' joined`);
this.eventBus.emitDeviceJoined({device});
});
this.herdsman.on('deviceLeave', (data: ZHEvents.DeviceLeavePayload) => {
this.#herdsman.on("deviceLeave", (data: ZHEvents.DeviceLeavePayload) => {
const name = settings.getDevice(data.ieeeAddr)?.friendly_name || data.ieeeAddr;
logger.warn(`Device '${name}' left the network`);
this.eventBus.emitDeviceLeave({ieeeAddr: data.ieeeAddr, name});
logger.warning(`Device '${name}' left the network`);
this.eventBus.emitDeviceLeave({ieeeAddr: data.ieeeAddr, name, device: this.deviceLookup.get(data.ieeeAddr)});
});
this.herdsman.on('message', (data: ZHEvents.MessagePayload) => {
const device = this.resolveDevice(data.device.ieeeAddr);
logger.debug(`Received Zigbee message from '${device.name}', type '${data.type}', ` +
`cluster '${data.cluster}', data '${stringify(data.data)}' from endpoint ${data.endpoint.ID}` +
(data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``) +
(device.zh.type === 'Coordinator' ? `, ignoring since it is from coordinator` : ``));
if (device.zh.type === 'Coordinator') return;
this.#herdsman.on("message", async (data: ZHEvents.MessagePayload) => {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
const device = this.resolveDevice(data.device.ieeeAddr)!;
await device.resolveDefinition();
logger.debug(() => {
const groupId = data.groupID !== undefined ? ` with groupID ${data.groupID}` : "";
const fromCoord = device.zh.type === "Coordinator" ? ", ignoring since it is from coordinator" : "";
return `Received Zigbee message from '${device.name}', type '${data.type}', cluster '${data.cluster}', data '${stringify(data.data)}' from endpoint ${data.endpoint.ID}${groupId}${fromCoord}`;
});
if (device.zh.type === "Coordinator") return;
this.eventBus.emitDeviceMessage({...data, device});
});
logger.info(`zigbee-herdsman started (${startResult})`);
logger.info(`Coordinator firmware version: '${stringify(await this.getCoordinatorVersion())}'`);
logger.debug(`Zigbee network parameters: ${stringify(await this.herdsman.getNetworkParameters())}`);
logger.debug(`Zigbee network parameters: ${stringify(await this.#herdsman.getNetworkParameters())}`);
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;
const blocklist = settings.get().blocklist;
const remove = async (device: Device): Promise<void> => {
try {
await device.zh.removeFromNetwork();
} catch (error) {
logger.error(`Failed to remove '${device.ieeeAddr}' (${error.message})`);
}
};
if (passlist.length > 0) {
if (abortSignal.aborted) {
return false;
}
const remove = async (device: Device): Promise<void> => {
try {
await device.zh.removeFromNetwork();
} catch (error) {
logger.error(`Failed to remove '${device.ieeeAddr}' (${(error as Error).message})`);
}
};
// If a passlist is used, all other device will be removed from the network.
const passlist = settings.get().passlist;
if (passlist.length > 0) {
for (const device of this.devicesIterator(utils.deviceNotCoordinator)) {
if (!passlist.includes(device.ieeeAddr)) {
logger.warn(`Device which is not on passlist connected (${device.ieeeAddr}), removing...`);
logger.warning(`Device not on passlist currently on the network (${device.ieeeAddr}), removing...`);
await remove(device);
if (abortSignal.aborted) {
return false;
}
}
}
} else {
for (const ieee of settings.get().blocklist) {
const device = this.resolveDevice(ieee);
if (device) {
if (device.zh.type === "Coordinator") {
continue;
}
logger.warning(`Device on blocklist currently on the network (${device.ieeeAddr}), removing...`);
await remove(device);
if (abortSignal.aborted) {
return false;
}
} else {
logger.debug(`Ignoring blocklist device ${ieee}, not currently on the network`);
}
} else if (blocklist.includes(device.ieeeAddr)) {
logger.warn(`Device on blocklist is connected (${device.ieeeAddr}), removing...`);
await remove(device);
}
}
// Check if we have to set a 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}'`);
}
return startResult;
return true;
}
private logDeviceInterview(data: eventdata.DeviceInterview): void {
const name = data.device.name;
if (data.status === 'successful') {
if (data.status === "successful") {
logger.info(`Successfully interviewed '${name}', device has successfully been paired`);
if (data.device.definition) {
const {vendor, description, model} = data.device.definition;
if (data.device.isSupported) {
// biome-ignore lint/style/noNonNullAssertion: valid from `isSupported`
const {vendor, description, model} = data.device.definition!;
logger.info(`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`);
} else {
logger.warn(`Device '${name}' with Zigbee model '${data.device.zh.modelID}' and manufacturer name ` +
`'${data.device.zh.manufacturerName}' is NOT supported, ` +
// eslint-disable-next-line max-len
`please follow https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html`);
logger.warning(
`Device '${name}' with Zigbee model '${data.device.zh.modelID}' and manufacturer name '${data.device.zh.manufacturerName}' is NOT supported, please follow https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html`,
);
}
} else if (data.status === 'failed') {
} else if (data.status === "failed") {
logger.error(`Failed to interview '${name}', device has not successfully been paired`);
} else { // data.status === 'started'
} else {
// data.status === 'started'
logger.info(`Starting interview of '${name}'`);
}
}
private generateNetworkKey(): number[] {
const key = Array.from({length: 16}, () => Math.floor(Math.random() * 255));
settings.set(['advanced', 'network_key'], key);
const key = Array.from({length: 16}, () => randomInt(256));
settings.set(["advanced", "network_key"], key);
return key;
}
private generateExtPanID(): number[] {
const key = Array.from({length: 8}, () => randomInt(256));
settings.set(["advanced", "ext_pan_id"], key);
return key;
}
private generatePanID(): number {
const panID = Math.floor(Math.random() * (0xFFFF - 2)) + 1;
settings.set(['advanced', 'pan_id'], panID);
const panID = randomInt(1, 0xffff - 1);
settings.set(["advanced", "pan_id"], panID);
return panID;
}
async getCoordinatorVersion(): Promise<zh.CoordinatorVersion> {
return this.herdsman.getCoordinatorVersion();
return await this.#herdsman.getCoordinatorVersion();
}
isStopping(): boolean {
return this.herdsman.isStopping();
return this.#herdsman.isStopping();
}
async backup(): Promise<void> {
return this.herdsman.backup();
return await this.#herdsman.backup();
}
async coordinatorCheck(): Promise<{missingRouters: Device[]}> {
const check = await this.#herdsman.coordinatorCheck();
// biome-ignore lint/style/noNonNullAssertion: assumed valid
return {missingRouters: check.missingRouters.map((d) => this.resolveDevice(d.ieeeAddr)!)};
}
async getNetworkParameters(): Promise<zh.NetworkParameters> {
return this.herdsman.getNetworkParameters();
}
async reset(type: 'soft' | 'hard'): Promise<void> {
await this.herdsman.reset(type);
return await this.#herdsman.getNetworkParameters();
}
async stop(): Promise<void> {
logger.info('Stopping zigbee-herdsman...');
await this.herdsman.stop();
logger.info('Stopped zigbee-herdsman');
logger.info("Stopping zigbee-herdsman...");
await this.#herdsman?.stop(); // could be undefined if this is called during startup (abort)
logger.info("Stopped zigbee-herdsman");
}
getPermitJoin(): boolean {
return this.herdsman.getPermitJoin();
return this.#herdsman.getPermitJoin();
}
getPermitJoinTimeout(): number {
return this.herdsman.getPermitJoinTimeout();
getPermitJoinEnd(): number | undefined {
return this.#herdsman.getPermitJoinEnd();
}
async permitJoin(permit: boolean, device?: Device, time: number=undefined): Promise<void> {
if (permit) {
logger.info(`Zigbee: allowing new devices to join${device ? ` via ${device.name}` : ''}.`);
async permitJoin(time: number, device?: Device): Promise<void> {
if (time > 0) {
logger.info(`Zigbee: allowing new devices to join${device ? ` via ${device.name}` : ""}.`);
} else {
logger.info('Zigbee: disabling joining new devices.');
logger.info("Zigbee: disabling joining new devices.");
}
if (device && permit) {
await this.herdsman.permitJoin(permit, device.zh, time);
} else {
await this.herdsman.permitJoin(permit, undefined, time);
await this.#herdsman.permitJoin(time, device?.zh);
}
async resolveDevicesDefinitions(ignoreCache = false, abortSignal: AbortSignal | undefined = undefined): Promise<void> {
for (const device of this.devicesIterator(utils.deviceNotCoordinator)) {
await device.resolveDefinition(ignoreCache);
if (abortSignal?.aborted) {
return;
}
}
}
@bind private resolveDevice(ieeeAddr: string): Device {
if (!this.deviceLookup[ieeeAddr]) {
const device = this.herdsman.getDeviceByIeeeAddr(ieeeAddr);
device && (this.deviceLookup[ieeeAddr] = new Device(device));
@bind private resolveDevice(ieeeAddr: string): Device | undefined {
if (!this.deviceLookup.has(ieeeAddr)) {
const device = this.#herdsman.getDeviceByIeeeAddr(ieeeAddr);
if (device) {
this.deviceLookup.set(ieeeAddr, new Device(device));
}
}
const device = this.deviceLookup[ieeeAddr];
const device = this.deviceLookup.get(ieeeAddr);
if (device && !device.zh.isDeleted) {
device.ensureInSettings();
return device;
}
}
private resolveGroup(groupID: number): Group {
const group = this.herdsman.getGroupByID(Number(groupID));
if (group && !this.groupLookup[groupID]) {
this.groupLookup[groupID] = new Group(group, this.resolveDevice);
}
private resolveGroup(groupID: number): Group | undefined {
if (!this.groupLookup.has(groupID)) {
const group = this.#herdsman.getGroupByID(groupID);
return this.groupLookup[groupID];
}
resolveEntity(key: string | number | zh.Device): Device | Group {
if (typeof key === 'object') {
return this.resolveDevice(key.ieeeAddr);
} else if (typeof key === 'string' && key.toLowerCase() === 'coordinator') {
return this.resolveDevice(this.herdsman.getDevicesByType('Coordinator')[0].ieeeAddr);
} else {
const settingsDevice = settings.getDevice(key.toString());
if (settingsDevice) return this.resolveDevice(settingsDevice.ID);
const groupSettings = settings.getGroup(key);
if (groupSettings) {
const group = this.resolveGroup(groupSettings.ID);
// If group does not exist, create it (since it's already in configuration.yaml)
return group ? group : this.createGroup(groupSettings.ID);
if (group) {
this.groupLookup.set(groupID, new Group(group, this.resolveDevice));
}
}
const group = this.groupLookup.get(groupID);
if (group) {
group.ensureInSettings();
return group;
}
}
resolveEntity(key: string | number | zh.Device): Device | Group | undefined {
if (typeof key === "object") {
return this.resolveDevice(key.ieeeAddr);
}
if (typeof key === "string" && (key.toLowerCase() === "coordinator" || key === this.coordinatorIeeeAddr)) {
return this.resolveDevice(this.coordinatorIeeeAddr);
}
const settingsDevice = settings.getDevice(key.toString());
if (settingsDevice) {
return this.resolveDevice(settingsDevice.ID);
}
const groupSettings = settings.getGroup(key);
if (groupSettings) {
const group = this.resolveGroup(groupSettings.ID);
// If group does not exist, create it (since it's already in configuration.yaml)
return group ? group : this.createGroup(groupSettings.ID);
}
}
resolveEntityAndEndpoint(id: string): {ID: string; entity: Device | Group | undefined; endpointID?: string; endpoint?: zh.Endpoint} {
// This function matches the following entity formats:
// device_name (just device name)
// device_name/ep_name (device name and endpoint numeric ID or name)
// device/name (device name with slashes)
// device/name/ep_name (device name with slashes, and endpoint numeric ID or name)
// The function tries to find an exact match first
let entityName = id;
let deviceOrGroup = this.resolveEntity(id);
let endpointNameOrID: string | undefined;
// If exact match did not happen, try matching a device_name/endpoint pattern
if (!deviceOrGroup) {
// First split the input token by the latest slash
const match = id.match(entityIDRegex);
if (match) {
// Get the resulting IDs from the match
entityName = match[1];
deviceOrGroup = this.resolveEntity(entityName);
endpointNameOrID = match[2];
}
}
// If the function returns non-null endpoint name, but the endpoint field is null, then
// it means that endpoint was not matched because there is no such endpoint on the device
// (or the entity is a group)
const endpoint = deviceOrGroup?.isDevice() ? deviceOrGroup.endpoint(endpointNameOrID) : undefined;
return {ID: entityName, entity: deviceOrGroup, endpointID: endpointNameOrID, endpoint};
}
firstCoordinatorEndpoint(): zh.Endpoint {
return this.herdsman.getDevicesByType('Coordinator')[0].endpoints[0];
return this.#herdsman.getDevicesByType("Coordinator")[0].endpoints[0];
}
groups(): Group[] {
return this.herdsman.getGroups().map((g) => this.resolveGroup(g.groupID));
*devicesAndGroupsIterator(
devicePredicate?: (value: zh.Device) => boolean,
groupPredicate?: (value: zh.Group) => boolean,
): Generator<Device | Group> {
for (const device of this.#herdsman.getDevicesIterator(devicePredicate)) {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
yield this.resolveDevice(device.ieeeAddr)!;
}
for (const group of this.#herdsman.getGroupsIterator(groupPredicate)) {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
yield this.resolveGroup(group.groupID)!;
}
}
devices(includeCoordinator=true): Device[] {
return this.herdsman.getDevices()
.map((d) => this.resolveDevice(d.ieeeAddr))
.filter((d) => includeCoordinator || d.zh.type !== 'Coordinator');
*groupsIterator(predicate?: (value: zh.Group) => boolean): Generator<Group> {
for (const group of this.#herdsman.getGroupsIterator(predicate)) {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
yield this.resolveGroup(group.groupID)!;
}
}
*devicesIterator(predicate?: (value: zh.Device) => boolean): Generator<Device> {
for (const device of this.#herdsman.getDevicesIterator(predicate)) {
// biome-ignore lint/style/noNonNullAssertion: assumed valid
yield this.resolveDevice(device.ieeeAddr)!;
}
}
// biome-ignore lint/suspicious/useAwait: API
@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;
@@ -285,54 +414,60 @@ export default class Zigbee {
if (passlist.includes(ieeeAddr)) {
logger.info(`Accepting joining device which is on passlist '${ieeeAddr}'`);
return true;
} else {
logger.info(`Rejecting joining not in passlist device '${ieeeAddr}'`);
return false;
}
} else if (blocklist.length > 0) {
logger.info(`Rejecting joining not in passlist device '${ieeeAddr}'`);
return false;
}
if (blocklist.length > 0) {
if (blocklist.includes(ieeeAddr)) {
logger.info(`Rejecting joining device which is on blocklist '${ieeeAddr}'`);
return false;
} else {
logger.info(`Accepting joining not in blocklist device '${ieeeAddr}'`);
return true;
}
} else {
return true;
logger.info(`Accepting joining not in blocklist device '${ieeeAddr}'`);
}
return true;
}
async touchlinkFactoryResetFirst(): Promise<boolean> {
return this.herdsman.touchlinkFactoryResetFirst();
return await this.#herdsman.touchlink.factoryResetFirst();
}
async touchlinkFactoryReset(ieeeAddr: string, channel: number): Promise<boolean> {
return this.herdsman.touchlinkFactoryReset(ieeeAddr, channel);
return await this.#herdsman.touchlink.factoryReset(ieeeAddr, channel);
}
async addInstallCode(installCode: string): Promise<void> {
await this.herdsman.addInstallCode(installCode);
await this.#herdsman.addInstallCode(installCode);
}
async touchlinkIdentify(ieeeAddr: string, channel: number): Promise<void> {
await this.herdsman.touchlinkIdentify(ieeeAddr, channel);
await this.#herdsman.touchlink.identify(ieeeAddr, channel);
}
async touchlinkScan(): Promise<{ieeeAddr: string, channel: number}[]> {
return this.herdsman.touchlinkScan();
async touchlinkScan(): Promise<{ieeeAddr: string; channel: number}[]> {
return await this.#herdsman.touchlink.scan();
}
createGroup(ID: number): Group {
this.herdsman.createGroup(ID);
return this.resolveGroup(ID);
createGroup(id: number): Group {
this.#herdsman.createGroup(id);
// biome-ignore lint/style/noNonNullAssertion: just created
return this.resolveGroup(id)!;
}
deviceByNetworkAddress(networkAddress: number): Device {
const device = this.herdsman.getDeviceByNetworkAddress(networkAddress);
deviceByNetworkAddress(networkAddress: number): Device | undefined {
const device = this.#herdsman.getDeviceByNetworkAddress(networkAddress);
return device && this.resolveDevice(device.ieeeAddr);
}
groupByID(ID: number): Group {
return this.resolveGroup(ID);
groupByID(id: number): Group | undefined {
return this.resolveGroup(id);
}
removeGroupFromLookup(id: number): void {
this.groupLookup.delete(id);
}
}
-16594
View File
File diff suppressed because it is too large Load Diff
+99 -104
View File
@@ -1,107 +1,102 @@
{
"name": "zigbee2mqtt",
"version": "1.31.2",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
},
"engines": {
"node": "^14 || ^16 || ^18 || ^19 || ^20"
},
"keywords": [
"xiaomi",
"tradfri",
"hue",
"bridge",
"zigbee",
"mqtt",
"cc2531"
],
"scripts": {
"build": "tsc && node index.js writehash",
"build-watch": "tsc --watch",
"eslint": "eslint lib/ --max-warnings=0",
"start": "node index.js",
"test-with-coverage": "jest test --coverage",
"test": "jest test",
"test-watch": "jest test --watch"
},
"author": "Koen Kanters",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/Koenkk/zigbee2mqtt/issues"
},
"homepage": "https://koenkk.github.io/zigbee2mqtt",
"dependencies": {
"ajv": "^8.12.0",
"bind-decorator": "^1.0.11",
"connect-gzip-static": "2.1.1",
"debounce": "^1.2.1",
"deep-object-diff": "^1.1.9",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.2.0",
"git-last-commit": "^1.0.1",
"humanize-duration": "^3.28.0",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"jszip": "^3.10.1",
"mkdir-recursive": "^0.4.0",
"moment": "^2.29.4",
"mqtt": "4.3.7",
"object-assign-deep": "^0.4.0",
"rimraf": "^5.0.1",
"semver": "^7.5.1",
"source-map-support": "^0.5.21",
"uri-js": "^4.4.1",
"winston": "^3.9.0",
"winston-syslog": "^2.7.0",
"winston-transport": "^4.5.0",
"ws": "^8.13.0",
"zigbee-herdsman": "0.14.117",
"zigbee-herdsman-converters": "15.13.1-hotfix.1",
"zigbee2mqtt-frontend": "0.6.129"
},
"devDependencies": {
"@babel/core": "^7.22.1",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.22.3",
"@babel/preset-env": "^7.22.2",
"@babel/preset-typescript": "^7.21.5",
"@types/debounce": "^1.2.1",
"@types/finalhandler": "^1.2.0",
"@types/humanize-duration": "^3.27.1",
"@types/jest": "^29.5.1",
"@types/js-yaml": "^4.0.5",
"@types/object-assign-deep": "^0.4.0",
"@types/rimraf": "^3.0.2",
"@types/ws": "^8.5.4",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.7",
"babel-jest": "^29.5.0",
"eslint": "^8.41.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-jest": "^27.2.1",
"jest": "^29.5.0",
"tmp": "^0.2.1",
"typescript": "^5.0.4"
},
"jest": {
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
"name": "zigbee2mqtt",
"version": "2.12.0",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"types": "dist/types/api.d.ts",
"packageManager": "pnpm@10.18.3",
"repository": {
"type": "git",
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
},
"collectCoverageFrom": [
"lib/**/*.js",
"lib/**/*.ts"
]
},
"bin": {
"zigbee2mqtt": "cli.js"
}
"engines": {
"node": "^20.15.0 || ^22.2.0 || ^24"
},
"keywords": [
"xiaomi",
"tradfri",
"hue",
"bridge",
"zigbee",
"mqtt",
"cc2531"
],
"scripts": {
"build": "tsc && node index.js writehash",
"build:types": "pnpm run clean && tsc --project tsconfig.types.json && cp lib/util/settings.schema.json dist/util",
"build:watch": "tsc --watch",
"check": "biome check --error-on-warnings",
"check:w": "biome check --write",
"start": "node index.js",
"test": "vitest run --config ./test/vitest.config.mts",
"test:coverage": "vitest run --config ./test/vitest.config.mts --coverage",
"test:watch": "vitest watch --config ./test/vitest.config.mts",
"bench": "vitest bench --run --config ./test/vitest.config.mts",
"prepack": "pnpm run clean && pnpm run build",
"clean": "rimraf coverage dist tsconfig.tsbuildinfo"
},
"author": "Koen Kanters",
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/Koenkk/zigbee2mqtt/issues"
},
"homepage": "https://koenkk.github.io/zigbee2mqtt",
"dependencies": {
"ajv": "^8.20.0",
"bind-decorator": "^1.0.11",
"debounce": "^3.0.0",
"express-static-gzip": "^3.0.1",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^2.1.1",
"humanize-duration": "^3.33.2",
"js-yaml": "^4.2.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"jszip": "^3.10.1",
"mqtt": "^5.15.1",
"object-assign-deep": "^0.4.0",
"rimraf": "^6.1.3",
"semver": "^7.8.3",
"source-map-support": "^0.5.21",
"throttleit": "^2.1.0",
"winston": "^3.19.0",
"winston-syslog": "^2.7.1",
"winston-transport": "^4.9.0",
"ws": "^8.21.0",
"zigbee-herdsman": "10.4.0",
"zigbee-herdsman-converters": "26.63.0",
"zigbee2mqtt-frontend": "0.9.21",
"zigbee2mqtt-windfront": "2.12.0"
},
"devDependencies": {
"@biomejs/biome": "^2.4.16",
"@types/finalhandler": "^1.2.3",
"@types/humanize-duration": "^3.27.4",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.13.1",
"@types/object-assign-deep": "^0.4.3",
"@types/readable-stream": "4.0.23",
"@types/serve-static": "^2.2.0",
"@types/ws": "8.18.1",
"@vitest/coverage-v8": "^3.1.1",
"tmp": "^0.2.7",
"typescript": "^6.0.3",
"vitest": "^3.1.1"
},
"pnpm": {
"overrides": {
"zigbee-herdsman": "$zigbee-herdsman"
},
"onlyBuiltDependencies": [
"@biomejs/biome",
"@serialport/bindings-cpp",
"esbuild",
"unix-dgram"
]
},
"bin": {
"zigbee2mqtt": "cli.js"
},
"optionalDependencies": {
"unix-dgram": "^2.0.7"
}
}

Some files were not shown because too many files have changed in this diff Show More