Compare commits

...
116 Commits
Author SHA1 Message Date
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
35 changed files with 1727 additions and 1088 deletions
+9 -9
View File
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v6
if: ((github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push') == false
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
@@ -63,14 +63,14 @@ jobs:
- 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@v3
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@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: koenkk
@@ -78,20 +78,20 @@ jobs:
- name: Docker setup - QEMU
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
uses: docker/setup-qemu-action@v3
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@v3
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@v6
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile
@@ -126,7 +126,7 @@ jobs:
- name: release - Docker meta
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
id: meta
with:
images: |
@@ -139,7 +139,7 @@ jobs:
- name: release - Docker build and push
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: docker/Dockerfile
@@ -222,7 +222,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
+1 -1
View File
@@ -12,4 +12,4 @@ jobs:
- name: 'Checkout repository'
uses: actions/checkout@v6
- name: 'Dependency review'
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@v5
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Delete untagged images
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
path: zhc
- name: Comment on new device support/external converter issue
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const {newDeviceSupport} = await import("${{ github.workspace }}/z2m/scripts/issueBot.mjs")
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
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@v4
- uses: pnpm/action-setup@v6
with:
version: 9
@@ -24,7 +24,7 @@ jobs:
with:
node-version: 24
- uses: googleapis/release-please-action@v4
- uses: googleapis/release-please-action@v5
id: release
with:
target-branch: dev
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
with:
ref: dev
token: ${{ secrets.GH_TOKEN }}
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: 24
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "2.9.1"
".": "2.11.0"
}
+126
View File
@@ -1,5 +1,131 @@
# Changelog
## [2.11.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.10.1...2.11.0) (2026-06-01)
### Features
* allow to disable external JS extensions ([#31826](https://github.com/Koenkk/zigbee2mqtt/issues/31826)) ([15fd9b3](https://github.com/Koenkk/zigbee2mqtt/commit/15fd9b371e30ed352cf2de2d241a0d0f7fafa9d1))
### Bug Fixes
* **deps-dev:** bump tmp from 0.2.5 to 0.2.6 ([#32122](https://github.com/Koenkk/zigbee2mqtt/issues/32122)) ([7b9407d](https://github.com/Koenkk/zigbee2mqtt/commit/7b9407d020b9db1093b360f5f6e217bbab7afb63))
* **deps:** bump actions/dependency-review-action from 4 to 5 ([#31980](https://github.com/Koenkk/zigbee2mqtt/issues/31980)) ([4d9e950](https://github.com/Koenkk/zigbee2mqtt/commit/4d9e9504ef9c857a1985c79d3a6799552abc3aea))
* **deps:** bump brace-expansion from 2.0.2 to 5.0.6 ([#32038](https://github.com/Koenkk/zigbee2mqtt/issues/32038)) ([897aa8f](https://github.com/Koenkk/zigbee2mqtt/commit/897aa8fb7c82dbc9c906f986477c4c1673bb3199))
* **deps:** bump pnpm/action-setup from 5 to 6 ([#31698](https://github.com/Koenkk/zigbee2mqtt/issues/31698)) ([1a1c094](https://github.com/Koenkk/zigbee2mqtt/commit/1a1c09449c9d077ea06048d7e2ecea90ff850c9b))
* **deps:** bump ws from 8.20.0 to 8.20.1 ([#32041](https://github.com/Koenkk/zigbee2mqtt/issues/32041)) ([bf964cc](https://github.com/Koenkk/zigbee2mqtt/commit/bf964cc6d976d70e687844e5a09c4577763dc0d3))
* Fix default value of "optimistic" group setting ([#32054](https://github.com/Koenkk/zigbee2mqtt/issues/32054)) ([b31e8c2](https://github.com/Koenkk/zigbee2mqtt/commit/b31e8c286e7a77517a88e3e7eb2247e9a0fd2cc1))
* **ignore:** bump the minor-patch group with 2 updates ([#32137](https://github.com/Koenkk/zigbee2mqtt/issues/32137)) ([ac8c45f](https://github.com/Koenkk/zigbee2mqtt/commit/ac8c45f67f0a4aae2688e55b98dabdc23f73a259))
* **ignore:** bump the minor-patch group with 3 updates ([#31982](https://github.com/Koenkk/zigbee2mqtt/issues/31982)) ([9426c3e](https://github.com/Koenkk/zigbee2mqtt/commit/9426c3ea118de9e60326e8dc257a0c2474380f8c))
* **ignore:** bump the minor-patch group with 3 updates ([#32106](https://github.com/Koenkk/zigbee2mqtt/issues/32106)) ([e5f70d5](https://github.com/Koenkk/zigbee2mqtt/commit/e5f70d5ba82923f9f37fbda79bed08d581706129))
* **ignore:** update zigbee-herdsman to 10.0.8 ([#31945](https://github.com/Koenkk/zigbee2mqtt/issues/31945)) ([04a4365](https://github.com/Koenkk/zigbee2mqtt/commit/04a43652d6e131694e29e06ca646eeac37ed3c7d))
* **ignore:** update zigbee-herdsman to 10.1.0 ([#32027](https://github.com/Koenkk/zigbee2mqtt/issues/32027)) ([36c4db7](https://github.com/Koenkk/zigbee2mqtt/commit/36c4db7f8f4e3438f6ebb82cd6ecadf6c54e231e))
* **ignore:** update zigbee-herdsman-converters to 26.47.0 ([#31946](https://github.com/Koenkk/zigbee2mqtt/issues/31946)) ([7a05464](https://github.com/Koenkk/zigbee2mqtt/commit/7a05464f0355380ee855415bc270632263e3f5d3))
* **ignore:** update zigbee-herdsman-converters to 26.48.0 ([#31959](https://github.com/Koenkk/zigbee2mqtt/issues/31959)) ([502030c](https://github.com/Koenkk/zigbee2mqtt/commit/502030c0168c8ba6eaf7495a9014489f2f7a7c50))
* **ignore:** update zigbee-herdsman-converters to 26.49.0 ([#31976](https://github.com/Koenkk/zigbee2mqtt/issues/31976)) ([32499d1](https://github.com/Koenkk/zigbee2mqtt/commit/32499d14d5f0c9eada160d596d6be92da6c4af95))
* **ignore:** update zigbee-herdsman-converters to 26.50.0 ([#31988](https://github.com/Koenkk/zigbee2mqtt/issues/31988)) ([6ea21c3](https://github.com/Koenkk/zigbee2mqtt/commit/6ea21c383b4801f28e30b022194e71375db94b17))
* **ignore:** update zigbee-herdsman-converters to 26.51.0 ([#31999](https://github.com/Koenkk/zigbee2mqtt/issues/31999)) ([744e4a6](https://github.com/Koenkk/zigbee2mqtt/commit/744e4a6d5c4dd179e7c3d1a2b4fbcdc820195547))
* **ignore:** update zigbee-herdsman-converters to 26.51.1 ([#32016](https://github.com/Koenkk/zigbee2mqtt/issues/32016)) ([5458e50](https://github.com/Koenkk/zigbee2mqtt/commit/5458e5084def411aaad9af08e415acb6bc16d181))
* **ignore:** update zigbee-herdsman-converters to 26.52.0 ([#32040](https://github.com/Koenkk/zigbee2mqtt/issues/32040)) ([1e114de](https://github.com/Koenkk/zigbee2mqtt/commit/1e114dee70b8079eda31d4d0cc52fa073a772798))
* **ignore:** update zigbee-herdsman-converters to 26.53.0 ([#32051](https://github.com/Koenkk/zigbee2mqtt/issues/32051)) ([52201c9](https://github.com/Koenkk/zigbee2mqtt/commit/52201c96bf897af9bcad30435575923a0452b6dd))
* **ignore:** update zigbee-herdsman-converters to 26.54.0 ([#32065](https://github.com/Koenkk/zigbee2mqtt/issues/32065)) ([2c5b536](https://github.com/Koenkk/zigbee2mqtt/commit/2c5b53613448ae4c6548f874a36acc1226d53ec3))
* **ignore:** update zigbee-herdsman-converters to 26.55.0 ([#32068](https://github.com/Koenkk/zigbee2mqtt/issues/32068)) ([7075b78](https://github.com/Koenkk/zigbee2mqtt/commit/7075b78a462a0d1f76cf91cc78601d90edfd8b4e))
* **ignore:** update zigbee-herdsman-converters to 26.56.0 ([#32087](https://github.com/Koenkk/zigbee2mqtt/issues/32087)) ([56aab4e](https://github.com/Koenkk/zigbee2mqtt/commit/56aab4e59c4ea7bdbd93a1ed6632171994d8ed56))
* **ignore:** update zigbee-herdsman-converters to 26.57.0 ([#32113](https://github.com/Koenkk/zigbee2mqtt/issues/32113)) ([7a1c2d5](https://github.com/Koenkk/zigbee2mqtt/commit/7a1c2d52f0b0699854f3c8c9375761cb0b22ac55))
* **ignore:** update zigbee-herdsman-converters to 26.58.0 ([#32126](https://github.com/Koenkk/zigbee2mqtt/issues/32126)) ([e2b4911](https://github.com/Koenkk/zigbee2mqtt/commit/e2b49113c7932c4cd8ef7b23a27e3cd5d4e7150d))
* **ignore:** update zigbee-herdsman-converters to 26.59.1 ([#32136](https://github.com/Koenkk/zigbee2mqtt/issues/32136)) ([a8364ad](https://github.com/Koenkk/zigbee2mqtt/commit/a8364ad67b0dcbde93b1d6445c269a821321e9b3))
* **ignore:** update zigbee-herdsman-converters to 26.60.0 ([#32150](https://github.com/Koenkk/zigbee2mqtt/issues/32150)) ([c6e13cc](https://github.com/Koenkk/zigbee2mqtt/commit/c6e13ccb45666f9604e8c9c9655bbe7ea682712d))
* **ignore:** update zigbee-herdsman-converters to 26.61.1 ([#32156](https://github.com/Koenkk/zigbee2mqtt/issues/32156)) ([58feb78](https://github.com/Koenkk/zigbee2mqtt/commit/58feb783626fd2429bea0f8a0db37bdc107c431b))
* Prevent invalid external JS file name on save ([#32037](https://github.com/Koenkk/zigbee2mqtt/issues/32037)) ([bbcbed1](https://github.com/Koenkk/zigbee2mqtt/commit/bbcbed12d53408eaa8ceec110c7071fff8e158e4))
* Use Jinja-safe property access in HA discovery templates ([#31930](https://github.com/Koenkk/zigbee2mqtt/issues/31930)) ([c4fd415](https://github.com/Koenkk/zigbee2mqtt/commit/c4fd415cbe7b01719679b1a96d2b6e07b0793125))
## [2.10.1](https://github.com/Koenkk/zigbee2mqtt/compare/2.10.0...2.10.1) (2026-05-07)
### Bug Fixes
* **ignore:** bump @biomejs/biome from 2.4.13 to 2.4.14 in the minor-patch group across 1 directory ([#31881](https://github.com/Koenkk/zigbee2mqtt/issues/31881)) ([55f12ff](https://github.com/Koenkk/zigbee2mqtt/commit/55f12ff52063d576be0fe82891678c52181f3b8e))
* **ignore:** bump express-static-gzip from 3.0.0 to 3.0.1 in the minor-patch group ([#31924](https://github.com/Koenkk/zigbee2mqtt/issues/31924)) ([24e9027](https://github.com/Koenkk/zigbee2mqtt/commit/24e90273c9e712e122887e74ccf8208a8b42e762))
* **ignore:** update zigbee-herdsman-converters to 26.43.0 ([#31878](https://github.com/Koenkk/zigbee2mqtt/issues/31878)) ([19eb05a](https://github.com/Koenkk/zigbee2mqtt/commit/19eb05a0e023c7e2e8639c1dcda18399e96ae74d))
* **ignore:** update zigbee-herdsman-converters to 26.44.0 ([#31900](https://github.com/Koenkk/zigbee2mqtt/issues/31900)) ([845bdb7](https://github.com/Koenkk/zigbee2mqtt/commit/845bdb7dba5fe0f5e67e1b92c68872da0f3abf6a))
* **ignore:** update zigbee-herdsman-converters to 26.45.0 ([#31925](https://github.com/Koenkk/zigbee2mqtt/issues/31925)) ([8727abd](https://github.com/Koenkk/zigbee2mqtt/commit/8727abdefc9114a5e2e1036d764271c90065e0a1))
* **ignore:** update zigbee-herdsman-converters to 26.46.0 ([#31937](https://github.com/Koenkk/zigbee2mqtt/issues/31937)) ([cc84566](https://github.com/Koenkk/zigbee2mqtt/commit/cc8456617a4a389e7297e380901bfd6bb0223b32))
* Replace deprecated `url.parse` ([#31845](https://github.com/Koenkk/zigbee2mqtt/issues/31845)) ([9f7ea9b](https://github.com/Koenkk/zigbee2mqtt/commit/9f7ea9b7c79db7a781b431fbe43568e15647f8e5))
## [2.10.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.9.2...2.10.0) (2026-05-01)
### Features
* Home Assistant: add group entities in discovery config ([#31663](https://github.com/Koenkk/zigbee2mqtt/issues/31663)) ([0419726](https://github.com/Koenkk/zigbee2mqtt/commit/041972669a6a8108e8fee1a0fffb555292371285))
### Bug Fixes
* Clarify units of pause_on_backoff_gt ([#31668](https://github.com/Koenkk/zigbee2mqtt/issues/31668)) ([4a63e65](https://github.com/Koenkk/zigbee2mqtt/commit/4a63e65981b6bbd746a730e7ca2291aba7a6c975))
* **deps:** bump googleapis/release-please-action from 4 to 5 ([#31805](https://github.com/Koenkk/zigbee2mqtt/issues/31805)) ([4e527bb](https://github.com/Koenkk/zigbee2mqtt/commit/4e527bbce8612c85fd636a1c493059af9793991f))
* **ignore:** bump @biomejs/biome from 2.4.10 to 2.4.11 in the minor-patch group ([#31699](https://github.com/Koenkk/zigbee2mqtt/issues/31699)) ([cdd7357](https://github.com/Koenkk/zigbee2mqtt/commit/cdd73575227527ae7eca5732d96687a30cea8538))
* **ignore:** bump @types/node from 24.12.0 to 24.12.2 in the minor-patch group across 1 directory ([#31628](https://github.com/Koenkk/zigbee2mqtt/issues/31628)) ([e6dd5c7](https://github.com/Koenkk/zigbee2mqtt/commit/e6dd5c73bc2d76b32c45ef19633626dbcdde8379))
* **ignore:** bump the minor-patch group with 2 updates ([#31758](https://github.com/Koenkk/zigbee2mqtt/issues/31758)) ([7ba232e](https://github.com/Koenkk/zigbee2mqtt/commit/7ba232e27d973119530119ca136aaab3e8e22ab6))
* **ignore:** bump the minor-patch group with 3 updates ([#31806](https://github.com/Koenkk/zigbee2mqtt/issues/31806)) ([b3983b3](https://github.com/Koenkk/zigbee2mqtt/commit/b3983b35de0b6242d8e4d01f0d1d1e535e2d0b60))
* **ignore:** bump typescript from 5.9.3 to 6.0.2 ([#31560](https://github.com/Koenkk/zigbee2mqtt/issues/31560)) ([9e27181](https://github.com/Koenkk/zigbee2mqtt/commit/9e27181b64b771250db283b5a4b1da7835470ee6))
* **ignore:** update zigbee-herdsman to 10.0.6 ([#31636](https://github.com/Koenkk/zigbee2mqtt/issues/31636)) ([aef3242](https://github.com/Koenkk/zigbee2mqtt/commit/aef32423195914aa9ef08875d4fe6de36df71f11))
* **ignore:** update zigbee-herdsman to 10.0.7 ([#31673](https://github.com/Koenkk/zigbee2mqtt/issues/31673)) ([a0204ad](https://github.com/Koenkk/zigbee2mqtt/commit/a0204ad3f00ed366e4c41455ae4189bd31870a95))
* **ignore:** update zigbee-herdsman-converters to 26.28.0 ([#31574](https://github.com/Koenkk/zigbee2mqtt/issues/31574)) ([08e4e9e](https://github.com/Koenkk/zigbee2mqtt/commit/08e4e9edeb048a07f89d6283e5bea04274a3fb47))
* **ignore:** update zigbee-herdsman-converters to 26.29.0 ([#31586](https://github.com/Koenkk/zigbee2mqtt/issues/31586)) ([23f9847](https://github.com/Koenkk/zigbee2mqtt/commit/23f9847994da94fa8b38ba823a8e55418b006e7c))
* **ignore:** update zigbee-herdsman-converters to 26.30.0 ([#31608](https://github.com/Koenkk/zigbee2mqtt/issues/31608)) ([6b567c1](https://github.com/Koenkk/zigbee2mqtt/commit/6b567c1286201aa9fef4744f7378798bc852c9ff))
* **ignore:** update zigbee-herdsman-converters to 26.31.0 ([#31637](https://github.com/Koenkk/zigbee2mqtt/issues/31637)) ([a50065c](https://github.com/Koenkk/zigbee2mqtt/commit/a50065cbb40bb149a87046bb83a7602a5587b3b0))
* **ignore:** update zigbee-herdsman-converters to 26.32.0 ([#31653](https://github.com/Koenkk/zigbee2mqtt/issues/31653)) ([4818cc4](https://github.com/Koenkk/zigbee2mqtt/commit/4818cc4163d578990339daf6c5e725bb40a08216))
* **ignore:** update zigbee-herdsman-converters to 26.33.1 ([#31674](https://github.com/Koenkk/zigbee2mqtt/issues/31674)) ([0c9ec72](https://github.com/Koenkk/zigbee2mqtt/commit/0c9ec722db3f53c57870de7f09de9971d69cbda8))
* **ignore:** update zigbee-herdsman-converters to 26.34.0 ([#31702](https://github.com/Koenkk/zigbee2mqtt/issues/31702)) ([55d8d1f](https://github.com/Koenkk/zigbee2mqtt/commit/55d8d1f4d0a5895ce146dbec98630d82adcd4e22))
* **ignore:** update zigbee-herdsman-converters to 26.35.0 ([#31713](https://github.com/Koenkk/zigbee2mqtt/issues/31713)) ([0a7dfce](https://github.com/Koenkk/zigbee2mqtt/commit/0a7dfcef596c3201f9f0cd81960c0e4ad8add0c4))
* **ignore:** update zigbee-herdsman-converters to 26.36.0 ([#31737](https://github.com/Koenkk/zigbee2mqtt/issues/31737)) ([a54775d](https://github.com/Koenkk/zigbee2mqtt/commit/a54775dea10a14b6d2f25ebb3c79a8a9d979e970))
* **ignore:** update zigbee-herdsman-converters to 26.37.0 ([#31749](https://github.com/Koenkk/zigbee2mqtt/issues/31749)) ([78f440b](https://github.com/Koenkk/zigbee2mqtt/commit/78f440bada9a14abb1d84af12fc77b1af7d250ea))
* **ignore:** update zigbee-herdsman-converters to 26.38.0 ([#31768](https://github.com/Koenkk/zigbee2mqtt/issues/31768)) ([305b7bc](https://github.com/Koenkk/zigbee2mqtt/commit/305b7bcafab59250173eddb9c08cb2fed0d966a0))
* **ignore:** update zigbee-herdsman-converters to 26.38.1 ([#31777](https://github.com/Koenkk/zigbee2mqtt/issues/31777)) ([3bb3d56](https://github.com/Koenkk/zigbee2mqtt/commit/3bb3d56c05b92e349d2b117973df6cf9414b8450))
* **ignore:** update zigbee-herdsman-converters to 26.39.1 ([#31783](https://github.com/Koenkk/zigbee2mqtt/issues/31783)) ([e715078](https://github.com/Koenkk/zigbee2mqtt/commit/e71507835b2c0673ec0df488e9d1db39a7b64a05))
* **ignore:** update zigbee-herdsman-converters to 26.40.0 ([#31800](https://github.com/Koenkk/zigbee2mqtt/issues/31800)) ([429c5ae](https://github.com/Koenkk/zigbee2mqtt/commit/429c5aea585da1525347adc13712bc556b4ea2ae))
* **ignore:** update zigbee-herdsman-converters to 26.41.0 ([#31820](https://github.com/Koenkk/zigbee2mqtt/issues/31820)) ([c581623](https://github.com/Koenkk/zigbee2mqtt/commit/c58162344fddc1cdb49027f6dcc9f1f35030bc61))
* **ignore:** update zigbee-herdsman-converters to 26.42.0 ([#31830](https://github.com/Koenkk/zigbee2mqtt/issues/31830)) ([df26459](https://github.com/Koenkk/zigbee2mqtt/commit/df26459b46e67f4f3b1079eb6fa6a439bf1979af))
* Improve transmit power description ([#31735](https://github.com/Koenkk/zigbee2mqtt/issues/31735)) ([5974286](https://github.com/Koenkk/zigbee2mqtt/commit/59742864457a9e29783363ebde8ede0e0aca5ea2))
## [2.9.2](https://github.com/Koenkk/zigbee2mqtt/compare/2.9.1...2.9.2) (2026-03-31)
### Bug Fixes
* Export definition version ([#31270](https://github.com/Koenkk/zigbee2mqtt/issues/31270)) ([9f1eb76](https://github.com/Koenkk/zigbee2mqtt/commit/9f1eb764a62b041a7d9782066a8ffcaa3562eaad))
* Home Assistant: fix entity names for derived weather sensors by removing `device_class` ([#31234](https://github.com/Koenkk/zigbee2mqtt/issues/31234)) ([b7b4303](https://github.com/Koenkk/zigbee2mqtt/commit/b7b43030eb8aaa144dbb5477805638d860efdd38))
* **ignore:** bump @biomejs/biome from 2.4.4 to 2.4.5 in the minor-patch group ([#31237](https://github.com/Koenkk/zigbee2mqtt/issues/31237)) ([aefbb8b](https://github.com/Koenkk/zigbee2mqtt/commit/aefbb8be14ae5d84495278cbe9a6ca9d750553cb))
* **ignore:** bump the minor-patch group with 2 updates ([#31411](https://github.com/Koenkk/zigbee2mqtt/issues/31411)) ([67aa509](https://github.com/Koenkk/zigbee2mqtt/commit/67aa5099df2371eb9ee220be7a8821f0d4f8b38b))
* **ignore:** bump the minor-patch group with 3 updates ([#31295](https://github.com/Koenkk/zigbee2mqtt/issues/31295)) ([b7f1cec](https://github.com/Koenkk/zigbee2mqtt/commit/b7f1cecb49f088b7fc79dcef068034ca1d012c1d))
* **ignore:** bump the minor-patch group with 3 updates ([#31489](https://github.com/Koenkk/zigbee2mqtt/issues/31489)) ([52b063b](https://github.com/Koenkk/zigbee2mqtt/commit/52b063bb32429e14b30319c905e596b7632bd674))
* **ignore:** bump the minor-patch group with 3 updates ([#31559](https://github.com/Koenkk/zigbee2mqtt/issues/31559)) ([73d8ae8](https://github.com/Koenkk/zigbee2mqtt/commit/73d8ae895613af0d77f0a684f7305d688af11146))
* **ignore:** update zigbee-herdsman to 10.0.1 ([#31383](https://github.com/Koenkk/zigbee2mqtt/issues/31383)) ([6fca374](https://github.com/Koenkk/zigbee2mqtt/commit/6fca3744c9715dd4afb7fc777cf783c84bfb91fc))
* **ignore:** update zigbee-herdsman to 10.0.2 ([#31413](https://github.com/Koenkk/zigbee2mqtt/issues/31413)) ([075f87c](https://github.com/Koenkk/zigbee2mqtt/commit/075f87c7822c29c74fc1c4031866aca8c445c9be))
* **ignore:** update zigbee-herdsman to 10.0.3 ([#31450](https://github.com/Koenkk/zigbee2mqtt/issues/31450)) ([6fb9d56](https://github.com/Koenkk/zigbee2mqtt/commit/6fb9d563e69c2db3b31fd0f163e5f30924047e3f))
* **ignore:** update zigbee-herdsman to 10.0.4 ([#31478](https://github.com/Koenkk/zigbee2mqtt/issues/31478)) ([867179c](https://github.com/Koenkk/zigbee2mqtt/commit/867179c430eacf9bd66ef359b9a7435f2299d944))
* **ignore:** update zigbee-herdsman to 10.0.5 ([#31562](https://github.com/Koenkk/zigbee2mqtt/issues/31562)) ([fb3189b](https://github.com/Koenkk/zigbee2mqtt/commit/fb3189b7431cb203092bbe235bd35194e2b44a44))
* **ignore:** update zigbee-herdsman to 9.0.10 ([#31245](https://github.com/Koenkk/zigbee2mqtt/issues/31245)) ([d632382](https://github.com/Koenkk/zigbee2mqtt/commit/d6323825e4ae3168d52a096e7647bc7174d3cdb1))
* **ignore:** update zigbee-herdsman to 9.0.11 ([#31257](https://github.com/Koenkk/zigbee2mqtt/issues/31257)) ([1747618](https://github.com/Koenkk/zigbee2mqtt/commit/1747618afa3948ac9fb8899d94fba8456198e4b5))
* **ignore:** update zigbee-herdsman-converters to 26.13.0 ([#31231](https://github.com/Koenkk/zigbee2mqtt/issues/31231)) ([fc3a196](https://github.com/Koenkk/zigbee2mqtt/commit/fc3a1968f6635d246ad2d68e5c6b79d495fd96bb))
* **ignore:** update zigbee-herdsman-converters to 26.14.0 ([#31246](https://github.com/Koenkk/zigbee2mqtt/issues/31246)) ([284d86e](https://github.com/Koenkk/zigbee2mqtt/commit/284d86e93ba8cd83139d6dd0217519f67c01e565))
* **ignore:** update zigbee-herdsman-converters to 26.16.1 ([#31268](https://github.com/Koenkk/zigbee2mqtt/issues/31268)) ([05946b5](https://github.com/Koenkk/zigbee2mqtt/commit/05946b59552608c6ae8a8b02098552fe611f027a))
* **ignore:** update zigbee-herdsman-converters to 26.17.0 ([#31314](https://github.com/Koenkk/zigbee2mqtt/issues/31314)) ([9143924](https://github.com/Koenkk/zigbee2mqtt/commit/91439244823d9897998a57448321d2e8af9b083f))
* **ignore:** update zigbee-herdsman-converters to 26.18.0 ([#31362](https://github.com/Koenkk/zigbee2mqtt/issues/31362)) ([f60db2d](https://github.com/Koenkk/zigbee2mqtt/commit/f60db2d7a641ab5fccbb3a7b6a0e1e73983559ef))
* **ignore:** update zigbee-herdsman-converters to 26.19.1 ([#31384](https://github.com/Koenkk/zigbee2mqtt/issues/31384)) ([69641d5](https://github.com/Koenkk/zigbee2mqtt/commit/69641d5467ec5e3ddfebb4ff98142d0ff990c814))
* **ignore:** update zigbee-herdsman-converters to 26.20.0 ([#31415](https://github.com/Koenkk/zigbee2mqtt/issues/31415)) ([9f49d0e](https://github.com/Koenkk/zigbee2mqtt/commit/9f49d0ec6738ebd1ea65a3bf1aae553548217f2e))
* **ignore:** update zigbee-herdsman-converters to 26.21.0 ([#31427](https://github.com/Koenkk/zigbee2mqtt/issues/31427)) ([e054d71](https://github.com/Koenkk/zigbee2mqtt/commit/e054d7124bd4dc5bdb18c9de4a6923b06df6b929))
* **ignore:** update zigbee-herdsman-converters to 26.22.0 ([#31451](https://github.com/Koenkk/zigbee2mqtt/issues/31451)) ([64286eb](https://github.com/Koenkk/zigbee2mqtt/commit/64286ebb3054bc15e020aab431c1d3af76e2c914))
* **ignore:** update zigbee-herdsman-converters to 26.23.0 ([#31462](https://github.com/Koenkk/zigbee2mqtt/issues/31462)) ([2e5e362](https://github.com/Koenkk/zigbee2mqtt/commit/2e5e3628188765cddbd4116f41703eab491670df))
* **ignore:** update zigbee-herdsman-converters to 26.24.0 ([#31479](https://github.com/Koenkk/zigbee2mqtt/issues/31479)) ([dde637a](https://github.com/Koenkk/zigbee2mqtt/commit/dde637af2f8449ae24684fa34a32bec106c24913))
* **ignore:** update zigbee-herdsman-converters to 26.25.0 ([#31497](https://github.com/Koenkk/zigbee2mqtt/issues/31497)) ([66985a8](https://github.com/Koenkk/zigbee2mqtt/commit/66985a8ce8d4cefbc4ddacc754e182e0b5953cea))
* **ignore:** update zigbee-herdsman-converters to 26.26.0 ([#31510](https://github.com/Koenkk/zigbee2mqtt/issues/31510)) ([969db9a](https://github.com/Koenkk/zigbee2mqtt/commit/969db9ae85838b955e07a80f5f72f655f6beffcf))
* **ignore:** update zigbee-herdsman-converters to 26.27.0 ([#31563](https://github.com/Koenkk/zigbee2mqtt/issues/31563)) ([62ab814](https://github.com/Koenkk/zigbee2mqtt/commit/62ab814d73b7de957fb8b28679b9dea8ae087133))
* Match new cluster-related typing from ZH ([#31298](https://github.com/Koenkk/zigbee2mqtt/issues/31298)) ([0f781ee](https://github.com/Koenkk/zigbee2mqtt/commit/0f781ee9c03ae502745841f4dd7d6f3775c620d4))
* Reintroduce onboarding improvements ([#31273](https://github.com/Koenkk/zigbee2mqtt/issues/31273)) ([4a83692](https://github.com/Koenkk/zigbee2mqtt/commit/4a8369295c5d6c07ff1ac66f90a35b0e8b915296))
## [2.9.1](https://github.com/Koenkk/zigbee2mqtt/compare/2.9.0...2.9.1) (2026-03-02)
+10 -4
View File
@@ -1,8 +1,14 @@
# Contributing to Zigbee2MQTT
Everybody is invited and welcome to contribute to Zigbee2MQTT. Zigbee2MQTT is written 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 definitions, 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 `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 is also enforced and can be run with `pnpm run eslint`.
- When you want to add support for a new device 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).
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.1/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
+25 -17
View File
@@ -50,7 +50,6 @@ export class Controller {
this.state = new State(this.eventBus, this.zigbee);
this.restartCallback = restartCallback;
this.exitCallback = exitCallback;
// Initialize extensions.
this.extensionArgs = [
this.zigbee,
@@ -62,22 +61,29 @@ export class Controller {
this.restartCallback,
this.addExtension,
];
this.extensions = new Set();
this.extensions = new Set([
new ExtensionExternalConverters(...this.extensionArgs),
new ExtensionOnEvent(...this.extensionArgs),
new ExtensionBridge(...this.extensionArgs),
new ExtensionPublish(...this.extensionArgs),
new ExtensionReceive(...this.extensionArgs),
new ExtensionConfigure(...this.extensionArgs),
new ExtensionNetworkMap(...this.extensionArgs),
new ExtensionGroups(...this.extensionArgs),
new ExtensionBind(...this.extensionArgs),
new ExtensionOTAUpdate(...this.extensionArgs),
new ExtensionExternalExtensions(...this.extensionArgs),
new ExtensionAvailability(...this.extensionArgs),
new ExtensionHealth(...this.extensionArgs),
]);
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> {
@@ -247,10 +253,12 @@ export class Controller {
}
const existingExtension = this.getExtension(name);
if (existingExtension) {
await this.removeExtension(existingExtension);
}
await this.extensions.add(extension);
this.extensions.add(extension);
} else {
switch (name) {
case "Frontend": {
+1
View File
@@ -900,6 +900,7 @@ export default class Bridge extends Extension {
exposes: device.exposes(),
supports_ota: !!device.definition.ota,
options: device.definition.options ?? [],
version: device.definition.version,
icon,
};
+1 -1
View File
@@ -38,7 +38,7 @@ export default class Configure extends Extension {
if (!device || !(device instanceof Device)) {
error = `Device '${ID}' does not exist`;
} else if (!device.definition || !device.definition.configure) {
} else if (!device.definition?.configure) {
error = `Device '${device.name}' cannot be configured`;
} else {
try {
+6
View File
@@ -176,7 +176,13 @@ export default abstract class ExternalJSExtension<M> extends Extension {
}
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();
+2 -3
View File
@@ -5,7 +5,6 @@ import {createServer} from "node:http";
import {createServer as createSecureServer} from "node:https";
import type {Socket} from "node:net";
import {posix} from "node:path";
import {parse} from "node:url";
import bind from "bind-decorator";
import expressStaticGzip from "express-static-gzip";
import finalhandler from "finalhandler";
@@ -157,10 +156,10 @@ export class Frontend extends Extension {
@bind private onUpgrade(request: IncomingMessage, socket: Socket, head: Buffer): void {
this.wss.handleUpgrade(request, socket, head, (ws) => {
// biome-ignore lint/style/noNonNullAssertion: `Only valid for request obtained from Server`
const {query} = parse(request.url!, true);
const {searchParams} = new URL(request.url!, "http://localhost"); // dummy base, may not be absolute
const authToken = settings.get().frontend.auth_token;
if (!authToken || authToken === query.token) {
if (!authToken || authToken === searchParams.get("token")) {
this.wss.emit("connection", ws, request);
} else {
ws.close(4401, "Unauthorized");
+1 -1
View File
@@ -168,7 +168,7 @@ export default class Groups extends Extension {
endpointNames &&
endpointNames.length >= member.ID &&
device.definition?.meta?.multiEndpoint &&
(!device.definition.meta.multiEndpointSkip || !device.definition.meta.multiEndpointSkip.includes("state"))
!device.definition.meta.multiEndpointSkip?.includes("state")
? `state_${endpointNames[member.ID - 1]}`
: "state";
+137 -107
View File
@@ -2,7 +2,7 @@ import assert from "node:assert";
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import type * as zhc from "zigbee-herdsman-converters";
import type {Zh} from "zigbee-herdsman-converters/lib/types";
import logger from "../util/logger";
import * as settings from "../util/settings";
import utils, {assertBinaryExpose, assertEnumExpose, assertNumericExpose, isBinaryExpose, isEnumExpose, isNumericExpose} from "../util/utils";
@@ -18,6 +18,7 @@ interface DiscoveryEntry {
type: string;
object_id: string;
discovery_payload: KeyValue;
endpoint?: Zh.Endpoint;
}
interface Discovered {
@@ -119,7 +120,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
alarm_temperature_min: {device_class: "temperature", entity_category: "config", icon: "mdi:thermometer-low"},
angle: {icon: "angle-acute"},
angle_axis: {icon: "angle-acute"},
apparent_temperature: {device_class: "temperature", icon: "mdi:thermometer-lines", state_class: "measurement"},
apparent_temperature: {icon: "mdi:thermometer-lines", state_class: "measurement"},
aqi: {device_class: "aqi", state_class: "measurement"},
auto_relock_time: {entity_category: "config", icon: "mdi:timer"},
away_preset_days: {entity_category: "config", icon: "mdi:timer"},
@@ -154,7 +155,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
entity_category: "diagnostic",
state_class: "measurement",
},
dew_point: {device_class: "temperature", icon: "mdi:thermometer-water", state_class: "measurement"},
dew_point: {icon: "mdi:thermometer-water", state_class: "measurement"},
distance: {device_class: "distance", state_class: "measurement"},
duration: {entity_category: "config", icon: "mdi:timer"},
eco2: {device_class: "volatile_organic_compounds_parts", state_class: "measurement"},
@@ -167,10 +168,10 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
flow: {device_class: "volume_flow_rate", state_class: "measurement"},
gas: {device_class: "gas", state_class: "total_increasing", icon: "mdi:meter-gas"},
gas_density: {icon: "mdi:google-circles-communities", state_class: "measurement"},
gust_speed: {device_class: "wind_speed", icon: "mdi:weather-windy-variant", state_class: "measurement"},
gust_speed: {icon: "mdi:weather-windy-variant", state_class: "measurement"},
hcho: {icon: "mdi:air-filter", state_class: "measurement"},
heat_stress: {icon: "mdi:weather-sunny-alert", state_class: "measurement"},
humidex: {device_class: "temperature", icon: "mdi:thermometer-alert", state_class: "measurement"},
humidex: {icon: "mdi:thermometer-alert", state_class: "measurement"},
humidity: {device_class: "humidity", state_class: "measurement"},
humidity_calibration: {entity_category: "config", icon: "mdi:wrench-clock"},
humidity_max: {entity_category: "config", icon: "mdi:water-percent"},
@@ -254,7 +255,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
device_class: "water",
state_class: "total_increasing",
},
wind_chill: {device_class: "temperature", icon: "mdi:snowflake-thermometer", state_class: "measurement"},
wind_chill: {icon: "mdi:snowflake-thermometer", state_class: "measurement"},
wind_direction: {icon: "mdi:compass-outline", state_class: "measurement"},
wind_speed: {device_class: "wind_speed", icon: "mdi:weather-windy", state_class: "measurement"},
x: {icon: "mdi:axis-x-arrow", state_class: "measurement"},
@@ -384,6 +385,7 @@ export class HomeAssistant extends Extension {
private discoveryTopic: string;
private discoveryRegex: RegExp;
private discoveryRegexWoTopic = /(.*)\/(.*)\/(.*)\/config/;
private groupMemberLookup = new Map<string, string>();
private statusTopic: string;
private legacyActionSensor: boolean;
private experimentalEventEntities: boolean;
@@ -459,10 +461,15 @@ export class HomeAssistant extends Extension {
const discoverWait = 5;
// Discover with `published = false`, this will populate `this.discovered` without publishing the discoveries.
// This is needed for clearing outdated entries in `this.onMQTTMessage()`
// Discover devices before groups to populate `this.groupMemberLookup` for group discovery.
await this.discover(this.bridge, false);
for (const e of this.zigbee.devicesAndGroupsIterator(utils.deviceNotCoordinator)) {
await this.discover(e, false);
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
await this.discover(device, false);
}
for (const group of this.zigbee.groupsIterator()) {
await this.discover(group, false);
}
logger.debug(`Discovering entities to Home Assistant in ${discoverWait}s`);
@@ -487,21 +494,16 @@ export class HomeAssistant extends Extension {
return this.discovered[ID];
}
private exposeToConfig(
exposes: zhc.Expose[],
entityType: "device" | "group",
allExposes: zhc.Expose[],
definition?: zhc.Definition,
): DiscoveryEntry[] {
private exposeToConfig(exposes: zhc.Expose[], entity: Device | Group, allExposes: zhc.Expose[]): DiscoveryEntry[] {
// For groups an array of exposes (of the same type) is passed, this is to determine e.g. what features
// to use for a bulb (e.g. color_xy/color_temp)
assert(entityType === "group" || exposes.length === 1, "Multiple exposes for device not allowed");
assert(entity.isGroup() || exposes.length === 1, "Multiple exposes for device not allowed");
const firstExpose = exposes[0];
assert(entityType === "device" || GROUP_SUPPORTED_TYPES.includes(firstExpose.type), `Unsupported expose type ${firstExpose.type} for group`);
assert(entity.isDevice() || GROUP_SUPPORTED_TYPES.includes(firstExpose.type), `Unsupported expose type ${firstExpose.type} for group`);
const discoveryEntries: DiscoveryEntry[] = [];
const endpoint = entityType === "device" ? exposes[0].endpoint : undefined;
const getProperty = (feature: zhc.Feature): string => (entityType === "group" ? featurePropertyWithoutEndpoint(feature) : feature.property);
const endpointName = entity.isDevice() ? exposes[0].endpoint : undefined;
const getProperty = (feature: zhc.Feature): string => (entity.isGroup() ? featurePropertyWithoutEndpoint(feature) : feature.property);
switch (firstExpose.type) {
case "light": {
@@ -520,16 +522,16 @@ export class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: "light",
object_id: endpoint ? `light_${endpoint}` : "light",
object_id: endpointName ? `light_${endpointName}` : "light",
mockProperties: [{property: state.property, value: null}],
discovery_payload: {
name: endpoint ? utils.capitalize(endpoint) : null,
name: endpointName ? utils.capitalize(endpointName) : null,
brightness: !!hasBrightness,
schema: "json",
command_topic: true,
brightness_scale: 254,
command_topic_prefix: endpoint,
state_topic_postfix: endpoint,
command_topic_prefix: endpointName,
state_topic_postfix: endpointName,
},
};
@@ -582,15 +584,15 @@ export class HomeAssistant extends Extension {
const property = getProperty(state);
const discoveryEntry: DiscoveryEntry = {
type: "switch",
object_id: endpoint ? `switch_${endpoint}` : "switch",
object_id: endpointName ? `switch_${endpointName}` : "switch",
mockProperties: [{property: property, value: null}],
discovery_payload: {
name: endpoint ? utils.capitalize(endpoint) : null,
name: endpointName ? utils.capitalize(endpointName) : null,
payload_off: state.value_off,
payload_on: state.value_on,
value_template: `{{ value_json.${property} }}`,
value_template: `{{ value_json["${property}"] }}`,
command_topic: true,
command_topic_prefix: endpoint,
command_topic_prefix: endpointName,
},
};
@@ -621,10 +623,10 @@ export class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: "climate",
object_id: endpoint ? `climate_${endpoint}` : "climate",
object_id: endpointName ? `climate_${endpointName}` : "climate",
mockProperties: [],
discovery_payload: {
name: endpoint ? utils.capitalize(endpoint) : null,
name: endpointName ? utils.capitalize(endpointName) : null,
// Static
state_topic: false,
temperature_unit: "C",
@@ -634,8 +636,8 @@ export class HomeAssistant extends Extension {
max_temp: setpoint.value_max.toString(),
// Temperature
current_temperature_topic: true,
current_temperature_template: `{{ value_json.${temperature.property} }}`,
command_topic_prefix: endpoint,
current_temperature_template: `{{ value_json["${temperature.property}"] }}`,
command_topic_prefix: endpointName,
},
};
@@ -648,7 +650,7 @@ export class HomeAssistant extends Extension {
mode.values.splice(mode.values.indexOf("sleep"), 1);
}
discoveryEntry.discovery_payload.mode_state_topic = true;
discoveryEntry.discovery_payload.mode_state_template = `{{ value_json.${mode.property} }}`;
discoveryEntry.discovery_payload.mode_state_template = `{{ value_json["${mode.property}"] }}`;
discoveryEntry.discovery_payload.modes = mode.values;
discoveryEntry.discovery_payload.mode_command_topic = true;
}
@@ -657,20 +659,20 @@ export class HomeAssistant extends Extension {
if (state) {
discoveryEntry.mockProperties.push({property: state.property, value: null});
discoveryEntry.discovery_payload.action_topic = true;
discoveryEntry.discovery_payload.action_template = `{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.${state.property}] }}`;
discoveryEntry.discovery_payload.action_template = `{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json["${state.property}"]] }}`;
}
const coolingSetpoint = (firstExpose as zhc.Climate).features.find((f) => f.name === "occupied_cooling_setpoint");
if (coolingSetpoint) {
discoveryEntry.discovery_payload.temperature_low_command_topic = setpoint.name;
discoveryEntry.discovery_payload.temperature_low_state_template = `{{ value_json.${setpoint.property} }}`;
discoveryEntry.discovery_payload.temperature_low_state_template = `{{ value_json["${setpoint.property}"] }}`;
discoveryEntry.discovery_payload.temperature_low_state_topic = true;
discoveryEntry.discovery_payload.temperature_high_command_topic = coolingSetpoint.name;
discoveryEntry.discovery_payload.temperature_high_state_template = `{{ value_json.${coolingSetpoint.property} }}`;
discoveryEntry.discovery_payload.temperature_high_state_template = `{{ value_json["${coolingSetpoint.property}"] }}`;
discoveryEntry.discovery_payload.temperature_high_state_topic = true;
} else {
discoveryEntry.discovery_payload.temperature_command_topic = setpoint.name;
discoveryEntry.discovery_payload.temperature_state_template = `{{ value_json.${setpoint.property} }}`;
discoveryEntry.discovery_payload.temperature_state_template = `{{ value_json["${setpoint.property}"] }}`;
discoveryEntry.discovery_payload.temperature_state_topic = true;
}
@@ -678,7 +680,7 @@ export class HomeAssistant extends Extension {
if (fanMode) {
discoveryEntry.discovery_payload.fan_modes = fanMode.values;
discoveryEntry.discovery_payload.fan_mode_command_topic = true;
discoveryEntry.discovery_payload.fan_mode_state_template = `{{ value_json.${fanMode.property} }}`;
discoveryEntry.discovery_payload.fan_mode_state_template = `{{ value_json["${fanMode.property}"] }}`;
discoveryEntry.discovery_payload.fan_mode_state_topic = true;
}
@@ -686,7 +688,7 @@ export class HomeAssistant extends Extension {
if (swingMode) {
discoveryEntry.discovery_payload.swing_modes = swingMode.values;
discoveryEntry.discovery_payload.swing_mode_command_topic = true;
discoveryEntry.discovery_payload.swing_mode_state_template = `{{ value_json.${swingMode.property} }}`;
discoveryEntry.discovery_payload.swing_mode_state_template = `{{ value_json["${swingMode.property}"] }}`;
discoveryEntry.discovery_payload.swing_mode_state_topic = true;
}
@@ -694,7 +696,7 @@ export class HomeAssistant extends Extension {
if (preset) {
discoveryEntry.discovery_payload.preset_modes = preset.values;
discoveryEntry.discovery_payload.preset_mode_command_topic = "preset";
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json.${preset.property} }}`;
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json["${preset.property}"] }}`;
discoveryEntry.discovery_payload.preset_mode_state_topic = true;
}
@@ -704,13 +706,13 @@ export class HomeAssistant extends Extension {
if (tempCalibration) {
const discoveryEntry: DiscoveryEntry = {
type: "number",
object_id: endpoint ? `${tempCalibration.name}_${endpoint}` : `${tempCalibration.name}`,
object_id: endpointName ? `${tempCalibration.name}_${endpointName}` : `${tempCalibration.name}`,
mockProperties: [{property: tempCalibration.property, value: null}],
discovery_payload: {
name: endpoint ? `${tempCalibration.label} ${endpoint}` : tempCalibration.label,
value_template: `{{ value_json.${tempCalibration.property} }}`,
name: endpointName ? `${tempCalibration.label} ${endpointName}` : tempCalibration.label,
value_template: `{{ value_json["${tempCalibration.property}"] }}`,
command_topic: true,
command_topic_prefix: endpoint,
command_topic_prefix: endpointName,
command_topic_postfix: tempCalibration.property,
device_class: "temperature_delta",
entity_category: "config",
@@ -729,11 +731,11 @@ export class HomeAssistant extends Extension {
const piHeatingDemand = (firstExpose as zhc.Climate).features.filter(isNumericExpose).find((f) => f.name === "pi_heating_demand");
if (piHeatingDemand) {
const discoveryEntry: Partial<DiscoveryEntry> = {
object_id: endpoint ? `${piHeatingDemand.name}_${endpoint}` : `${piHeatingDemand.name}`,
object_id: endpointName ? `${piHeatingDemand.name}_${endpointName}` : `${piHeatingDemand.name}`,
mockProperties: [{property: piHeatingDemand.property, value: null}],
discovery_payload: {
name: endpoint ? `${piHeatingDemand.label} ${endpoint}` : piHeatingDemand.label,
value_template: `{{ value_json.${piHeatingDemand.property} }}`,
name: endpointName ? `${piHeatingDemand.label} ${endpointName}` : piHeatingDemand.label,
value_template: `{{ value_json["${piHeatingDemand.property}"] }}`,
...(piHeatingDemand.unit && {unit_of_measurement: piHeatingDemand.unit}),
icon: "mdi:radiator",
},
@@ -744,7 +746,7 @@ export class HomeAssistant extends Extension {
if (piHeatingDemand.access & ACCESS_SET) {
discoveryEntry.type = "number";
discoveryEntry.discovery_payload.command_topic = true;
discoveryEntry.discovery_payload.command_topic_prefix = endpoint;
discoveryEntry.discovery_payload.command_topic_prefix = endpointName;
discoveryEntry.discovery_payload.command_topic_postfix = piHeatingDemand.property;
discoveryEntry.discovery_payload.min = piHeatingDemand.value_min;
discoveryEntry.discovery_payload.max = piHeatingDemand.value_max;
@@ -760,11 +762,11 @@ export class HomeAssistant extends Extension {
if (piCoolingDemand) {
const discoveryEntry: DiscoveryEntry = {
type: "sensor",
object_id: endpoint ? /* v8 ignore next */ `${piCoolingDemand.name}_${endpoint}` : `${piCoolingDemand.name}`,
object_id: endpointName ? /* v8 ignore next */ `${piCoolingDemand.name}_${endpointName}` : `${piCoolingDemand.name}`,
mockProperties: [{property: piCoolingDemand.property, value: null}],
discovery_payload: {
name: endpoint ? /* v8 ignore next */ `${piCoolingDemand.label} ${endpoint}` : piCoolingDemand.label,
value_template: `{{ value_json.${piCoolingDemand.property} }}`,
name: endpointName ? /* v8 ignore next */ `${piCoolingDemand.label} ${endpointName}` : piCoolingDemand.label,
value_template: `{{ value_json["${piCoolingDemand.property}"] }}`,
...(piCoolingDemand.unit && {unit_of_measurement: piCoolingDemand.unit}),
entity_category: "diagnostic",
icon: "mdi:air-conditioner",
@@ -782,11 +784,11 @@ export class HomeAssistant extends Extension {
if (localTemperature && !temperatureSensor && !localTemperatureSensor) {
const discoveryEntry: DiscoveryEntry = {
type: "sensor",
object_id: endpoint ? `${localTemperature.name}_${endpoint}` : `${localTemperature.name}`,
object_id: endpointName ? `${localTemperature.name}_${endpointName}` : `${localTemperature.name}`,
mockProperties: [{property: localTemperature.property, value: null}],
discovery_payload: {
name: endpoint ? `${localTemperature.label} ${endpoint}` : localTemperature.label,
value_template: `{{ value_json.${localTemperature.property} }}`,
name: endpointName ? `${localTemperature.label} ${endpointName}` : localTemperature.label,
value_template: `{{ value_json["${localTemperature.property}"] }}`,
...(localTemperature.unit && {unit_of_measurement: localTemperature.unit}),
device_class: "temperature",
state_class: "measurement",
@@ -798,7 +800,7 @@ export class HomeAssistant extends Extension {
const currentHumidity = allExposes?.filter(isNumericExpose).find((e) => e.name === "humidity" && e.access & ACCESS_STATE);
if (currentHumidity) {
discoveryEntry.discovery_payload.current_humidity_template = `{{ value_json.${currentHumidity.property} }}`;
discoveryEntry.discovery_payload.current_humidity_template = `{{ value_json["${currentHumidity.property}"] }}`;
discoveryEntry.discovery_payload.current_humidity_topic = true;
}
@@ -811,18 +813,18 @@ export class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: "lock",
/* v8 ignore next */
object_id: endpoint ? `lock_${endpoint}` : "lock",
object_id: endpointName ? `lock_${endpointName}` : "lock",
mockProperties: [{property: state.property, value: null}],
discovery_payload: {
/* v8 ignore next */
name: endpoint ? utils.capitalize(endpoint) : null,
command_topic_prefix: endpoint,
name: endpointName ? utils.capitalize(endpointName) : null,
command_topic_prefix: endpointName,
command_topic: true,
value_template: `{{ value_json.${state.property} }}`,
value_template: `{{ value_json["${state.property}"] }}`,
state_locked: state.value_on,
state_unlocked: state.value_off,
/* v8 ignore next */
command_topic_postfix: endpoint ? state.property : null,
command_topic_postfix: endpointName ? state.property : null,
},
};
discoveryEntries.push(discoveryEntry);
@@ -847,13 +849,13 @@ export class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: "cover",
mockProperties: [{property: state.property, value: null}],
object_id: endpoint ? `cover_${endpoint}` : "cover",
object_id: endpointName ? `cover_${endpointName}` : "cover",
discovery_payload: {
name: endpoint ? utils.capitalize(endpoint) : null,
command_topic_prefix: endpoint,
name: endpointName ? utils.capitalize(endpointName) : null,
command_topic_prefix: endpointName,
command_topic: true,
state_topic: true,
state_topic_postfix: endpoint,
state_topic_postfix: endpointName,
},
};
@@ -861,7 +863,7 @@ export class HomeAssistant extends Extension {
// The movement direction is calculated (assumed) in this case.
if (running) {
assert(position, `Cover must have 'position' when it has 'running'`);
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(running)}" in value_json and value_json.${featurePropertyWithoutEndpoint(running)} %} {% if value_json.${featurePropertyWithoutEndpoint(position)} > 0 %} closing {% else %} opening {% endif %} {% else %} stopped {% endif %}`;
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(running)}" in value_json and value_json["${featurePropertyWithoutEndpoint(running)}"] %} {% if value_json["${featurePropertyWithoutEndpoint(position)}"] > 0 %} closing {% else %} opening {% endif %} {% else %} stopped {% endif %}`;
}
// If curtains have `motor_state` or `moving` property, lookup for possible
@@ -875,13 +877,13 @@ export class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.state_opening = openingState;
discoveryEntry.discovery_payload.state_closing = closingState;
discoveryEntry.discovery_payload.state_stopped = stoppedState;
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(motorState)}" in value_json and value_json.${featurePropertyWithoutEndpoint(motorState)} %} {{ value_json.${featurePropertyWithoutEndpoint(motorState)} }} {% else %} ${stoppedState} {% endif %}`;
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(motorState)}" in value_json and value_json["${featurePropertyWithoutEndpoint(motorState)}"] %} {{ value_json["${featurePropertyWithoutEndpoint(motorState)}"] }} {% else %} ${stoppedState} {% endif %}`;
}
}
// If curtains do not have `running`, `motor_state` or `moving` properties.
if (!discoveryEntry.discovery_payload.value_template) {
discoveryEntry.discovery_payload.value_template = `{{ value_json.${featurePropertyWithoutEndpoint(state)} }}`;
discoveryEntry.discovery_payload.value_template = `{{ value_json["${featurePropertyWithoutEndpoint(state)}"] }}`;
discoveryEntry.discovery_payload.state_open = "OPEN";
discoveryEntry.discovery_payload.state_closed = "CLOSE";
discoveryEntry.discovery_payload.state_stopped = "STOP";
@@ -896,7 +898,7 @@ export class HomeAssistant extends Extension {
if (position) {
discoveryEntry.discovery_payload = {
...discoveryEntry.discovery_payload,
position_template: `{{ value_json.${featurePropertyWithoutEndpoint(position)} }}`,
position_template: `{{ value_json["${featurePropertyWithoutEndpoint(position)}"] }}`,
set_position_template: `{ "${getProperty(position)}": {{ position }} }`,
set_position_topic: true,
position_topic: true,
@@ -908,7 +910,7 @@ export class HomeAssistant extends Extension {
...discoveryEntry.discovery_payload,
tilt_command_topic: true,
tilt_status_topic: true,
tilt_status_template: `{{ value_json.${featurePropertyWithoutEndpoint(tilt)} }}`,
tilt_status_template: `{{ value_json["${featurePropertyWithoutEndpoint(tilt)}"] }}`,
};
}
@@ -916,7 +918,7 @@ export class HomeAssistant extends Extension {
break;
}
case "fan": {
assert(!endpoint, "Endpoint not supported for fan type");
assert(!endpointName, "Endpoint not supported for fan type");
const discoveryEntry: DiscoveryEntry = {
type: "fan",
object_id: "fan",
@@ -952,7 +954,7 @@ export class HomeAssistant extends Extension {
);
let presets = ["on", "auto", "smart"].filter((s) => modeEmulatedSpeed.values.includes(s));
if (definition?.model === "99432") {
if (entity.isDevice() && entity.definition?.model === "99432") {
// The Hampton Bay 99432 fan implements 4 speeds using the ZCL
// hvacFanCtrl values `low`, `medium`, `high`, and `on`, and
// 1 preset called "Comfort Breeze" using the ZCL value `smart`.
@@ -973,14 +975,14 @@ export class HomeAssistant extends Extension {
discoveryEntry.discovery_payload.percentage_state_topic = true;
discoveryEntry.discovery_payload.percentage_command_topic = "fan_mode";
discoveryEntry.discovery_payload.percentage_value_template = `{{ {${percentValues}}[value_json.${modeEmulatedSpeed.property}] | default('None') }}`;
discoveryEntry.discovery_payload.percentage_value_template = `{{ {${percentValues}}[value_json["${modeEmulatedSpeed.property}"]] | default('None') }}`;
discoveryEntry.discovery_payload.percentage_command_template = `{{ {${percentCommands}}[value] | default('') }}`;
discoveryEntry.discovery_payload.speed_range_min = 1;
discoveryEntry.discovery_payload.speed_range_max = speeds.length - 1;
assert(presets.length !== 0);
discoveryEntry.discovery_payload.preset_mode_state_topic = true;
discoveryEntry.discovery_payload.preset_mode_command_topic = "fan_mode";
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json.${modeEmulatedSpeed.property} if value_json.${modeEmulatedSpeed.property} in [${presetList}] else 'None' | default('None') }}`;
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json["${modeEmulatedSpeed.property}"] if value_json["${modeEmulatedSpeed.property}"] in [${presetList}] else 'None' | default('None') }}`;
discoveryEntry.discovery_payload.preset_modes = presets;
// Emulate state based on mode
@@ -989,7 +991,7 @@ export class HomeAssistant extends Extension {
} else if (nativeSpeed) {
discoveryEntry.discovery_payload.percentage_state_topic = true;
discoveryEntry.discovery_payload.percentage_command_topic = "speed";
discoveryEntry.discovery_payload.percentage_value_template = `{{ value_json.${nativeSpeed.property} | default('None') }}`;
discoveryEntry.discovery_payload.percentage_value_template = `{{ value_json["${nativeSpeed.property}"] | default('None') }}`;
discoveryEntry.discovery_payload.percentage_command_template = `{{ value | default('') }}`;
discoveryEntry.discovery_payload.speed_range_min = nativeSpeed.value_min;
discoveryEntry.discovery_payload.speed_range_max = nativeSpeed.value_max;
@@ -1014,17 +1016,17 @@ export class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: "switch",
mockProperties: [{property: firstExpose.property, value: null}],
object_id: endpoint ? `switch_${firstExpose.name}_${endpoint}` : `switch_${firstExpose.name}`,
object_id: endpointName ? `switch_${firstExpose.name}_${endpointName}` : `switch_${firstExpose.name}`,
discovery_payload: {
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpointName ? /* v8 ignore next */ `${firstExpose.label} ${endpointName}` : firstExpose.label,
value_template:
typeof firstExpose.value_on === "boolean"
? `{% if value_json.${firstExpose.property} %}true{% else %}false{% endif %}`
: `{{ value_json.${firstExpose.property} }}`,
? `{% if value_json["${firstExpose.property}"] %}true{% else %}false{% endif %}`
: `{{ value_json["${firstExpose.property}"] }}`,
payload_on: firstExpose.value_on.toString(),
payload_off: firstExpose.value_off.toString(),
command_topic: true,
command_topic_prefix: endpoint,
command_topic_prefix: endpointName,
command_topic_postfix: firstExpose.property,
...(BINARY_DISCOVERY_LOOKUP[firstExpose.name] || {}),
},
@@ -1034,11 +1036,11 @@ export class HomeAssistant extends Extension {
} else {
const discoveryEntry: DiscoveryEntry = {
type: "binary_sensor",
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
object_id: endpointName ? `${firstExpose.name}_${endpointName}` : `${firstExpose.name}`,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
value_template: `{{ value_json.${firstExpose.property} }}`,
name: endpointName ? /* v8 ignore next */ `${firstExpose.label} ${endpointName}` : firstExpose.label,
value_template: `{{ value_json["${firstExpose.property}"] }}`,
payload_on: firstExpose.value_on,
payload_off: firstExpose.value_off,
...(BINARY_DISCOVERY_LOOKUP[firstExpose.name] || {}),
@@ -1059,13 +1061,13 @@ export class HomeAssistant extends Extension {
if (allowsSet) {
const discoveryEntry: DiscoveryEntry = {
type: "number",
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
object_id: endpointName ? `${firstExpose.name}_${endpointName}` : `${firstExpose.name}`,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
value_template: `{{ value_json.${firstExpose.property} }}`,
name: endpointName ? `${firstExpose.label} ${endpointName}` : firstExpose.label,
value_template: `{{ value_json["${firstExpose.property}"] }}`,
command_topic: true,
command_topic_prefix: endpoint,
command_topic_prefix: endpointName,
command_topic_postfix: firstExpose.property,
...(firstExpose.unit && {unit_of_measurement: firstExpose.unit}),
...(firstExpose.value_step && {step: firstExpose.value_step}),
@@ -1110,11 +1112,11 @@ export class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: "sensor",
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
object_id: endpointName ? `${firstExpose.name}_${endpointName}` : `${firstExpose.name}`,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
value_template: `{{ value_json.${firstExpose.property} }}`,
name: endpointName ? `${firstExpose.label} ${endpointName}` : firstExpose.label,
value_template: `{{ value_json["${firstExpose.property}"] }}`,
enabled_by_default: !allowsSet,
...(firstExpose.unit && {unit_of_measurement: firstExpose.unit}),
...NUMERIC_DISCOVERY_LOOKUP[key],
@@ -1155,7 +1157,7 @@ export class HomeAssistant extends Extension {
object_id: firstExpose.property,
mockProperties: [],
discovery_payload: {
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpointName ? /* v8 ignore next */ `${firstExpose.label} ${endpointName}` : firstExpose.label,
state_topic: true,
event_types: this.prepareActionEventTypes(firstExpose.values),
value_template: this.actionValueTemplate,
@@ -1168,7 +1170,7 @@ export class HomeAssistant extends Extension {
}
}
const valueTemplate = firstExpose.access & ACCESS_STATE ? `{{ value_json.${firstExpose.property} }}` : undefined;
const valueTemplate = firstExpose.access & ACCESS_STATE ? `{{ value_json["${firstExpose.property}"] }}` : undefined;
/**
* If enum has only one item and has SET access then expose as BUTTON entity.
@@ -1179,9 +1181,9 @@ export class HomeAssistant extends Extension {
object_id: firstExpose.property,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpointName ? /* v8 ignore next */ `${firstExpose.label} ${endpointName}` : firstExpose.label,
state_topic: false,
command_topic_prefix: endpoint,
command_topic_prefix: endpointName,
command_topic: true,
command_topic_postfix: firstExpose.property,
payload_press: firstExpose.values[0].toString(),
@@ -1200,10 +1202,10 @@ export class HomeAssistant extends Extension {
object_id: firstExpose.property,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpointName ? `${firstExpose.label} ${endpointName}` : firstExpose.label,
value_template: valueTemplate,
state_topic: !!(firstExpose.access & ACCESS_STATE),
command_topic_prefix: endpoint,
command_topic_prefix: endpointName,
command_topic: true,
command_topic_postfix: firstExpose.property,
options: firstExpose.values.map((v) => v.toString()),
@@ -1222,7 +1224,7 @@ export class HomeAssistant extends Extension {
object_id: firstExpose.property,
mockProperties: [{property: firstExpose.property, value: null}],
discovery_payload: {
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
name: endpointName ? `${firstExpose.label} ${endpointName}` : firstExpose.label,
value_template: valueTemplate,
...ENUM_DISCOVERY_LOOKUP[firstExpose.name],
},
@@ -1244,12 +1246,12 @@ export class HomeAssistant extends Extension {
const discoveryEntry: DiscoveryEntry = {
type: "siren",
object_id: endpoint ? /* v8 ignore next */ `siren_${endpoint}` : "siren",
object_id: endpointName ? /* v8 ignore next */ `siren_${endpointName}` : "siren",
mockProperties: [{property: warningExpose.property, value: null}],
discovery_payload: {
name: endpoint ? /* v8 ignore next */ utils.capitalize(endpoint) : null,
name: endpointName ? /* v8 ignore next */ utils.capitalize(endpointName) : null,
command_topic: true,
command_topic_prefix: endpoint,
command_topic_prefix: endpointName,
state_topic: false,
optimistic: true,
},
@@ -1295,10 +1297,10 @@ export class HomeAssistant extends Extension {
object_id: firstExposeTyped.property,
mockProperties: [{property: firstExposeTyped.property, value: null}],
discovery_payload: {
name: endpoint ? `${firstExposeTyped.label} ${endpoint}` : firstExposeTyped.label,
name: endpointName ? `${firstExposeTyped.label} ${endpointName}` : firstExposeTyped.label,
state_topic: firstExposeTyped.access & ACCESS_STATE,
value_template: `{{ value_json.${firstExposeTyped.property} }}`,
command_topic_prefix: endpoint,
value_template: `{{ value_json["${firstExposeTyped.property}"] }}`,
command_topic_prefix: endpointName,
command_topic: true,
command_topic_postfix: firstExposeTyped.property,
...LIST_DISCOVERY_LOOKUP[firstExposeTyped.name],
@@ -1312,10 +1314,10 @@ export class HomeAssistant extends Extension {
object_id: firstExposeTyped.property,
mockProperties: [{property: firstExposeTyped.property, value: null}],
discovery_payload: {
name: endpoint ? `${firstExposeTyped.label} ${endpoint}` : firstExposeTyped.label,
name: endpointName ? `${firstExposeTyped.label} ${endpointName}` : firstExposeTyped.label,
// Truncate text if it's too long
// https://github.com/Koenkk/zigbee2mqtt/issues/23199
value_template: `{{ value_json.${firstExposeTyped.property} | default('',True) | string | truncate(254, True, '', 0) }}`,
value_template: `{{ value_json["${firstExposeTyped.property}"] | default('',True) | string | truncate(254, True, '', 0) }}`,
...LIST_DISCOVERY_LOOKUP[firstExposeTyped.name],
},
});
@@ -1349,6 +1351,8 @@ export class HomeAssistant extends Extension {
if (entry.discovery_payload.device_class) {
delete entry.discovery_payload.name;
}
entry.endpoint = entity.isDevice() ? entity.endpoint(endpointName) : undefined;
}
return discoveryEntries;
@@ -1474,7 +1478,7 @@ export class HomeAssistant extends Extension {
if (isDevice) {
const exposes = entity.exposes(); // avoid calling it hundred of times/s
for (const expose of exposes) {
configs.push(...this.exposeToConfig([expose], "device", exposes, entity.definition));
configs.push(...this.exposeToConfig([expose], entity, exposes));
}
} else if (isGroup) {
// group
@@ -1503,7 +1507,7 @@ export class HomeAssistant extends Extension {
}
configs = ([] as DiscoveryEntry[]).concat(
...Object.values(exposesByType).map((exposes) => this.exposeToConfig(exposes, "group", allExposes)),
...Object.values(exposesByType).map((exposes) => this.exposeToConfig(exposes, entity, allExposes)),
);
} else {
// Discover bridge config.
@@ -1656,7 +1660,33 @@ export class HomeAssistant extends Extension {
payload.default_entity_id = `${config.type}.${payload.object_id}`;
// Set unique_id
payload.unique_id = `${entity.options.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
const uniqueId = `${entity.options.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
payload.unique_id = uniqueId;
if (config.endpoint) {
assert(entity.isDevice());
const memberKey = `${config.endpoint.deviceIeeeAddress}_${config.endpoint.ID}_${config.type}`;
this.groupMemberLookup.set(memberKey, uniqueId);
}
// Add group member unique_ids for Home Assistant entity grouping
// This assumes that `discover()` already has been called for all devices, since it depends on the
// `groupMemberLookup` being populated.
// https://www.home-assistant.io/integrations/mqtt#grouping-entities
if (isGroup) {
const memberIds: string[] = [];
for (const endpoint of entity.zh.members) {
const memberKey = `${endpoint.deviceIeeeAddress}_${endpoint.ID}_${config.type}`;
const memberUniqueId = this.groupMemberLookup.get(memberKey);
if (memberUniqueId) {
memberIds.push(memberUniqueId);
}
}
if (memberIds.length > 0) {
payload.group = memberIds;
}
}
// Attributes for device registry and origin
payload.device = devicePayload;
@@ -1867,11 +1897,11 @@ export class HomeAssistant extends Extension {
try {
message = JSON.parse(data.message);
const baseTopic = `${settings.get().mqtt.base_topic}/`;
if (isDeviceAutomation && (!message.topic || !message.topic.startsWith(baseTopic))) {
if (isDeviceAutomation && !message.topic?.startsWith(baseTopic)) {
return;
}
if (!isDeviceAutomation && (!message.availability || !message.availability[0].topic.startsWith(baseTopic))) {
if (!isDeviceAutomation && !message.availability?.[0].topic.startsWith(baseTopic)) {
return;
}
} catch {
+6 -6
View File
@@ -284,11 +284,11 @@ export default class OTAUpdate extends Extension {
if (payload.url) {
source.url = payload.url;
} else if (!device.definition || !device.definition.ota) {
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
} else if (!device.definition || !device.definition.ota) {
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
@@ -344,7 +344,7 @@ export default class OTAUpdate extends Extension {
source.url = writeFirmwareHexToDataDir(payload.hex.data, payload.hex.file_name, device.ieeeAddr);
} else if (payload.url) {
source.url = payload.url;
} else if (!device.definition || !device.definition.ota) {
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
@@ -360,7 +360,7 @@ export default class OTAUpdate extends Extension {
if (payload.default_maximum_data_size) {
dataSettings.baseSize = payload.default_maximum_data_size;
}
} else if (!device.definition || !device.definition.ota) {
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
@@ -415,11 +415,11 @@ export default class OTAUpdate extends Extension {
source.url = writeFirmwareHexToDataDir(payload.hex.data, payload.hex.file_name, device.ieeeAddr);
} else if (payload.url) {
source.url = payload.url;
} else if (!device.definition || !device.definition.ota) {
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
} else if (!device.definition || !device.definition.ota) {
} else if (!device.definition?.ota) {
error = `Device '${device.name}' does not support OTA updates`;
break;
}
+5 -2
View File
@@ -1,7 +1,7 @@
import type * as zigbeeHerdsman from "zigbee-herdsman/dist";
import type {ZclPayload} from "zigbee-herdsman/dist/adapter/events";
import type {Eui64} from "zigbee-herdsman/dist/zspec/tstypes";
import type {ClusterDefinition, ClusterName, CustomClusters} from "zigbee-herdsman/dist/zspec/zcl/definition/tstype";
import type {Cluster, ClusterName, CustomClusters} from "zigbee-herdsman/dist/zspec/zcl/definition/tstype";
import type {GenericZdoResponse, RoutingTableEntry} from "zigbee-herdsman/dist/zspec/zdo/definition/tstypes";
import type * as zigbeeHerdsmanConverter from "zigbee-herdsman-converters";
import type {Base} from "zigbee-herdsman-converters/lib/exposes";
@@ -215,6 +215,8 @@ export interface Zigbee2MQTTSettings {
timestamp_format: string;
output: "json" | "attribute" | "attribute_and_json";
transmit_power?: number;
/** 3.0: default to false (JSON schema & settings `defaults`) */
enable_external_js: boolean;
};
health: {
/** in minutes */
@@ -259,6 +261,7 @@ export interface Zigbee2MQTTDeviceDefinition {
exposes: zigbeeHerdsmanConverter.Expose[];
supports_ota: boolean;
options: zigbeeHerdsmanConverter.Option[];
version: `0.0.${number}`;
icon: string;
}
@@ -343,7 +346,7 @@ export interface Zigbee2MQTTAPI {
};
"bridge/definitions": {
clusters: Readonly<Record<ClusterName, Readonly<ClusterDefinition>>>;
clusters: Readonly<Record<ClusterName, Cluster>>;
custom_clusters: Record<string, CustomClusters>;
actions: string[];
};
+187 -459
View File
@@ -1,398 +1,98 @@
import {existsSync, mkdirSync} from "node:fs";
import {existsSync, mkdirSync, writeFileSync} from "node:fs";
import type {ServerResponse} from "node:http";
import {createServer} from "node:http";
import {parse} from "node:querystring";
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";
type OnboardSettings = {
mqtt_base_topic?: string;
mqtt_server?: string;
mqtt_user?: string;
mqtt_password?: string;
serial_port?: string;
serial_adapter?: Settings["serial"]["adapter"];
serial_baudrate?: string;
serial_rtscts?: "on";
network_channel?: string;
network_key?: string;
network_pan_id?: string;
network_ext_pan_id?: string;
frontend_enabled?: "on";
frontend_port?: string;
homeassistant_enabled?: "on";
log_level?: Settings["advanced"]["log_level"];
/** 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 escapeHtml(s: string): string {
return s.replace(/[^0-9A-Za-z \-_.]/g, (c) => `&#${c.charCodeAt(0)};`);
}
function generateHtmlDone(frontendUrl: string | undefined): string {
return `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zigbee2MQTT Onboarding</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css">
</head>
<body>
<main>
<h1>Zigbee2MQTT Onboarding</h1>
<p>Settings saved.</p>
<p>Zigbee2MQTT is now starting...</p>
<small>${frontendUrl ? `Redirecting to Zigbee2MQTT frontend at <a href="${frontendUrl}">${frontendUrl}</a> in 30 seconds.` : "You can close this page."}</small>
</main>
${frontendUrl ? `<script>setTimeout(() => { window.location.replace("${frontendUrl}"); }, 30000);</script>` : ""}
</body>
</html>
`;
}
function generateHtmlForm(currentSettings: RecursivePartial<Settings>, devices: Awaited<ReturnType<typeof findAllDevices>>): string {
let devicesSelect = "";
if (devices.length > 0) {
devicesSelect += '<select id="found_device" onchange="setFoundDevice(this)">';
devicesSelect += '<option value="">Select a device</option>';
for (const device of devices) {
// just in case name has commas, remove them to not mess with `split` logic
const deviceStr = `${device.name.replaceAll(",", "")}, ${device.path}, ${device.adapter ?? "unknown"}`;
devicesSelect += `<option value="${deviceStr}">${deviceStr}</option>`;
}
devicesSelect += "</select>";
devicesSelect += "<small>Optionally allows to configure coordinator port and type (if known) automatically.</small>";
} else {
devicesSelect = "<small>No device found</small>";
}
let generateCheckbox = "";
if (
Array.isArray(currentSettings.advanced?.network_key) ||
typeof currentSettings.advanced?.pan_id === "number" ||
Array.isArray(currentSettings.advanced?.ext_pan_id)
) {
generateCheckbox = `
<label for="generate_network">
<input
type="checkbox"
id="generate_network"
onclick="setGenerate(this)"
${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_NETWORK_KEY || process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_PAN_ID || process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_EXT_PAN_ID ? "disabled" : ""}>
Generate network?
</label>
`;
}
/* v8 ignore start */
return `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zigbee2MQTT Onboarding</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css">
</head>
<body>
<main>
<h1>Zigbee2MQTT Onboarding</h1>
<p>Set the base configuration to start Zigbee2MQTT.</p>
<p>Optional fields will either be ignored or fallback to defaults if not set (see appropriate documentation page for more details).</p>
<p>If a field is disabled, it means <a href="https://www.zigbee2mqtt.io/guide/configuration/#environment-variables" target="_blank">environment variables</a> are being used to override specific values (for example, through the Home Assistant add-on configuration page).</p>
<hr>
<form method="post">
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_SERIAL || process.env.ZIGBEE2MQTT_CONFIG_SERIAL_PORT || process.env.ZIGBEE2MQTT_CONFIG_SERIAL_ADAPTER ? "disabled" : ""}>
<label for="found_device">Found Devices</label>
${devicesSelect}
</fieldset>
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_SERIAL ? "disabled" : ""}>
<label for="serial_port">Coordinator/Adapter Port/Path</label>
<input
type="text"
id="serial_port"
name="serial_port"
value="${currentSettings.serial?.port ?? ""}"
required
${process.env.ZIGBEE2MQTT_CONFIG_SERIAL_PORT ? "disabled" : ""}>
<label for="serial_adapter">Coordinator/Adapter Type/Stack/Driver</label>
<select id="serial_adapter" name="serial_adapter" required ${process.env.ZIGBEE2MQTT_CONFIG_SERIAL_ADAPTER ? "disabled" : ""}>
<option value="zstack" ${currentSettings.serial?.adapter === "zstack" ? "selected" : ""}>zstack</option>
<option value="ember" ${currentSettings.serial?.adapter === "ember" ? "selected" : ""}>ember</option>
<option value="deconz" ${currentSettings.serial?.adapter === "deconz" ? "selected" : ""}>deconz</option>
<option value="zigate" ${currentSettings.serial?.adapter === "zigate" ? "selected" : ""}>zigate</option>
<option value="zboss" ${currentSettings.serial?.adapter === "zboss" ? "selected" : ""}>zboss</option>
</select>
<label for="serial_baudrate">Coordinator/Adapter Baudrate</label>
<select id="serial_baudrate" name="serial_baudrate" ${process.env.ZIGBEE2MQTT_CONFIG_SERIAL_BAUDRATE ? "disabled" : ""}>
<option value="38400" ${currentSettings.serial?.baudrate === 38400 ? "selected" : ""}>38400</option>
<option value="57600" ${currentSettings.serial?.baudrate === 57600 ? "selected" : ""}>57600</option>
<option value="115200" ${!currentSettings.serial?.baudrate || currentSettings.serial?.baudrate === 115200 ? "selected" : ""}>115200</option>
<option value="230400" ${currentSettings.serial?.baudrate === 230400 ? "selected" : ""}>230400</option>
<option value="460800" ${currentSettings.serial?.baudrate === 460800 ? "selected" : ""}>460800</option>
<option value="921600" ${currentSettings.serial?.baudrate === 921600 ? "selected" : ""}>921600</option>
</select>
<small>Can be ignored for networked coordinators (TCP).</small>
<label for="serial_rtscts">Coordinator/Adapter Hardware Flow Control ("rtscts: true")</label>
<input
type="checkbox"
id="serial_rtscts"
name="serial_rtscts"
${currentSettings.serial?.rtscts ? "checked" : ""}
${process.env.ZIGBEE2MQTT_CONFIG_SERIAL_RTSCTS ? "disabled" : ""}
style="margin-bottom: 1rem;">
<small>Can be ignored for networked coordinators (TCP).</small>
</fieldset>
<small>
<a href="https://www.zigbee2mqtt.io/guide/configuration/adapter-settings.html" target="_blank">https://www.zigbee2mqtt.io/guide/configuration/adapter-settings.html</a>
</small>
<hr>
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED ? "disabled" : ""}>
<label for="closest_wifi_channel">Closest WiFi Channel</label>
<input
type="number"
min="0"
max="14"
id="closest_wifi_channel"
value="0"
onclick="setBestZigbeeChannel(this)"
${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_CHANNEL ? "disabled" : ""}>
<small>Optionally set to your closest WiFi channel to pick the best value for "Network channel" below.</small>
<label for="network_channel">Network Channel</label>
<input
type="number"
min="11"
max="26"
id="network_channel"
name="network_channel"
value="${currentSettings.advanced?.channel ?? "25"}"
required
${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_CHANNEL ? "disabled" : ""}>
</fieldset>
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED ? "disabled" : ""}>
${generateCheckbox}
<label for="network_key">Network Key</label>
<input
type="text"
id="network_key"
name="network_key"
value="${currentSettings.advanced?.network_key ?? "GENERATE"}"
pattern="^([0-9]+(,[0-9]+){15})|GENERATE$"
required
${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_NETWORK_KEY ? "disabled" : ""}>
<label for="network_pan_id">Network PAN ID</label>
<input
type="text"
id="network_pan_id"
name="network_pan_id"
value="${currentSettings.advanced?.pan_id ?? "GENERATE"}"
pattern="^([0-9]{1,5})|GENERATE$"
required
${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_PAN_ID ? "disabled" : ""}>
<label for="network_ext_pan_id">Network Extended PAN ID</label>
<input
type="text"
id="network_ext_pan_id"
name="network_ext_pan_id"
value="${currentSettings.advanced?.ext_pan_id ?? "GENERATE"}"
pattern="^([0-9]+(,[0-9]+){7})|GENERATE$"
required
${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_EXT_PAN_ID ? "disabled" : ""}>
</fieldset>
<small>
<a href="https://www.zigbee2mqtt.io/guide/configuration/zigbee-network.html" target="_blank">https://www.zigbee2mqtt.io/guide/configuration/zigbee-network.html</a>
</small>
<hr>
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_MQTT ? "disabled" : ""}>
<label for="mqtt_base_topic">MQTT Base Topic</label>
<input
type="text"
id="mqtt_base_topic"
name="mqtt_base_topic"
value="${currentSettings.mqtt?.base_topic ?? "zigbee2mqtt"}"
required
${process.env.ZIGBEE2MQTT_CONFIG_MQTT_BASE_TOPIC ? "disabled" : ""}>
<label for="mqtt_server">MQTT Server</label>
<input
type="text"
id="mqtt_server"
name="mqtt_server"
value="${currentSettings.mqtt?.server ?? "mqtt://localhost:1883"}"
required
${process.env.ZIGBEE2MQTT_CONFIG_MQTT_SERVER ? "disabled" : ""}>
<label for="mqtt_user">MQTT User</label>
<input
type="text"
id="mqtt_user"
name="mqtt_user"
value="${currentSettings.mqtt?.user ?? ""}"
${process.env.ZIGBEE2MQTT_CONFIG_MQTT_USER ? "disabled" : ""}>
<small>Optional. Set only if using authentication.</small>
<label for="mqtt_password">MQTT Password</label>
<input
type="password"
id="mqtt_password"
name="mqtt_password"
value="${currentSettings.mqtt?.password ?? ""}"
${process.env.ZIGBEE2MQTT_CONFIG_MQTT_PASSWORD ? "disabled" : ""}>
<small>Optional. Set only if using authentication.</small>
</fieldset>
<small>
<a href="https://www.zigbee2mqtt.io/guide/configuration/mqtt.html" target="_blank">https://www.zigbee2mqtt.io/guide/configuration/mqtt.html</a>
</small>
<hr>
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_FRONTEND ? "disabled" : ""}>
<label for="frontend_enabled">
<input
type="checkbox"
id="frontend_enabled"
name="frontend_enabled"
${currentSettings.frontend?.enabled ? "checked" : ""}
${process.env.ZIGBEE2MQTT_CONFIG_FRONTEND_ENABLED ? "disabled" : ""}>
Frontend enabled?
</label>
<label for="frontend_port">Frontend Port</label>
<input
type="number"
min="0"
max="65535"
id="frontend_port"
name="frontend_port"
value="${currentSettings.frontend?.port ?? "8080"}"
required
${process.env.ZIGBEE2MQTT_CONFIG_FRONTEND_PORT ? "disabled" : ""}>
</fieldset>
<small>
<a href="https://www.zigbee2mqtt.io/guide/configuration/frontend.html" target="_blank">https://www.zigbee2mqtt.io/guide/configuration/frontend.html</a>
</small>
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_HOMEASSISTANT ? "disabled" : ""}>
<label for="homeassistant_enabled">
<input
type="checkbox"
id="homeassistant_enabled"
name="homeassistant_enabled"
${currentSettings.homeassistant?.enabled ? "checked" : ""}
${process.env.ZIGBEE2MQTT_CONFIG_HOMEASSISTANT_ENABLED ? "disabled" : ""}>
Home Assistant enabled?
</label>
</fieldset>
<small>
<a href="https://www.zigbee2mqtt.io/guide/configuration/homeassistant.html" target="_blank">https://www.zigbee2mqtt.io/guide/configuration/homeassistant.html</a>
</small>
<hr>
<fieldset ${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED ? "disabled" : ""}>
<label for="log_level">Log Level</label>
<select id="log_level" name="log_level" ${process.env.ZIGBEE2MQTT_CONFIG_ADVANCED_LOG_LEVEL ? "disabled" : ""}>
<option value="error" ${currentSettings.advanced?.log_level === "error" ? "selected" : ""}>error</option>
<option value="warning" ${currentSettings.advanced?.log_level === "warning" ? "selected" : ""}>warning</option>
<option value="info" ${!currentSettings.advanced?.log_level || currentSettings.advanced?.log_level === "info" ? "selected" : ""}>info</option>
<option value="debug" ${currentSettings.advanced?.log_level === "debug" ? "selected" : ""}>debug</option>
</select>
</fieldset>
<small>
<a href="https://www.zigbee2mqtt.io/guide/configuration/logging.html" target="_blank">https://www.zigbee2mqtt.io/guide/configuration/logging.html</a>
</small>
<hr>
<input type="submit" value="Submit">
</form>
</main>
<script>
function setFoundDevice(e) {
if (!e.value) {
return;
}
const [, path, adapter] = e.value.split(", ");
const serialPortEl = document.querySelector("#serial_port");
serialPortEl.value = path;
const serialAdapterEl = document.querySelector("#serial_adapter");
if (['zstack', 'ember', 'deconz', 'zigate', 'zboss'].includes(adapter)) {
serialAdapterEl.value = adapter;
} else {
serialAdapterEl.value = '';
}
}
function setBestZigbeeChannel(e) {
const wifiChannel = parseInt(e.value, 10);
const networkChannelEl = document.querySelector("#network_channel");
if (wifiChannel >= 11) {
// WiFi 11-14
networkChannelEl.value = 15;
} else if (wifiChannel >= 6) {
// WiFi 6-10
networkChannelEl.value = 11;
} else {
// WiFi 1-5
networkChannelEl.value = 25;
}
}
function setGenerate(e) {
document.querySelector("#network_key").value = e.checked ? "GENERATE" : "${currentSettings.advanced?.network_key ?? "GENERATE"}";
document.querySelector("#network_pan_id").value = e.checked ? "GENERATE" : "${currentSettings.advanced?.pan_id ?? "GENERATE"}";
document.querySelector("#network_ext_pan_id").value = e.checked ? "GENERATE" : "${currentSettings.advanced?.ext_pan_id ?? "GENERATE"}";
}
</script>
</body>
</html>
`;
/* v8 ignore stop */
}
function generateHtmlError(errors: string): string {
return `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zigbee2MQTT Onboarding</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css">
</head>
<body>
<main>
<h1>Zigbee2MQTT configuration is not valid</h1>
<p style="color: #F00;">Found the following errors:</p>
${errors}
<hr>
<p>If you don't know how to solve this, read <a href="https://www.zigbee2mqtt.io/guide/configuration" target="_blank">https://www.zigbee2mqtt.io/guide/configuration</a></p>
<form method="post" action="/">
<input type="submit" value="Close">
</form>
</main>
</body>
</html>
`;
}
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;
let failed = false;
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 (failed) {
res.end(() => {
resolve(false);
});
} else {
if (pathname === "/submit") {
let body = "";
req.on("data", (chunk) => {
@@ -400,92 +100,108 @@ async function startOnboardingServer(): Promise<boolean> {
});
req.on("end", () => {
const result = parse(body) as unknown as OnboardSettings;
const frontendEnabled = result.frontend_enabled === "on";
const updatedSettings: RecursivePartial<Settings> = {
mqtt: {
base_topic: result.mqtt_base_topic,
server: result.mqtt_server,
user: result.mqtt_user || undefined, // empty string => removed
password: result.mqtt_password || undefined, // empty string => removed
},
serial: {
port: result.serial_port,
adapter: result.serial_adapter,
baudrate: result.serial_baudrate ? Number.parseInt(result.serial_baudrate, 10) : undefined,
rtscts: result.serial_rtscts === "on",
},
advanced: {
log_level: result.log_level,
channel: result.network_channel ? Number.parseInt(result.network_channel, 10) : undefined,
network_key: result.network_key
? result.network_key === "GENERATE"
? result.network_key
: result.network_key.split(",").map((v) => Number.parseInt(v, 10))
: undefined,
pan_id: result.network_pan_id
? result.network_pan_id === "GENERATE"
? result.network_pan_id
: Number.parseInt(result.network_pan_id, 10)
: undefined,
ext_pan_id: result.network_ext_pan_id
? result.network_ext_pan_id === "GENERATE"
? result.network_ext_pan_id
: result.network_ext_pan_id.split(",").map((v) => Number.parseInt(v, 10))
: undefined,
},
frontend: {
enabled: frontendEnabled,
port: result.frontend_port ? Number.parseInt(result.frontend_port, 10) : undefined,
},
homeassistant: {
enabled: result.homeassistant_enabled === "on",
},
};
try {
settings.apply(updatedSettings);
const result = (body ? JSON.parse(body) : {}) as RecursivePartial<Zigbee2MQTTSettings>;
// to redirect, make sure frontend "will be" enabled, and host isn't socket
settings.apply(result);
const appliedSettings = settings.get();
const redirect =
!process.env.Z2M_ONBOARD_NO_REDIRECT &&
frontendEnabled &&
(!currentSettings.frontend?.host || !currentSettings.frontend.host.startsWith("/"));
const protocol = currentSettings.frontend?.ssl_cert && currentSettings.frontend.ssl_key ? "https" : "http";
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", "text/html");
res.setHeader("Content-Type", "application/json");
res.writeHead(200);
res.end(
generateHtmlDone(
redirect
? /* v8 ignore next */ `${protocol}://${currentSettings.frontend?.host ?? "localhost"}:${currentSettings.frontend?.port ?? "8080"}${currentSettings.frontend?.base_url ?? "/"}`
: undefined,
),
() => {
resolve(true);
},
);
res.end(stringify(payload), () => {
resolve(true);
});
} catch (error) {
console.error(`Failed to apply configuration: ${(error as Error).message}`);
failed = true;
if (process.env.Z2M_ONBOARD_NO_FAILURE_PAGE) {
res.end(() => {
resolve(false);
});
} else {
res.setHeader("Content-Type", "text/html");
res.writeHead(406);
res.end(generateHtmlError(`<p>${escapeHtml((error as Error).message)}</p>`));
}
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;
}
} else {
res.setHeader("Content-Type", "text/html");
res.writeHead(200);
res.end(generateHtmlForm(currentSettings, await findAllDevices()));
}
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, () => {
@@ -498,21 +214,37 @@ async function startOnboardingServer(): Promise<boolean> {
return success;
}
async function startFailureServer(errors: string): Promise<void> {
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) => {
if (req.method === "POST") {
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();
});
} else {
res.setHeader("Content-Type", "text/html");
res.writeHead(406);
res.end(generateHtmlError(errors));
return;
}
const next = finalhandler(req, res);
fileServer(req, res, next);
});
server.listen(Number.parseInt(serverUrl.port, 10), serverUrl.hostname, () => {
@@ -524,23 +256,19 @@ async function startFailureServer(errors: string): Promise<void> {
}
async function onSettingsErrors(errors: string[]): Promise<void> {
let pErrors = "";
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}`);
pErrors += `<p>- ${escapeHtml(error)}</p>`;
}
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(pErrors);
await startFailureServer(errors);
}
}
+19 -5
View File
@@ -88,7 +88,7 @@
"title": "Pause on backoff greater than",
"default": 0,
"minimum": 0,
"description": "Pause availability pings when backoff reaches over this limit until a new Zigbee message is received from the device. A value of zero disables pausing."
"description": "Pause availability pings when the backoff multiplier reaches over this limit until a new Zigbee message is received from the device. A value of zero disables pausing."
}
},
"required": ["timeout"]
@@ -800,7 +800,7 @@
"requiresRestart": true,
"minimum": -128,
"maximum": 127,
"description": "Transmit power of adapter, only available for Z-Stack (CC253*/CC2652/CC1352) adapters, CC2652 = 5dbm, CC1352 max is = 20dbm (5dbm default)"
"description": "Transmit power of adapter, in dBm (max is often 20, refer to chip specifications)"
},
"output": {
"type": "string",
@@ -808,6 +808,13 @@
"title": "MQTT output type",
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)",
"default": "json"
},
"enable_external_js": {
"type": "boolean",
"title": "Enable external JS",
"description": "Enable external JavaScript (extensions and converters) that can execute arbitrary user-provided code. WARNING: If unused, it is advised to disable this.",
"default": true,
"requiresRestart": true
}
}
},
@@ -848,7 +855,8 @@
"retain": {
"type": "boolean",
"title": "Retain",
"description": "Retain MQTT messages of this device"
"description": "Retain MQTT messages of this device",
"default": false
},
"disabled": {
"type": "boolean",
@@ -953,10 +961,16 @@
"type": "string"
},
"retain": {
"type": "boolean"
"type": "boolean",
"title": "Retain",
"description": "Retain MQTT messages of this group",
"default": false
},
"optimistic": {
"type": "boolean"
"type": "boolean",
"title": "Optimistic",
"description": "Publish the expected state of group members after set",
"default": true
},
"qos": {
"type": ["number"],
+2
View File
@@ -113,6 +113,7 @@ export const defaults = {
network_key: [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13],
timestamp_format: "YYYY-MM-DD HH:mm:ss",
output: "json",
enable_external_js: true,
},
health: {
interval: 10,
@@ -167,6 +168,7 @@ export function writeMinimalDefaults(): void {
network_key: "GENERATE",
pan_id: "GENERATE",
ext_pan_id: "GENERATE",
enable_external_js: false,
},
frontend: {
enabled: defaults.frontend.enabled,
+13 -13
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "2.9.1",
"version": "2.11.0",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"types": "dist/types/api.d.ts",
@@ -42,44 +42,44 @@
},
"homepage": "https://koenkk.github.io/zigbee2mqtt",
"dependencies": {
"ajv": "^8.18.0",
"ajv": "^8.20.0",
"bind-decorator": "^1.0.11",
"debounce": "^3.0.0",
"express-static-gzip": "^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.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"jszip": "^3.10.1",
"mqtt": "^5.15.0",
"mqtt": "^5.15.1",
"object-assign-deep": "^0.4.0",
"rimraf": "^6.1.3",
"semver": "^7.7.4",
"semver": "^7.8.1",
"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.19.0",
"zigbee-herdsman": "9.0.9",
"zigbee-herdsman-converters": "26.12.0",
"ws": "^8.21.0",
"zigbee-herdsman": "10.1.0",
"zigbee-herdsman-converters": "26.61.1",
"zigbee2mqtt-frontend": "0.9.21",
"zigbee2mqtt-windfront": "2.9.2"
"zigbee2mqtt-windfront": "2.11.3"
},
"devDependencies": {
"@biomejs/biome": "^2.4.4",
"@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.11.0",
"@types/node": "^24.12.4",
"@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.5",
"typescript": "^5.9.3",
"tmp": "^0.2.7",
"typescript": "^6.0.3",
"vitest": "^3.1.1"
},
"pnpm": {
+187 -206
View File
@@ -5,15 +5,15 @@ settings:
excludeLinksFromLockfile: false
overrides:
zigbee-herdsman: 9.0.9
zigbee-herdsman: 10.1.0
importers:
.:
dependencies:
ajv:
specifier: ^8.18.0
version: 8.18.0
specifier: ^8.20.0
version: 8.20.0
bind-decorator:
specifier: ^1.0.11
version: 1.0.11
@@ -21,8 +21,8 @@ importers:
specifier: ^3.0.0
version: 3.0.0
express-static-gzip:
specifier: ^3.0.0
version: 3.0.0
specifier: ^3.0.1
version: 3.0.1
fast-deep-equal:
specifier: ^3.1.3
version: 3.1.3
@@ -42,8 +42,8 @@ importers:
specifier: ^3.10.1
version: 3.10.1
mqtt:
specifier: ^5.15.0
version: 5.15.0
specifier: ^5.15.1
version: 5.15.1
object-assign-deep:
specifier: ^0.4.0
version: 0.4.0
@@ -51,8 +51,8 @@ importers:
specifier: ^6.1.3
version: 6.1.3
semver:
specifier: ^7.7.4
version: 7.7.4
specifier: ^7.8.1
version: 7.8.1
source-map-support:
specifier: ^0.5.21
version: 0.5.21
@@ -69,24 +69,24 @@ importers:
specifier: ^4.9.0
version: 4.9.0
ws:
specifier: ^8.19.0
version: 8.19.0
specifier: ^8.21.0
version: 8.21.0
zigbee-herdsman:
specifier: 9.0.9
version: 9.0.9
specifier: 10.1.0
version: 10.1.0
zigbee-herdsman-converters:
specifier: 26.12.0
version: 26.12.0
specifier: 26.61.1
version: 26.61.1
zigbee2mqtt-frontend:
specifier: 0.9.21
version: 0.9.21
zigbee2mqtt-windfront:
specifier: 2.9.2
version: 2.9.2
specifier: 2.11.3
version: 2.11.3
devDependencies:
'@biomejs/biome':
specifier: ^2.4.4
version: 2.4.4
specifier: ^2.4.16
version: 2.4.16
'@types/finalhandler':
specifier: ^1.2.3
version: 1.2.4
@@ -97,8 +97,8 @@ importers:
specifier: ^4.0.9
version: 4.0.9
'@types/node':
specifier: ^24.11.0
version: 24.11.0
specifier: ^24.12.4
version: 24.12.4
'@types/object-assign-deep':
specifier: ^0.4.3
version: 0.4.3
@@ -113,16 +113,16 @@ importers:
version: 8.18.1
'@vitest/coverage-v8':
specifier: ^3.1.1
version: 3.2.4(vitest@3.2.4(@types/node@24.11.0))
version: 3.2.4(vitest@3.2.4(@types/node@24.12.4))
tmp:
specifier: ^0.2.5
version: 0.2.5
specifier: ^0.2.7
version: 0.2.7
typescript:
specifier: ^5.9.3
version: 5.9.3
specifier: ^6.0.3
version: 6.0.3
vitest:
specifier: ^3.1.1
version: 3.2.4(@types/node@24.11.0)
version: 3.2.4(@types/node@24.12.4)
optionalDependencies:
unix-dgram:
specifier: ^2.0.7
@@ -147,8 +147,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/runtime@7.28.6':
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
'@babel/runtime@7.29.2':
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
'@babel/types@7.27.6':
@@ -159,55 +159,55 @@ packages:
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
'@biomejs/biome@2.4.4':
resolution: {integrity: sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q==}
'@biomejs/biome@2.4.16':
resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
engines: {node: '>=14.21.3'}
hasBin: true
'@biomejs/cli-darwin-arm64@2.4.4':
resolution: {integrity: sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A==}
'@biomejs/cli-darwin-arm64@2.4.16':
resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@2.4.4':
resolution: {integrity: sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg==}
'@biomejs/cli-darwin-x64@2.4.16':
resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@2.4.4':
resolution: {integrity: sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ==}
'@biomejs/cli-linux-arm64-musl@2.4.16':
resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-arm64@2.4.4':
resolution: {integrity: sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg==}
'@biomejs/cli-linux-arm64@2.4.16':
resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-x64-musl@2.4.4':
resolution: {integrity: sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g==}
'@biomejs/cli-linux-x64-musl@2.4.16':
resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-linux-x64@2.4.4':
resolution: {integrity: sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg==}
'@biomejs/cli-linux-x64@2.4.16':
resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-win32-arm64@2.4.4':
resolution: {integrity: sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q==}
'@biomejs/cli-win32-arm64@2.4.16':
resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
'@biomejs/cli-win32-x64@2.4.4':
resolution: {integrity: sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A==}
'@biomejs/cli-win32-x64@2.4.16':
resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
@@ -553,8 +553,8 @@ packages:
'@types/js-yaml@4.0.9':
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
'@types/node@24.11.0':
resolution: {integrity: sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==}
'@types/node@24.12.4':
resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==}
'@types/object-assign-deep@0.4.3':
resolution: {integrity: sha512-d9Gxaj5j1hzrxJ61EFEg13B4g4FgrT/DYtcDWFXPehR8DF2SUZbVMFtZIs8exkVRiqrqBpdTc/lUUZjncsPpMw==}
@@ -613,8 +613,8 @@ packages:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
ajv@8.18.0:
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
ajv@8.20.0:
resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@@ -667,15 +667,15 @@ packages:
bonjour-service@1.3.0:
resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
brace-expansion@2.1.0:
resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==}
brace-expansion@5.0.2:
resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==}
engines: {node: 20 || >=22}
brace-expansion@5.0.6:
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
engines: {node: 18 || 20 || >=22}
broker-factory@3.1.13:
resolution: {integrity: sha512-H2VALe31mEtO/SRcNp4cUU5BAm1biwhc/JaF77AigUuni/1YT0FLCJfbUxwIEs9y6Kssjk2fmXgf+Y9ALvmKlw==}
broker-factory@3.1.14:
resolution: {integrity: sha512-L45k5HMbPIrMid0nTOZ/UPXG/c0aRuQKVrSDFIb1zOkvfiyHgYmIjc3cSiN1KwQIvRDOtKE0tfb3I9EZ3CmpQQ==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -745,15 +745,6 @@ packages:
supports-color:
optional: true
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -824,14 +815,14 @@ packages:
resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
engines: {node: '>=12.0.0'}
express-static-gzip@3.0.0:
resolution: {integrity: sha512-36O10S0asHl3QojOBQQ0ZjXNtElmhgPS6erSUCCZymXkB/CK1mnGqOj4BTJN+FYRDIzVFnzo3wLFCZJvAk6rQQ==}
express-static-gzip@3.0.1:
resolution: {integrity: sha512-LMeU/3YjFlFUa4vrPX+RoMMRW5mIpF4Iysgs6gX7A59WCY4BzyF3O28mBr4eMlWuW4DU9wVAVuVcfx29ln1N6g==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-unique-numbers@9.0.26:
resolution: {integrity: sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig==}
fast-unique-numbers@9.0.27:
resolution: {integrity: sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==}
engines: {node: '>=18.2.0'}
fast-uri@3.1.0:
@@ -894,8 +885,8 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
humanize-duration@3.33.2:
@@ -990,8 +981,8 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lru-cache@11.2.6:
resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==}
lru-cache@11.2.7:
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
engines: {node: 20 || >=22}
magic-string@0.30.17:
@@ -1008,9 +999,9 @@ packages:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@3.0.1:
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
engines: {node: '>= 0.6'}
mime-types@3.0.2:
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
engines: {node: '>=18'}
minimatch@10.2.0:
resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==}
@@ -1030,8 +1021,8 @@ packages:
mqtt-packet@9.0.2:
resolution: {integrity: sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==}
mqtt@5.15.0:
resolution: {integrity: sha512-KC+wAssYk83Qu5bT8YDzDYgUJxPhbLeVsDvpY2QvL28PnXYJzC2WkKruyMUgBAZaQ7h9lo9k2g4neRNUUxzgMw==}
mqtt@5.15.1:
resolution: {integrity: sha512-V1WnkGuJh3ec9QXzy5Iylw8OOBK+Xu1WhxcQ9mMpLThG+/JZIMV1PgLNRgIiqXhZnvnVLsuyxHl5A/3bHHbcAA==}
engines: {node: '>=16.0.0'}
hasBin: true
@@ -1164,17 +1155,17 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
semver@7.8.1:
resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
engines: {node: '>=10'}
hasBin: true
send@1.2.0:
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
serve-static@2.2.0:
resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
setimmediate@1.0.5:
@@ -1230,10 +1221,6 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -1306,8 +1293,8 @@ packages:
resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==}
engines: {node: '>=14.0.0'}
tmp@0.2.5:
resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==}
tmp@0.2.7:
resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==}
engines: {node: '>=14.14'}
toidentifier@1.0.1:
@@ -1324,8 +1311,8 @@ packages:
typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
typescript@6.0.3:
resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
engines: {node: '>=14.17'}
hasBin: true
@@ -1440,17 +1427,17 @@ packages:
resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==}
engines: {node: '>= 12.0.0'}
worker-factory@7.0.48:
resolution: {integrity: sha512-CGmBy3tJvpBPjUvb0t4PrpKubUsfkI1Ohg0/GGFU2RvA9j/tiVYwKU8O7yu7gH06YtzbeJLzdUR29lmZKn5pag==}
worker-factory@7.0.49:
resolution: {integrity: sha512-lW7tpgy6aUv2dFsQhv1yv+XFzdkCf/leoKRTGMPVK5/die6RrUjqgJHJf556qO+ZfytNG6wPXc17E8zzsOLUDw==}
worker-timers-broker@8.0.15:
resolution: {integrity: sha512-Te+EiVUMzG5TtHdmaBZvBrZSFNauym6ImDaCAnzQUxvjnw+oGjMT2idmAOgDy30vOZMLejd0bcsc90Axu6XPWA==}
worker-timers-broker@8.0.16:
resolution: {integrity: sha512-JyP3AvUGyPGbBGW7XiUewm2+0pN/aYo1QpVf5kdXAfkDZcN3p7NbWrG6XnyDEpDIvfHk/+LCnOW/NsuiU9riYA==}
worker-timers-worker@9.0.13:
resolution: {integrity: sha512-qjn18szGb1kjcmh2traAdki1eiIS5ikFo+L90nfMOvSRpuDw1hAcR1nzkP2+Hkdqz5thIRnfuWx7QSpsEUsA6Q==}
worker-timers-worker@9.0.14:
resolution: {integrity: sha512-/qF06C60sXmSLfUl7WglvrDIbspmPOM8UrG63Dnn4bi2x4/DfqHS/+dxF5B+MdHnYO5tVuZYLHdAodrKdabTIg==}
worker-timers@8.0.29:
resolution: {integrity: sha512-9jk0MWHhWAZ2xlJPXr45oe5UF/opdpfZrY0HtyPizWuJ+ce1M3IYk/4IIdGct3kn9Ncfs+tkZt3w1tU6KW2Fsg==}
worker-timers@8.0.31:
resolution: {integrity: sha512-ngkq5S6JuZyztom8tDgBzorLo9byhBMko/sXfgiUD945AuzKGg1GCgDMCC3NaYkicLpGKXutONM36wEX8UbBCA==}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
@@ -1460,8 +1447,8 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
ws@8.19.0:
resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
ws@8.21.0:
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -1472,12 +1459,12 @@ packages:
utf-8-validate:
optional: true
zigbee-herdsman-converters@26.12.0:
resolution: {integrity: sha512-Cvla9M0+05FPuFyVb9Qzq6WWZqM7fq5Znu1OmQAdBZBfeIwnc750JKM9fTKjC4kquPIsbrekQ9sNwoJEet4YxQ==}
zigbee-herdsman-converters@26.61.1:
resolution: {integrity: sha512-cCt42o58p0KpdzeUb5zLkldnLI9nIOcOdydkV+XyI7VH95LTjCbW89ax71XV3uRV1rnLHZLeEVXsFzf2U7w8aA==}
engines: {node: '>=20.15.0'}
zigbee-herdsman@9.0.9:
resolution: {integrity: sha512-Q15/Kl028AznkcWlxeD9a7OIHK06rO6+981CuPRXxM+wlHl5dFIu/HZlUwcFkpDtFBBcCvJuWzqwkuXVHAG9Rw==}
zigbee-herdsman@10.1.0:
resolution: {integrity: sha512-qFl/QsHgnYXn3LhqqsP3ZgmdQtHxiZ5JRwUpy1KaGnmR33In99f0ZNsoekxfuXFHmvxvQkqoCPFEqV1iukS+lg==}
zigbee-on-host@0.2.4:
resolution: {integrity: sha512-NIG6CWp+Yfn7PjqEIRvenHqpwT1U7rSkyimnFOUIFpnmxQOJKrmcwWDfn6WjbU/YIYYWSQPlj+g13icu1xwlyg==}
@@ -1487,8 +1474,8 @@ packages:
resolution: {integrity: sha512-ClHYlG7g0v/tnIsD6kycNCi1ZEKIbQuU7lGJR3WqYcDmuqEMVcHDGbRiRwYRQcZGMxkqCf7guxNsXiKnC10+kg==}
engines: {node: '>=20.11'}
zigbee2mqtt-windfront@2.9.2:
resolution: {integrity: sha512-weP7q0hLqXRf4tc94tzl2NGxl3DXMopgCwluMD3HKQiRMyjt3F1xy3NeoJu6Q2o1644cbRg8C+SQnVQUZJVyCw==}
zigbee2mqtt-windfront@2.11.3:
resolution: {integrity: sha512-CANPCKiJMsVD6NEbtJprDGvatQ62txBWPV1sCcdR5cvR7rXuGsHlJjj1N/YJGbsQ0N+eN8+CyBXzNLj1YWcRdQ==}
engines: {node: '>=22.12.0'}
snapshots:
@@ -1506,7 +1493,7 @@ snapshots:
dependencies:
'@babel/types': 7.27.6
'@babel/runtime@7.28.6': {}
'@babel/runtime@7.29.2': {}
'@babel/types@7.27.6':
dependencies:
@@ -1515,39 +1502,39 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
'@biomejs/biome@2.4.4':
'@biomejs/biome@2.4.16':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 2.4.4
'@biomejs/cli-darwin-x64': 2.4.4
'@biomejs/cli-linux-arm64': 2.4.4
'@biomejs/cli-linux-arm64-musl': 2.4.4
'@biomejs/cli-linux-x64': 2.4.4
'@biomejs/cli-linux-x64-musl': 2.4.4
'@biomejs/cli-win32-arm64': 2.4.4
'@biomejs/cli-win32-x64': 2.4.4
'@biomejs/cli-darwin-arm64': 2.4.16
'@biomejs/cli-darwin-x64': 2.4.16
'@biomejs/cli-linux-arm64': 2.4.16
'@biomejs/cli-linux-arm64-musl': 2.4.16
'@biomejs/cli-linux-x64': 2.4.16
'@biomejs/cli-linux-x64-musl': 2.4.16
'@biomejs/cli-win32-arm64': 2.4.16
'@biomejs/cli-win32-x64': 2.4.16
'@biomejs/cli-darwin-arm64@2.4.4':
'@biomejs/cli-darwin-arm64@2.4.16':
optional: true
'@biomejs/cli-darwin-x64@2.4.4':
'@biomejs/cli-darwin-x64@2.4.16':
optional: true
'@biomejs/cli-linux-arm64-musl@2.4.4':
'@biomejs/cli-linux-arm64-musl@2.4.16':
optional: true
'@biomejs/cli-linux-arm64@2.4.4':
'@biomejs/cli-linux-arm64@2.4.16':
optional: true
'@biomejs/cli-linux-x64-musl@2.4.4':
'@biomejs/cli-linux-x64-musl@2.4.16':
optional: true
'@biomejs/cli-linux-x64@2.4.4':
'@biomejs/cli-linux-x64@2.4.16':
optional: true
'@biomejs/cli-win32-arm64@2.4.4':
'@biomejs/cli-win32-arm64@2.4.16':
optional: true
'@biomejs/cli-win32-x64@2.4.4':
'@biomejs/cli-win32-x64@2.4.16':
optional: true
'@colors/colors@1.6.0': {}
@@ -1770,7 +1757,7 @@ snapshots:
'@types/finalhandler@1.2.4':
dependencies:
'@types/node': 24.11.0
'@types/node': 24.12.4
'@types/http-errors@2.0.5': {}
@@ -1778,7 +1765,7 @@ snapshots:
'@types/js-yaml@4.0.9': {}
'@types/node@24.11.0':
'@types/node@24.12.4':
dependencies:
undici-types: 7.16.0
@@ -1786,25 +1773,25 @@ snapshots:
'@types/readable-stream@4.0.23':
dependencies:
'@types/node': 24.11.0
'@types/node': 24.12.4
'@types/serve-static@2.2.0':
dependencies:
'@types/http-errors': 2.0.5
'@types/node': 24.11.0
'@types/node': 24.12.4
'@types/triple-beam@1.3.5': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 24.11.0
'@types/node': 24.12.4
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.11.0))':
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.12.4))':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
ast-v8-to-istanbul: 0.3.3
debug: 4.4.1
debug: 4.4.3
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6
@@ -1814,7 +1801,7 @@ snapshots:
std-env: 3.9.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
vitest: 3.2.4(@types/node@24.11.0)
vitest: 3.2.4(@types/node@24.12.4)
transitivePeerDependencies:
- supports-color
@@ -1826,13 +1813,13 @@ snapshots:
chai: 5.2.0
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.11.0))':
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.12.4))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.3.5(@types/node@24.11.0)
vite: 6.3.5(@types/node@24.12.4)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -1864,7 +1851,7 @@ snapshots:
dependencies:
event-target-shim: 5.0.1
ajv@8.18.0:
ajv@8.20.0:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.1.0
@@ -1920,20 +1907,20 @@ snapshots:
fast-deep-equal: 3.1.3
multicast-dns: 7.2.5
brace-expansion@2.0.2:
brace-expansion@2.1.0:
dependencies:
balanced-match: 1.0.2
brace-expansion@5.0.2:
brace-expansion@5.0.6:
dependencies:
balanced-match: 4.0.2
broker-factory@3.1.13:
broker-factory@3.1.14:
dependencies:
'@babel/runtime': 7.28.6
fast-unique-numbers: 9.0.26
'@babel/runtime': 7.29.2
fast-unique-numbers: 9.0.27
tslib: 2.8.1
worker-factory: 7.0.48
worker-factory: 7.0.49
buffer-from@1.1.2: {}
@@ -1998,10 +1985,6 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.4.1:
dependencies:
ms: 2.1.3
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -2070,19 +2053,19 @@ snapshots:
expect-type@1.2.1: {}
express-static-gzip@3.0.0:
express-static-gzip@3.0.1:
dependencies:
mime-types: 3.0.1
mime-types: 3.0.2
parseurl: 1.3.3
serve-static: 2.2.0
serve-static: 2.2.1
transitivePeerDependencies:
- supports-color
fast-deep-equal@3.1.3: {}
fast-unique-numbers@9.0.26:
fast-unique-numbers@9.0.27:
dependencies:
'@babel/runtime': 7.28.6
'@babel/runtime': 7.29.2
tslib: 2.8.1
fast-uri@3.1.0: {}
@@ -2142,12 +2125,12 @@ snapshots:
html-escaper@2.0.2: {}
http-errors@2.0.0:
http-errors@2.0.1:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.1
statuses: 2.0.2
toidentifier: 1.0.1
humanize-duration@3.33.2: {}
@@ -2241,7 +2224,7 @@ snapshots:
lru-cache@10.4.3: {}
lru-cache@11.2.6: {}
lru-cache@11.2.7: {}
magic-string@0.30.17:
dependencies:
@@ -2255,21 +2238,21 @@ snapshots:
make-dir@4.0.0:
dependencies:
semver: 7.7.4
semver: 7.8.1
mime-db@1.54.0: {}
mime-types@3.0.1:
mime-types@3.0.2:
dependencies:
mime-db: 1.54.0
minimatch@10.2.0:
dependencies:
brace-expansion: 5.0.2
brace-expansion: 5.0.6
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
brace-expansion: 2.1.0
minimist@1.2.8: {}
@@ -2283,7 +2266,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
mqtt@5.15.0:
mqtt@5.15.1:
dependencies:
'@types/readable-stream': 4.0.23
'@types/ws': 8.18.1
@@ -2299,8 +2282,8 @@ snapshots:
rfdc: 1.4.1
socks: 2.8.7
split2: 4.2.0
worker-timers: 8.0.29
ws: 8.19.0
worker-timers: 8.0.31
ws: 8.21.0
transitivePeerDependencies:
- bufferutil
- supports-color
@@ -2354,7 +2337,7 @@ snapshots:
path-scurry@2.0.1:
dependencies:
lru-cache: 11.2.6
lru-cache: 11.2.7
minipass: 7.1.2
pathe@2.0.3: {}
@@ -2444,17 +2427,17 @@ snapshots:
safer-buffer@2.1.2: {}
semver@7.7.4: {}
semver@7.8.1: {}
send@1.2.0:
send@1.2.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 2.0.0
http-errors: 2.0.0
mime-types: 3.0.1
http-errors: 2.0.1
mime-types: 3.0.2
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
@@ -2462,12 +2445,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
serve-static@2.2.0:
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 1.2.0
send: 1.2.1
transitivePeerDependencies:
- supports-color
@@ -2509,8 +2492,6 @@ snapshots:
stackback@0.0.2: {}
statuses@2.0.1: {}
statuses@2.0.2: {}
std-env@3.9.0: {}
@@ -2578,7 +2559,7 @@ snapshots:
tinyspy@4.0.3: {}
tmp@0.2.5: {}
tmp@0.2.7: {}
toidentifier@1.0.1: {}
@@ -2588,7 +2569,7 @@ snapshots:
typedarray@0.0.6: {}
typescript@5.9.3: {}
typescript@6.0.3: {}
undici-types@7.16.0: {}
@@ -2606,13 +2587,13 @@ snapshots:
util-deprecate@1.0.2: {}
vite-node@3.2.4(@types/node@24.11.0):
vite-node@3.2.4(@types/node@24.12.4):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.3.5(@types/node@24.11.0)
vite: 6.3.5(@types/node@24.12.4)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -2627,7 +2608,7 @@ snapshots:
- tsx
- yaml
vite@6.3.5(@types/node@24.11.0):
vite@6.3.5(@types/node@24.12.4):
dependencies:
esbuild: 0.25.5
fdir: 6.4.6(picomatch@4.0.2)
@@ -2636,21 +2617,21 @@ snapshots:
rollup: 4.44.0
tinyglobby: 0.2.14
optionalDependencies:
'@types/node': 24.11.0
'@types/node': 24.12.4
fsevents: 2.3.3
vitest@3.2.4(@types/node@24.11.0):
vitest@3.2.4(@types/node@24.12.4):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.11.0))
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.12.4))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.2.0
debug: 4.4.1
debug: 4.4.3
expect-type: 1.2.1
magic-string: 0.30.17
pathe: 2.0.3
@@ -2661,11 +2642,11 @@ snapshots:
tinyglobby: 0.2.14
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 6.3.5(@types/node@24.11.0)
vite-node: 3.2.4(@types/node@24.11.0)
vite: 6.3.5(@types/node@24.12.4)
vite-node: 3.2.4(@types/node@24.12.4)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 24.11.0
'@types/node': 24.12.4
transitivePeerDependencies:
- jiti
- less
@@ -2718,32 +2699,32 @@ snapshots:
triple-beam: 1.4.1
winston-transport: 4.9.0
worker-factory@7.0.48:
worker-factory@7.0.49:
dependencies:
'@babel/runtime': 7.28.6
fast-unique-numbers: 9.0.26
'@babel/runtime': 7.29.2
fast-unique-numbers: 9.0.27
tslib: 2.8.1
worker-timers-broker@8.0.15:
worker-timers-broker@8.0.16:
dependencies:
'@babel/runtime': 7.28.6
broker-factory: 3.1.13
fast-unique-numbers: 9.0.26
'@babel/runtime': 7.29.2
broker-factory: 3.1.14
fast-unique-numbers: 9.0.27
tslib: 2.8.1
worker-timers-worker: 9.0.13
worker-timers-worker: 9.0.14
worker-timers-worker@9.0.13:
worker-timers-worker@9.0.14:
dependencies:
'@babel/runtime': 7.28.6
'@babel/runtime': 7.29.2
tslib: 2.8.1
worker-factory: 7.0.48
worker-factory: 7.0.49
worker-timers@8.0.29:
worker-timers@8.0.31:
dependencies:
'@babel/runtime': 7.28.6
'@babel/runtime': 7.29.2
tslib: 2.8.1
worker-timers-broker: 8.0.15
worker-timers-worker: 9.0.13
worker-timers-broker: 8.0.16
worker-timers-worker: 9.0.14
wrap-ansi@7.0.0:
dependencies:
@@ -2757,17 +2738,17 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.2
ws@8.19.0: {}
ws@8.21.0: {}
zigbee-herdsman-converters@26.12.0:
zigbee-herdsman-converters@26.61.1:
dependencies:
iconv-lite: 0.7.2
semver: 7.7.4
zigbee-herdsman: 9.0.9
semver: 7.8.1
zigbee-herdsman: 10.1.0
transitivePeerDependencies:
- supports-color
zigbee-herdsman@9.0.9:
zigbee-herdsman@10.1.0:
dependencies:
'@date-fns/tz': 1.4.1
'@serialport/bindings-cpp': 13.0.1
@@ -2785,4 +2766,4 @@ snapshots:
zigbee2mqtt-frontend@0.9.21: {}
zigbee2mqtt-windfront@2.9.2: {}
zigbee2mqtt-windfront@2.11.3: {}
+13 -23
View File
@@ -1,4 +1,12 @@
import {execSync} from "node:child_process";
import {execFileSync} from "node:child_process";
function grepTypeScriptFiles(needle, dir) {
try {
return execFileSync("grep", ["-r", "-F", "--include=*.ts", "--", needle, dir], {encoding: "utf8"});
} catch {
return undefined;
}
}
async function checkDuplicateIssue(github, context, name) {
// Search for existing issues with the same `name`
@@ -63,13 +71,7 @@ export async function newDeviceSupport(github, _core, context, zhcDir) {
if (tuyaManufacturerNames.length > 0) {
for (const [fullName, partialName] of tuyaManufacturerNames) {
if (await checkDuplicateIssue(github, context, fullName)) return;
const fullMatch = (() => {
try {
return execSync(`grep -r --include="*.ts" "${fullName}" "${zhcDir}"`, {encoding: "utf8"});
} catch {
return undefined;
}
})();
const fullMatch = grepTypeScriptFiles(fullName, zhcDir);
console.log(`Checking full match for '${fullName}', result: '${fullMatch}'`);
if (fullMatch) {
@@ -94,13 +96,7 @@ If you need help with the process, feel free to ask here and we'll be happy to a
return;
}
const partialMatch = (() => {
try {
return execSync(`grep -r --include="*.ts" "${partialName}" "${zhcDir}"`, {encoding: "utf8"});
} catch {
return undefined;
}
})();
const partialMatch = grepTypeScriptFiles(partialName, zhcDir);
console.log(`Checking partial match for '${partialName}', result: '${partialMatch}'`);
if (partialMatch) {
@@ -129,14 +125,8 @@ Let us know if it works so we can support this device out-of-the-box!`,
if (zigbeeModels.length > 0) {
for (const zigbeeModel of zigbeeModels) {
if (await checkDuplicateIssue(github, context, fullName)) return;
const fullMatch = (() => {
try {
return execSync(`grep -r --include="*.ts" '"${zigbeeModel}"' "${zhcDir}"`, {encoding: "utf8"});
} catch {
return undefined;
}
})();
if (await checkDuplicateIssue(github, context, zigbeeModel)) return;
const fullMatch = grepTypeScriptFiles(`"${zigbeeModel}"`, zhcDir);
if (fullMatch) {
await github.rest.issues.createComment({
+2 -2
View File
@@ -18,5 +18,5 @@ const sandbox = {
};
vm.runInNewContext(moduleCode, sandbox, moduleFakePath);
const converter = sandbox.module.exports;
assert(!converter.toZigbee || !converter.toZigbee.includes(undefined));
assert(!converter.fromZigbee || !converter.fromZigbee.includes(undefined));
assert(!converter.toZigbee?.includes(undefined));
assert(!converter.fromZigbee?.includes(undefined));
+1 -1
View File
@@ -112,7 +112,7 @@ describe("Controller", () => {
expect(mockLogger.info).toHaveBeenCalledWith(
"bulb (0x000b57fffec6a5b2): LED1545G12 - IKEA TRADFRI bulb E26/E27, white spectrum, globe, opal, 980 lm (Router)",
);
expect(mockLogger.info).toHaveBeenCalledWith("remote (0x0017880104e45517): 324131092621 - Philips Hue dimmer switch (EndDevice)");
expect(mockLogger.info).toHaveBeenCalledWith("remote (0x0017880104e45517): 324131092621 - Philips Hue dimmer switch gen 1 (EndDevice)");
expect(mockLogger.info).toHaveBeenCalledWith("0x0017880104e45518 (0x0017880104e45518): Not supported (EndDevice)");
expect(mockMQTTConnectAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTConnectAsync).toHaveBeenCalledWith("mqtt://localhost", {
+75 -12
View File
@@ -144,6 +144,7 @@ describe("Extension: Bridge", () => {
output: "json",
pan_id: 6754,
timestamp_format: "YYYY-MM-DD HH:mm:ss",
enable_external_js: true,
},
blocklist: [],
device_options: {},
@@ -597,6 +598,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: true,
vendor: "IKEA",
version: "0.0.0",
},
description: "this is my bulb",
disabled: false,
@@ -734,7 +736,7 @@ describe("Extension: Bridge", () => {
values: ["off", "on", "toggle", "previous"],
},
{
access: 2,
access: 3,
label: "Effect",
name: "effect",
property: "effect",
@@ -747,11 +749,41 @@ describe("Extension: Bridge", () => {
"candle",
"fireplace",
"colorloop",
"sunset",
"sunrise",
"sparkle",
"opal",
"glisten",
"underwater",
"cosmos",
"sunbeam",
"enchant",
"none",
"finish_effect",
"stop_effect",
"stop_hue_effect",
],
},
{
access: 3,
description: "Animation speed for the active effect (0=slowest, 1=fastest)",
label: "Effect speed",
name: "effect_speed",
property: "effect_speed",
type: "numeric",
value_max: 1,
value_min: 0,
value_step: 0.01,
},
{
access: 2,
description:
'Set the base color of the active effect without stopping it (hex e.g. #FF4400, or JSON {"x":0.6,"y":0.3})',
label: "Effect color",
name: "effect_color",
property: "effect_color",
type: "text",
},
{
access: 1,
category: "diagnostic",
@@ -767,6 +799,27 @@ describe("Extension: Bridge", () => {
],
model: "7146060PH",
options: [
{
access: 2,
description:
"Control this light using a Philips-specific protocol instead of standard Zigbee commands. When enabled, on/off, brightness, color, and color temperature are combined into single atomic commands. This is required to use the Effect color update mode. When disabled (default), standard Zigbee commands are used, which preserves the usual behavior, including simulating on/off transitions.",
label: "Hue native control",
name: "hue_native_control",
property: "hue_native_control",
type: "binary",
value_off: false,
value_on: true,
},
{
access: 2,
description:
"Controls what happens when color is changed while an effect is active (requires Hue native control). 'stop' (default): color change stops the effect (Hue app behavior). 'update': color change re-sends the effect with the new color.",
label: "Effect color mode",
name: "effect_color_mode",
property: "effect_color_mode",
type: "enum",
values: ["stop", "update"],
},
{
access: 2,
description:
@@ -778,6 +831,16 @@ describe("Extension: Bridge", () => {
value_min: 0,
value_step: 0.1,
},
{
access: 2,
description: "State actions will also be published as 'action' when true (default false).",
label: "State action",
name: "state_action",
property: "state_action",
type: "binary",
value_off: false,
value_on: true,
},
{
access: 2,
description:
@@ -789,19 +852,10 @@ describe("Extension: Bridge", () => {
value_off: false,
value_on: true,
},
{
access: 2,
description: "State actions will also be published as 'action' when true (default false).",
label: "State action",
name: "state_action",
property: "state_action",
type: "binary",
value_off: false,
value_on: true,
},
],
supports_ota: true,
vendor: "Philips",
version: "0.0.0",
},
disabled: false,
endpoints: {
@@ -831,7 +885,7 @@ describe("Extension: Bridge", () => {
{
definition: {
source: "native",
description: "Hue dimmer switch",
description: "Hue dimmer switch gen 1",
exposes: [
{
access: 1,
@@ -930,6 +984,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: true,
vendor: "Philips",
version: "0.0.0",
},
disabled: false,
endpoints: {
@@ -1038,6 +1093,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: false,
vendor: "notSupportedMfg",
version: "0.0.0",
},
disabled: false,
endpoints: {
@@ -1143,6 +1199,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: false,
vendor: "Aqara",
version: "0.0.0",
},
disabled: false,
endpoints: {
@@ -1304,6 +1361,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: false,
vendor: "Aqara",
version: "0.0.0",
},
disabled: false,
endpoints: {"1": {bindings: [], clusters: {input: ["genBasic"], output: []}, configured_reportings: [], scenes: []}},
@@ -1455,6 +1513,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: true,
vendor: "Xiaomi",
version: "0.0.0",
},
disabled: false,
endpoints: {"1": {bindings: [], clusters: {input: ["genBasic", "genOnOff"], output: []}, configured_reportings: [], scenes: []}},
@@ -1914,6 +1973,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: false,
vendor: "Siglis",
version: "0.0.0",
},
disabled: false,
endpoints: {
@@ -2204,6 +2264,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: true,
vendor: "IKEA",
version: "0.0.0",
},
disabled: false,
endpoints: {
@@ -2639,6 +2700,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: true,
vendor: "IKEA",
version: "0.0.0",
},
friendly_name: "bulb",
ieee_address: "0x000b57fffec6a5b2",
@@ -2725,6 +2787,7 @@ describe("Extension: Bridge", () => {
],
supports_ota: false,
vendor: "notSupportedMfg",
version: "0.0.0",
},
friendly_name: "0x0017880104e45518",
ieee_address: "0x0017880104e45518",
@@ -488,6 +488,25 @@ describe("Extension: ExternalConverters", () => {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/converters", stringify([]), {retain: true});
});
it("returns error on invalid name", async () => {
const converterName = "foo1";
await resetExtension();
for (const mock of mocksClear) mock.mockClear();
await (controller.getExtension("ExternalConverters")! as ExternalConverters).onMQTTMessage({
topic: "zigbee2mqtt/bridge/request/converter/save",
message: {name: converterName, code: "a"},
});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/converter/save",
expect.stringContaining(`JavaScript file must have '.mjs', '.js' or '.cjs' extension`),
{},
);
expect(writeFileSyncSpy).toHaveBeenCalledTimes(0);
});
it("returns error on invalid code", async () => {
const converterName = "foo1.js";
const converterCode = "definetly not a correct javascript code";
@@ -609,4 +628,15 @@ describe("Extension: ExternalConverters", () => {
);
});
});
it("doesn't add extension when external JS disabled", async () => {
settings.set(["advanced", "enable_external_js"], false);
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
expect(controller.getExtension("ExternalConverters")).toBeUndefined();
});
});
@@ -299,6 +299,25 @@ describe("Extension: ExternalExtensions", () => {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/extensions", stringify([]), {retain: true});
});
it("returns error on invalid name", async () => {
const extensionName = "foo1";
await resetExtension();
for (const mock of mocksClear) mock.mockClear();
await (controller.getExtension("ExternalExtensions")! as ExternalExtensions).onMQTTMessage({
topic: "zigbee2mqtt/bridge/request/extension/save",
message: {name: extensionName, code: "a"},
});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/extension/save",
expect.stringContaining(`JavaScript file must have '.mjs', '.js' or '.cjs' extension`),
{},
);
expect(writeFileSyncSpy).toHaveBeenCalledTimes(0);
});
it("returns error on invalid code", async () => {
const extensionName = "foo1.js";
const extensionCode = "definetly not a correct javascript code";
@@ -365,4 +384,15 @@ describe("Extension: ExternalExtensions", () => {
);
});
});
it("doesn't add extension when external JS disabled", async () => {
settings.set(["advanced", "enable_external_js"], false);
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
expect(controller.getExtension("ExternalExtensions")).toBeUndefined();
});
});
+6
View File
@@ -245,6 +245,8 @@ describe("Extension: Frontend", () => {
stringify({
state: "ON",
effect: null,
effect_color: null,
effect_speed: null,
power_on_behavior: null,
linkquality: 20,
update: {state: null, installed_version: -1, latest_version: -1},
@@ -269,6 +271,8 @@ describe("Extension: Frontend", () => {
state: "ON",
power_on_behavior: null,
effect: null,
effect_color: null,
effect_speed: null,
linkquality: 20,
update: {state: null, installed_version: -1, latest_version: -1},
},
@@ -296,6 +300,8 @@ describe("Extension: Frontend", () => {
brightness: 90,
power_on_behavior: null,
effect: null,
effect_color: null,
effect_speed: null,
linkquality: 20,
update: {state: null, installed_version: -1, latest_version: -1},
},
+109 -59
View File
@@ -103,6 +103,7 @@ describe("Extension: HomeAssistant", () => {
definition: d,
isDevice: (): boolean => true,
isGroup: (): boolean => false,
endpoint: () => undefined,
options: {},
exposes: (): unknown[] => exposes,
zh: {endpoints: []},
@@ -124,7 +125,7 @@ describe("Extension: HomeAssistant", () => {
}
}
expect(duplicated).toHaveLength(0);
expect(duplicated).toStrictEqual([]);
});
it("Should discover devices and groups", async () => {
@@ -163,6 +164,16 @@ describe("Extension: HomeAssistant", () => {
"candle",
"fireplace",
"colorloop",
"sunset",
"sunrise",
"sparkle",
"opal",
"glisten",
"underwater",
"cosmos",
"sunbeam",
"enchant",
"none",
"finish_effect",
"stop_effect",
"stop_hue_effect",
@@ -170,6 +181,7 @@ describe("Extension: HomeAssistant", () => {
object_id: "ha_discovery_group",
default_entity_id: "light.ha_discovery_group",
unique_id: "9_light_zigbee2mqtt",
group: ["0x000b57fffec6a5b4_light_zigbee2mqtt", "0x000b57fffec6a5b7_light_zigbee2mqtt"],
origin: origin,
};
@@ -225,8 +237,9 @@ describe("Extension: HomeAssistant", () => {
object_id: "ha_discovery_group",
default_entity_id: "switch.ha_discovery_group",
unique_id: "9_switch_zigbee2mqtt",
group: ["0x0017880104e45542_switch_right_zigbee2mqtt"],
origin: origin,
value_template: "{{ value_json.state }}",
value_template: '{{ value_json["state"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
@@ -239,7 +252,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "°C",
device_class: "temperature",
state_class: "measurement",
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_temperature",
default_entity_id: "sensor.weather_sensor_temperature",
@@ -266,7 +279,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "%",
device_class: "humidity",
state_class: "measurement",
value_template: "{{ value_json.humidity }}",
value_template: '{{ value_json["humidity"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_humidity",
default_entity_id: "sensor.weather_sensor_humidity",
@@ -293,7 +306,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "hPa",
device_class: "atmospheric_pressure",
state_class: "measurement",
value_template: "{{ value_json.pressure }}",
value_template: '{{ value_json["pressure"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_pressure",
default_entity_id: "sensor.weather_sensor_pressure",
@@ -320,7 +333,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "%",
device_class: "battery",
state_class: "measurement",
value_template: "{{ value_json.battery }}",
value_template: '{{ value_json["battery"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_battery",
default_entity_id: "sensor.weather_sensor_battery",
@@ -350,7 +363,7 @@ describe("Extension: HomeAssistant", () => {
entity_category: "diagnostic",
unit_of_measurement: "lqi",
state_class: "measurement",
value_template: "{{ value_json.linkquality }}",
value_template: '{{ value_json["linkquality"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
name: "Linkquality",
object_id: "weather_sensor_linkquality",
@@ -392,7 +405,7 @@ describe("Extension: HomeAssistant", () => {
default_entity_id: "switch.wall_switch_double_left",
unique_id: "0x0017880104e45542_switch_left_zigbee2mqtt",
origin: origin,
value_template: "{{ value_json.state_left }}",
value_template: '{{ value_json["state_left"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/switch/0x0017880104e45542/switch_left/config", stringify(payload), {
@@ -419,7 +432,7 @@ describe("Extension: HomeAssistant", () => {
default_entity_id: "switch.wall_switch_double_right",
unique_id: "0x0017880104e45542_switch_right_zigbee2mqtt",
origin: origin,
value_template: "{{ value_json.state_right }}",
value_template: '{{ value_json["state_right"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/switch/0x0017880104e45542/switch_right/config", stringify(payload), {
@@ -522,7 +535,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "%",
device_class: "humidity",
state_class: "measurement",
value_template: "{{ value_json.humidity }}",
value_template: '{{ value_json["humidity"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_humidity",
default_entity_id: "sensor.weather_sensor_humidity",
@@ -594,7 +607,7 @@ describe("Extension: HomeAssistant", () => {
device_class: "temperature",
state_class: "measurement",
enabled_by_default: true,
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_temperature",
default_entity_id: "sensor.weather_sensor_temperature",
@@ -620,7 +633,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "%",
device_class: "humidity",
state_class: "measurement",
value_template: "{{ value_json.humidity }}",
value_template: '{{ value_json["humidity"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_humidity",
default_entity_id: "sensor.weather_sensor_humidity",
@@ -647,7 +660,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "hPa",
device_class: "atmospheric_pressure",
state_class: "measurement",
value_template: "{{ value_json.pressure }}",
value_template: '{{ value_json["pressure"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
enabled_by_default: true,
object_id: "weather_sensor_pressure",
@@ -705,7 +718,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "°C",
device_class: "temperature",
state_class: "measurement",
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
enabled_by_default: true,
object_id: "weather_sensor_temperature",
@@ -735,7 +748,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "%",
device_class: "humidity",
state_class: "measurement",
value_template: "{{ value_json.humidity }}",
value_template: '{{ value_json["humidity"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
enabled_by_default: true,
device: {
@@ -778,7 +791,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "°C",
device_class: "temperature",
state_class: "measurement",
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_temperature",
default_entity_id: "sensor.weather_sensor_temperature",
@@ -805,7 +818,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "%",
device_class: "humidity",
state_class: "measurement",
value_template: "{{ value_json.humidity }}",
value_template: '{{ value_json["humidity"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_humidity",
default_entity_id: "sensor.weather_sensor_humidity",
@@ -868,7 +881,7 @@ describe("Extension: HomeAssistant", () => {
default_entity_id: "light.my_switch",
unique_id: "0x0017880104e45541_light_zigbee2mqtt",
origin: origin,
value_template: "{{ value_json.state }}",
value_template: '{{ value_json["state"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/light/0x0017880104e45541/light/config", stringify(payload), {
@@ -914,11 +927,11 @@ describe("Extension: HomeAssistant", () => {
command_topic: "zigbee2mqtt/fan/set/fan_state",
percentage_state_topic: "zigbee2mqtt/fan",
percentage_command_topic: "zigbee2mqtt/fan/set/fan_mode",
percentage_value_template: "{{ {'off':0, 'low':1, 'medium':2, 'high':3, 'on':4}[value_json.fan_mode] | default('None') }}",
percentage_value_template: "{{ {'off':0, 'low':1, 'medium':2, 'high':3, 'on':4}[value_json[\"fan_mode\"]] | default('None') }}",
percentage_command_template: "{{ {0:'off', 1:'low', 2:'medium', 3:'high', 4:'on'}[value] | default('') }}",
preset_mode_state_topic: "zigbee2mqtt/fan",
preset_mode_command_topic: "zigbee2mqtt/fan/set/fan_mode",
preset_mode_value_template: "{{ value_json.fan_mode if value_json.fan_mode in ['smart'] else 'None' | default('None') }}",
preset_mode_value_template: "{{ value_json[\"fan_mode\"] if value_json[\"fan_mode\"] in ['smart'] else 'None' | default('None') }}",
preset_modes: ["smart"],
speed_range_min: 1,
speed_range_max: 4,
@@ -990,7 +1003,7 @@ describe("Extension: HomeAssistant", () => {
command_topic: "zigbee2mqtt/fanbee/set/state",
percentage_state_topic: "zigbee2mqtt/fanbee",
percentage_command_topic: "zigbee2mqtt/fanbee/set/speed",
percentage_value_template: "{{ value_json.speed | default('None') }}",
percentage_value_template: "{{ value_json[\"speed\"] | default('None') }}",
percentage_command_template: "{{ value | default('') }}",
speed_range_min: 1,
speed_range_max: 254,
@@ -1023,7 +1036,7 @@ describe("Extension: HomeAssistant", () => {
it("Should discover thermostat devices", () => {
const payload = {
action_template:
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
action_topic: "zigbee2mqtt/TS0601_thermostat",
availability: [
{
@@ -1031,7 +1044,7 @@ describe("Extension: HomeAssistant", () => {
value_template: "{{ value_json.state }}",
},
],
current_temperature_template: "{{ value_json.local_temperature }}",
current_temperature_template: '{{ value_json["local_temperature"] }}',
current_temperature_topic: "zigbee2mqtt/TS0601_thermostat",
device: {
identifiers: ["zigbee2mqtt_0x0017882104a44559"],
@@ -1043,18 +1056,18 @@ describe("Extension: HomeAssistant", () => {
},
preset_mode_command_topic: "zigbee2mqtt/TS0601_thermostat/set/preset",
preset_modes: ["schedule", "manual", "boost", "complex", "comfort", "eco", "away"],
preset_mode_value_template: "{{ value_json.preset }}",
preset_mode_value_template: '{{ value_json["preset"] }}',
preset_mode_state_topic: "zigbee2mqtt/TS0601_thermostat",
max_temp: "35",
min_temp: "5",
mode_command_topic: "zigbee2mqtt/TS0601_thermostat/set/system_mode",
mode_state_template: "{{ value_json.system_mode }}",
mode_state_template: '{{ value_json["system_mode"] }}',
mode_state_topic: "zigbee2mqtt/TS0601_thermostat",
modes: ["heat", "auto", "off"],
name: null,
temp_step: 0.5,
temperature_command_topic: "zigbee2mqtt/TS0601_thermostat/set/current_heating_setpoint",
temperature_state_template: "{{ value_json.current_heating_setpoint }}",
temperature_state_template: '{{ value_json["current_heating_setpoint"] }}',
temperature_state_topic: "zigbee2mqtt/TS0601_thermostat",
temperature_unit: "C",
object_id: "ts0601_thermostat",
@@ -1094,7 +1107,7 @@ describe("Extension: HomeAssistant", () => {
state_topic: "zigbee2mqtt/thermostat",
unique_id: "0x0017880104e45550_pi_heating_demand_zigbee2mqtt",
unit_of_measurement: "%",
value_template: "{{ value_json.pi_heating_demand }}",
value_template: '{{ value_json["pi_heating_demand"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/sensor/0x0017880104e45550/pi_heating_demand/config", stringify(payload), {
@@ -1131,7 +1144,7 @@ describe("Extension: HomeAssistant", () => {
state_topic: "zigbee2mqtt/bosch_radiator",
unique_id: "0x18fc2600000d7ae2_pi_heating_demand_zigbee2mqtt",
unit_of_measurement: "%",
value_template: "{{ value_json.pi_heating_demand }}",
value_template: '{{ value_json["pi_heating_demand"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/number/0x18fc2600000d7ae2/pi_heating_demand/config", stringify(payload), {
@@ -1143,10 +1156,10 @@ describe("Extension: HomeAssistant", () => {
it("Should discover Bosch BTH-RA with a compatibility mapping", () => {
const payload = {
action_template:
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
action_topic: "zigbee2mqtt/bosch_radiator",
availability: [{topic: "zigbee2mqtt/bridge/state", value_template: "{{ value_json.state }}"}],
current_temperature_template: "{{ value_json.local_temperature }}",
current_temperature_template: '{{ value_json["local_temperature"] }}',
current_temperature_topic: "zigbee2mqtt/bosch_radiator",
device: {
identifiers: ["zigbee2mqtt_0x18fc2600000d7ae2"],
@@ -1171,7 +1184,7 @@ describe("Extension: HomeAssistant", () => {
origin: origin,
temp_step: 0.5,
temperature_command_topic: "zigbee2mqtt/bosch_radiator/set/occupied_heating_setpoint",
temperature_state_template: "{{ value_json.occupied_heating_setpoint }}",
temperature_state_template: '{{ value_json["occupied_heating_setpoint"] }}',
temperature_state_topic: "zigbee2mqtt/bosch_radiator",
temperature_unit: "C",
unique_id: "0x18fc2600000d7ae2_climate_zigbee2mqtt",
@@ -1198,10 +1211,10 @@ describe("Extension: HomeAssistant", () => {
const payload = {
action_template:
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
action_topic: "zigbee2mqtt/bosch_radiator",
availability: [{topic: "zigbee2mqtt/bridge/state", value_template: "{{ value_json.state }}"}],
current_temperature_template: "{{ value_json.local_temperature }}",
current_temperature_template: '{{ value_json["local_temperature"] }}',
current_temperature_topic: "zigbee2mqtt/bosch_radiator",
device: {
identifiers: ["zigbee2mqtt_0x18fc2600000d7ae2"],
@@ -1215,7 +1228,7 @@ describe("Extension: HomeAssistant", () => {
max_temp: "30",
min_temp: "5",
mode_command_topic: "zigbee2mqtt/bosch_radiator/set/system_mode",
mode_state_template: "{{ value_json.system_mode }}",
mode_state_template: '{{ value_json["system_mode"] }}',
mode_state_topic: "zigbee2mqtt/bosch_radiator",
modes: ["heat"],
name: null,
@@ -1224,7 +1237,7 @@ describe("Extension: HomeAssistant", () => {
origin: origin,
temp_step: 0.5,
temperature_command_topic: "zigbee2mqtt/bosch_radiator/set/occupied_heating_setpoint",
temperature_state_template: "{{ value_json.occupied_heating_setpoint }}",
temperature_state_template: '{{ value_json["occupied_heating_setpoint"] }}',
temperature_state_topic: "zigbee2mqtt/bosch_radiator",
temperature_unit: "C",
unique_id: "0x18fc2600000d7ae2_climate_zigbee2mqtt",
@@ -1242,12 +1255,12 @@ describe("Extension: HomeAssistant", () => {
it("Should discover Bosch BTH-RM230Z with a current_humidity attribute", () => {
const payload = {
action_template:
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
action_topic: "zigbee2mqtt/bosch_rm230z",
availability: [{topic: "zigbee2mqtt/bridge/state", value_template: "{{ value_json.state }}"}],
current_humidity_template: "{{ value_json.humidity }}",
current_humidity_template: '{{ value_json["humidity"] }}',
current_humidity_topic: "zigbee2mqtt/bosch_rm230z",
current_temperature_template: "{{ value_json.local_temperature }}",
current_temperature_template: '{{ value_json["local_temperature"] }}',
current_temperature_topic: "zigbee2mqtt/bosch_rm230z",
default_entity_id: "climate.bosch_rm230z",
device: {
@@ -1272,10 +1285,10 @@ describe("Extension: HomeAssistant", () => {
origin,
temp_step: 0.5,
temperature_high_command_topic: "zigbee2mqtt/bosch_rm230z/set/occupied_cooling_setpoint",
temperature_high_state_template: "{{ value_json.occupied_cooling_setpoint }}",
temperature_high_state_template: '{{ value_json["occupied_cooling_setpoint"] }}',
temperature_high_state_topic: "zigbee2mqtt/bosch_rm230z",
temperature_low_command_topic: "zigbee2mqtt/bosch_rm230z/set/occupied_heating_setpoint",
temperature_low_state_template: "{{ value_json.occupied_heating_setpoint }}",
temperature_low_state_template: '{{ value_json["occupied_heating_setpoint"] }}',
temperature_low_state_topic: "zigbee2mqtt/bosch_rm230z",
temperature_unit: "C",
unique_id: "0x18fc2600000d7ae3_climate_zigbee2mqtt",
@@ -1306,7 +1319,7 @@ describe("Extension: HomeAssistant", () => {
state_topic: "zigbee2mqtt/bosch_rm230z",
unique_id: "0x18fc2600000d7ae3_local_temperature_zigbee2mqtt",
unit_of_measurement: "°C",
value_template: "{{ value_json.local_temperature }}",
value_template: '{{ value_json["local_temperature"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/sensor/0x18fc2600000d7ae3/local_temperature/config", stringify(payload), {
@@ -1323,9 +1336,9 @@ describe("Extension: HomeAssistant", () => {
position_topic: "zigbee2mqtt/smart vent",
set_position_topic: "zigbee2mqtt/smart vent/set",
set_position_template: '{ "position": {{ position }} }',
position_template: "{{ value_json.position }}",
position_template: '{{ value_json["position"] }}',
state_topic: "zigbee2mqtt/smart vent",
value_template: "{{ value_json.state }}",
value_template: '{{ value_json["state"] }}',
state_open: "OPEN",
state_closed: "CLOSE",
state_stopped: "STOP",
@@ -1362,7 +1375,7 @@ describe("Extension: HomeAssistant", () => {
via_device: "zigbee2mqtt_bridge_0x00124b00120144ae",
},
name: "L6",
position_template: "{{ value_json.position }}",
position_template: '{{ value_json["position"] }}',
position_topic: "zigbee2mqtt/zigfred_plus/l6",
set_position_template: '{ "position_l6": {{ position }} }',
set_position_topic: "zigbee2mqtt/zigfred_plus/l6/set",
@@ -1371,13 +1384,13 @@ describe("Extension: HomeAssistant", () => {
state_open: "OPEN",
state_topic: "zigbee2mqtt/zigfred_plus/l6",
tilt_command_topic: "zigbee2mqtt/zigfred_plus/l6/set/tilt",
tilt_status_template: "{{ value_json.tilt }}",
tilt_status_template: '{{ value_json["tilt"] }}',
tilt_status_topic: "zigbee2mqtt/zigfred_plus/l6",
object_id: "zigfred_plus_l6",
default_entity_id: "cover.zigfred_plus_l6",
unique_id: "0xf4ce368a38be56a1_cover_l6_zigbee2mqtt",
origin: origin,
value_template: "{{ value_json.state }}",
value_template: '{{ value_json["state"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/cover/0xf4ce368a38be56a1/cover_l6/config", stringify(payload), {
@@ -1407,7 +1420,7 @@ describe("Extension: HomeAssistant", () => {
object_id: "0xa4c138018cf95021_left",
default_entity_id: "cover.0xa4c138018cf95021_left",
origin: origin,
position_template: "{{ value_json.position }}",
position_template: '{{ value_json["position"] }}',
position_topic: "zigbee2mqtt/0xa4c138018cf95021/left",
set_position_template: '{ "position_left": {{ position }} }',
set_position_topic: "zigbee2mqtt/0xa4c138018cf95021/left/set",
@@ -1416,7 +1429,7 @@ describe("Extension: HomeAssistant", () => {
state_stopped: "STOP",
state_topic: "zigbee2mqtt/0xa4c138018cf95021/left",
unique_id: "0xa4c138018cf95021_cover_left_zigbee2mqtt",
value_template: '{% if "moving" in value_json and value_json.moving %} {{ value_json.moving }} {% else %} STOP {% endif %}',
value_template: '{% if "moving" in value_json and value_json["moving"] %} {{ value_json["moving"] }} {% else %} STOP {% endif %}',
};
const payload_right = {
availability: [
@@ -1438,7 +1451,7 @@ describe("Extension: HomeAssistant", () => {
object_id: "0xa4c138018cf95021_right",
default_entity_id: "cover.0xa4c138018cf95021_right",
origin: origin,
position_template: "{{ value_json.position }}",
position_template: '{{ value_json["position"] }}',
position_topic: "zigbee2mqtt/0xa4c138018cf95021/right",
set_position_template: '{ "position_right": {{ position }} }',
set_position_topic: "zigbee2mqtt/0xa4c138018cf95021/right/set",
@@ -1447,11 +1460,9 @@ describe("Extension: HomeAssistant", () => {
state_stopped: "STOP",
state_topic: "zigbee2mqtt/0xa4c138018cf95021/right",
unique_id: "0xa4c138018cf95021_cover_right_zigbee2mqtt",
value_template: '{% if "moving" in value_json and value_json.moving %} {{ value_json.moving }} {% else %} STOP {% endif %}',
value_template: '{% if "moving" in value_json and value_json["moving"] %} {{ value_json["moving"] }} {% else %} STOP {% endif %}',
};
console.log(mockMQTTPublishAsync.mock.calls.find((c) => c[0] === "homeassistant/cover/0xa4c138018cf95021/cover_left/config"));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/cover/0xa4c138018cf95021/cover_left/config", stringify(payload_left), {
retain: true,
qos: 1,
@@ -1470,7 +1481,7 @@ describe("Extension: HomeAssistant", () => {
unit_of_measurement: "°C",
device_class: "temperature",
state_class: "measurement",
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
enabled_by_default: true,
object_id: "weather_sensor_temperature",
@@ -1558,6 +1569,8 @@ describe("Extension: HomeAssistant", () => {
color: {hue: 0, saturation: 100, h: 0, s: 100},
color_mode: "hs",
effect: null,
effect_color: null,
effect_speed: null,
linkquality: null,
state: null,
power_on_behavior: null,
@@ -1581,6 +1594,8 @@ describe("Extension: HomeAssistant", () => {
color: {x: 0.4576, y: 0.41},
color_mode: "xy",
effect: null,
effect_color: null,
effect_speed: null,
linkquality: null,
state: null,
power_on_behavior: null,
@@ -1603,6 +1618,8 @@ describe("Extension: HomeAssistant", () => {
stringify({
linkquality: null,
effect: null,
effect_color: null,
effect_speed: null,
state: "ON",
power_on_behavior: null,
update: {state: null, installed_version: -1, latest_version: -1},
@@ -1650,7 +1667,7 @@ describe("Extension: HomeAssistant", () => {
device_class: "temperature",
enabled_by_default: true,
state_class: "measurement",
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_temperature",
default_entity_id: "sensor.weather_sensor_temperature",
@@ -1811,7 +1828,7 @@ describe("Extension: HomeAssistant", () => {
device_class: "temperature",
enabled_by_default: true,
state_class: "measurement",
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_temperature",
default_entity_id: "sensor.weather_sensor_temperature",
@@ -1881,7 +1898,7 @@ describe("Extension: HomeAssistant", () => {
device_class: "temperature",
state_class: "measurement",
enabled_by_default: true,
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor_renamed",
object_id: "weather_sensor_renamed_temperature",
default_entity_id: "sensor.weather_sensor_renamed_temperature",
@@ -1965,6 +1982,16 @@ describe("Extension: HomeAssistant", () => {
"candle",
"fireplace",
"colorloop",
"sunset",
"sunrise",
"sparkle",
"opal",
"glisten",
"underwater",
"cosmos",
"sunbeam",
"enchant",
"none",
"finish_effect",
"stop_effect",
"stop_hue_effect",
@@ -1972,6 +1999,7 @@ describe("Extension: HomeAssistant", () => {
object_id: "ha_discovery_group_new",
default_entity_id: "light.ha_discovery_group_new",
unique_id: "9_light_zigbee2mqtt",
group: ["0x000b57fffec6a5b4_light_zigbee2mqtt", "0x000b57fffec6a5b7_light_zigbee2mqtt"],
origin: origin,
};
@@ -2004,7 +2032,7 @@ describe("Extension: HomeAssistant", () => {
device_class: "temperature",
state_class: "measurement",
enabled_by_default: true,
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor_renamed",
object_id: "weather_sensor_renamed_temperature",
default_entity_id: "sensor.weather_sensor_renamed_temperature",
@@ -2434,6 +2462,16 @@ describe("Extension: HomeAssistant", () => {
"candle",
"fireplace",
"colorloop",
"sunset",
"sunrise",
"sparkle",
"opal",
"glisten",
"underwater",
"cosmos",
"sunbeam",
"enchant",
"none",
"finish_effect",
"stop_effect",
"stop_hue_effect",
@@ -2441,6 +2479,7 @@ describe("Extension: HomeAssistant", () => {
object_id: "ha_discovery_group",
default_entity_id: "light.ha_discovery_group",
unique_id: "9_light_zigbee2mqtt",
group: ["0x000b57fffec6a5b4_light_zigbee2mqtt", "0x000b57fffec6a5b7_light_zigbee2mqtt"],
origin: origin,
};
@@ -2479,6 +2518,16 @@ describe("Extension: HomeAssistant", () => {
"candle",
"fireplace",
"colorloop",
"sunset",
"sunrise",
"sparkle",
"opal",
"glisten",
"underwater",
"cosmos",
"sunbeam",
"enchant",
"none",
"finish_effect",
"stop_effect",
"stop_hue_effect",
@@ -2486,6 +2535,7 @@ describe("Extension: HomeAssistant", () => {
object_id: "ha_discovery_group",
default_entity_id: "light.ha_discovery_group",
unique_id: "9_light_zigbee2mqtt",
group: ["0x000b57fffec6a5b4_light_zigbee2mqtt", "0x000b57fffec6a5b7_light_zigbee2mqtt"],
origin: origin,
};
@@ -2592,7 +2642,7 @@ describe("Extension: HomeAssistant", () => {
device_class: "temperature",
state_class: "measurement",
enabled_by_default: true,
value_template: "{{ value_json.temperature }}",
value_template: '{{ value_json["temperature"] }}',
state_topic: "zigbee2mqtt/weather_sensor",
object_id: "weather_sensor_temperature",
default_entity_id: "sensor.weather_sensor_temperature",
@@ -2959,7 +3009,7 @@ describe("Extension: HomeAssistant", () => {
origin: origin,
state_topic: "zigbee2mqtt/0x18fc26000000cafe",
unique_id: "0x18fc26000000cafe_device_mode_zigbee2mqtt",
value_template: "{{ value_json.device_mode }}",
value_template: '{{ value_json["device_mode"] }}',
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/select/0x18fc26000000cafe/device_mode/config", stringify(payload), {
retain: true,
+3 -3
View File
@@ -286,7 +286,7 @@ describe("Extension: NetworkMap", () => {
description: "Hue Go",
model: "7146060PH",
supports:
"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality",
"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, effect_speed, effect_color, linkquality",
vendor: "Philips",
},
failed: [],
@@ -616,7 +616,7 @@ describe("Extension: NetworkMap", () => {
description: "Hue Go",
model: "7146060PH",
supports:
"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality",
"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, effect_speed, effect_color, linkquality",
vendor: "Philips",
},
failed: [],
@@ -785,7 +785,7 @@ describe("Extension: NetworkMap", () => {
description: "Hue Go",
model: "7146060PH",
supports:
"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, linkquality",
"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), power_on_behavior, effect, effect_speed, effect_color, linkquality",
vendor: "Philips",
},
failed: [],
+713 -146
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -11,7 +11,6 @@
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"baseUrl": ".",
"rootDir": "lib",
"inlineSourceMap": true,
"resolveJsonModule": true,