mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-29 15:18:41 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba337bd329 | ||
|
|
7e7057eccd | ||
|
|
cbcd32064c | ||
|
|
d2d7ebeed9 | ||
|
|
9f72c5c455 | ||
|
|
e050e209b0 | ||
|
|
29d25aed66 | ||
|
|
063aba1b6c | ||
|
|
e8031efd2d | ||
|
|
102857a7fe | ||
|
|
db7b51ac80 | ||
|
|
d2166f2f4f | ||
|
|
07145accba | ||
|
|
44f9fe7a14 | ||
|
|
d522d2cd18 | ||
|
|
4e650b72cd | ||
|
|
a6072adcd0 | ||
|
|
0d193ebac2 | ||
|
|
ede1a470dd | ||
|
|
24779e3476 | ||
|
|
67e1a02525 | ||
|
|
7ce5b581aa | ||
|
|
40f41477e7 | ||
|
|
382c89c49e | ||
|
|
7d63a9a0f2 | ||
|
|
93af062a9b | ||
|
|
758c56647d | ||
|
|
845e03e48a | ||
|
|
dec5ca8b3a | ||
|
|
1e656c2114 | ||
|
|
ba76533c2f | ||
|
|
30d13d6e5e | ||
|
|
49fe147921 | ||
|
|
7a0964ebc9 | ||
|
|
b35cdc3474 | ||
|
|
5e55ef0454 | ||
|
|
3e3cefff1b | ||
|
|
97c74852c9 | ||
|
|
8542b35706 | ||
|
|
1eda69a50a | ||
|
|
16cecd917e | ||
|
|
1accb8b66b | ||
|
|
a3682b5fc7 | ||
|
|
cfea8b76e2 | ||
|
|
9e28818c53 | ||
|
|
bfbbfcda37 | ||
|
|
e4e4f16ec8 | ||
|
|
75c3c5e57f | ||
|
|
6a15328001 | ||
|
|
b960775160 | ||
|
|
41ba32db49 | ||
|
|
83ff4a7684 | ||
|
|
98b5a60886 | ||
|
|
b34723db36 | ||
|
|
5e9d53f0aa | ||
|
|
8c7961de1f | ||
|
|
ff466f817f | ||
|
|
c7a7c3d8c2 | ||
|
|
7540a0d02f | ||
|
|
33d5f0595d | ||
|
|
c4510d7382 | ||
|
|
e670524241 | ||
|
|
75f8ed07ed | ||
|
|
0706a5e039 | ||
|
|
32329695d0 | ||
|
|
cdbd55320a | ||
|
|
7b199f898e | ||
|
|
0462b94f5a | ||
|
|
85460cd5ce | ||
|
|
420a9f2ff6 | ||
|
|
5cf4547fe0 | ||
|
|
2af65cf28e | ||
|
|
e9f74f0958 | ||
|
|
2567b4c39e | ||
|
|
ac1671609c | ||
|
|
8da5fa663d | ||
|
|
3d3c68b54a | ||
|
|
5e60f1dc8d | ||
|
|
a1f74a312a | ||
|
|
b21167b326 | ||
|
|
be17e698a1 | ||
|
|
7392557af2 | ||
|
|
360be57079 | ||
|
|
361f9eef8b | ||
|
|
5b82d7e4df | ||
|
|
9daeba1f0a | ||
|
|
2d5e7bf9ff | ||
|
|
b5bef45cb2 | ||
|
|
f1f73bd464 | ||
|
|
23b709cee2 | ||
|
|
31bdf02a72 | ||
|
|
26ef565c8a | ||
|
|
6f3524b251 | ||
|
|
318dc04132 | ||
|
|
bf354f2121 | ||
|
|
1667ba64ac | ||
|
|
a28a5baf17 | ||
|
|
c33946ffc3 | ||
|
|
0d30eb553b | ||
|
|
43a5b24dcc | ||
|
|
3c46cfb2e9 | ||
|
|
7972950c84 |
@@ -20,12 +20,10 @@ jobs:
|
||||
if: ((github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push') == false
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: pnpm
|
||||
|
||||
@@ -85,6 +83,19 @@ jobs:
|
||||
VERSION=dev
|
||||
DATE=${{ github.event.repository.updated_at }}
|
||||
|
||||
- name: release - Docker meta
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
|
||||
uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
with:
|
||||
images: |
|
||||
koenkk/zigbee2mqtt
|
||||
ghcr.io/koenkk/zigbee2mqtt
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: release - Docker build and push
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -93,7 +104,7 @@ jobs:
|
||||
file: docker/Dockerfile
|
||||
provenance: false
|
||||
platforms: linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/riscv64,linux/386
|
||||
tags: koenkk/zigbee2mqtt:latest,ghcr.io/koenkk/zigbee2mqtt:latest,koenkk/zigbee2mqtt:${{ github.ref_name }},ghcr.io/koenkk/zigbee2mqtt:${{ github.ref_name }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
push: true
|
||||
build-args: |
|
||||
COMMIT=${{ github.sha }}
|
||||
@@ -155,9 +166,7 @@ jobs:
|
||||
git push origin master
|
||||
git checkout dev
|
||||
jq --indent 4 ".version = \"$TAG-dev\"" package.json > package.json.tmp
|
||||
jq --indent 4 ".version = \"$TAG-dev\"" package-lock.json > package-lock.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
mv package-lock.json.tmp package-lock.json
|
||||
git add -A
|
||||
git commit -m "chore: promote to dev"
|
||||
git push origin dev
|
||||
@@ -172,8 +181,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
@@ -1,26 +1,92 @@
|
||||
name: codeql
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: 'CodeQL'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
branches: ['master', 'dev']
|
||||
pull_request:
|
||||
branches: ['master', 'dev']
|
||||
schedule:
|
||||
- cron: '15 2 * * 2'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
build-mode: none
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: '/language:javascript-typescript'
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
|
||||
- uses: googleapis/release-please-action@v4
|
||||
id: release
|
||||
|
||||
@@ -18,11 +18,9 @@ jobs:
|
||||
ref: dev
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: |
|
||||
pnpm install ${{ github.event.client_payload.package }}@${{ github.event.client_payload.version }} --save-exact
|
||||
@@ -32,7 +30,7 @@ jobs:
|
||||
with:
|
||||
commit-message: 'fix(ignore): update ${{ github.event.client_payload.package }} to ${{ github.event.client_payload.version }}'
|
||||
branch: 'deps/${{ github.event.client_payload.package }}'
|
||||
title: Update ${{ github.event.client_payload.package }} to ${{ github.event.client_payload.version }}
|
||||
title: 'fix(ignore): update ${{ github.event.client_payload.package }} to ${{ github.event.client_payload.version }}'
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- run: sleep 5 # Otherwise pull request may not exist yet causing automerge to fail
|
||||
- run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
|
||||
|
||||
@@ -19,11 +19,9 @@ jobs:
|
||||
ref: dev
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
- run: |
|
||||
pnpm up --latest
|
||||
|
||||
@@ -45,6 +45,9 @@ typings/
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional pnpm cache directory
|
||||
.pnpm-store
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
@@ -68,6 +71,7 @@ tsconfig.tsbuildinfo
|
||||
|
||||
# IDE specific folders
|
||||
.idea
|
||||
.*.swp
|
||||
|
||||
# Ignore config
|
||||
data/*
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "2.0.0"
|
||||
".": "2.1.3"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,86 @@
|
||||
# Changelog
|
||||
|
||||
## [2.1.3](https://github.com/Koenkk/zigbee2mqtt/compare/2.1.2...2.1.3) (2025-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Home Assistant: fix brightness only lights ([#26560](https://github.com/Koenkk/zigbee2mqtt/issues/26560)) ([7e7057e](https://github.com/Koenkk/zigbee2mqtt/commit/7e7057eccd1fe638dd73b4a4f303c6612b73c89d))
|
||||
|
||||
## [2.1.2](https://github.com/Koenkk/zigbee2mqtt/compare/2.1.1...2.1.2) (2025-03-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add Home Assistant device class to external_temperature_input entity ([#26535](https://github.com/Koenkk/zigbee2mqtt/issues/26535)) ([e8031ef](https://github.com/Koenkk/zigbee2mqtt/commit/e8031efd2d95658c5f3fe72ee6e2f95dd584b5e8))
|
||||
* Allow configuring number of folders of old logs to keep ([#26398](https://github.com/Koenkk/zigbee2mqtt/issues/26398)) ([845e03e](https://github.com/Koenkk/zigbee2mqtt/commit/845e03e48acf1367feadeaae986f3c987bbc164d))
|
||||
* Fix Docker build hanging ([#26547](https://github.com/Koenkk/zigbee2mqtt/issues/26547)) ([e050e20](https://github.com/Koenkk/zigbee2mqtt/commit/e050e209b0d273cad6ad0efcf20761f33963da7b))
|
||||
* Fix HA MQTT dict entry indicating that an update is progress ([#26267](https://github.com/Koenkk/zigbee2mqtt/issues/26267)) ([b35cdc3](https://github.com/Koenkk/zigbee2mqtt/commit/b35cdc347423b219722c9f9dd0d2a82ded8eb24f))
|
||||
* Home Assistant: add `brightness` to `supported_color_modes` ([#26545](https://github.com/Koenkk/zigbee2mqtt/issues/26545)) ([063aba1](https://github.com/Koenkk/zigbee2mqtt/commit/063aba1b6c72901ed2d51a367485092691c358da))
|
||||
* Home Assistant: add `is_updating` attribute ([#26231](https://github.com/Koenkk/zigbee2mqtt/issues/26231)) ([1eda69a](https://github.com/Koenkk/zigbee2mqtt/commit/1eda69a50a8f92c3471acdbdc82b41055d5992f2))
|
||||
* Home Assistant: add missing `device_class` for some `current` and `power` entities ([#26324](https://github.com/Koenkk/zigbee2mqtt/issues/26324)) ([1e656c2](https://github.com/Koenkk/zigbee2mqtt/commit/1e656c2114d58b96fd7b7750e2e7d255a9175653))
|
||||
* Home Assistant: support speed-controlled fans ([#24483](https://github.com/Koenkk/zigbee2mqtt/issues/24483)) ([7ce5b58](https://github.com/Koenkk/zigbee2mqtt/commit/7ce5b581aa57d828043a1ca75ef7efe7b7bf1619))
|
||||
* **ignore:** Add `publish` to `meta` of toZigbee converters ([#26490](https://github.com/Koenkk/zigbee2mqtt/issues/26490)) ([07145ac](https://github.com/Koenkk/zigbee2mqtt/commit/07145accbae6c481f6d6740e66e27ab1baf5eb6d))
|
||||
* **ignore:** remove obsolete @types/eslint__js ([d522d2c](https://github.com/Koenkk/zigbee2mqtt/commit/d522d2cd18b8cb77fa50cd457823cfa573622cb7))
|
||||
* **ignore:** update dependencies ([#26266](https://github.com/Koenkk/zigbee2mqtt/issues/26266)) ([3e3ceff](https://github.com/Koenkk/zigbee2mqtt/commit/3e3cefff1b7f85f59b6bf529f6593339498561a1))
|
||||
* **ignore:** update dependencies ([#26383](https://github.com/Koenkk/zigbee2mqtt/issues/26383)) ([758c566](https://github.com/Koenkk/zigbee2mqtt/commit/758c56647d353e81ea22a34c2fa2665ed76dc149))
|
||||
* **ignore:** update dependencies ([#26478](https://github.com/Koenkk/zigbee2mqtt/issues/26478)) ([d2166f2](https://github.com/Koenkk/zigbee2mqtt/commit/d2166f2f4fa1e1e286face994ec4825a70144908))
|
||||
* **ignore:** update zigbee-herdsman to 3.2.6 ([#26303](https://github.com/Koenkk/zigbee2mqtt/issues/26303)) ([49fe147](https://github.com/Koenkk/zigbee2mqtt/commit/49fe1479213c928786c64adeb3031ca6fe91250d))
|
||||
* **ignore:** update zigbee-herdsman to 3.2.7 ([#26498](https://github.com/Koenkk/zigbee2mqtt/issues/26498)) ([4e650b7](https://github.com/Koenkk/zigbee2mqtt/commit/4e650b72cd187488893213018b619e933835a58e))
|
||||
* **ignore:** update zigbee-herdsman-converters to 21.32.0 ([#26245](https://github.com/Koenkk/zigbee2mqtt/issues/26245)) ([8542b35](https://github.com/Koenkk/zigbee2mqtt/commit/8542b35706cefc87c9ec9dccb2980302ea25f100))
|
||||
* **ignore:** update zigbee-herdsman-converters to 21.33.0 ([#26258](https://github.com/Koenkk/zigbee2mqtt/issues/26258)) ([97c7485](https://github.com/Koenkk/zigbee2mqtt/commit/97c74852c95536945e33d0388dc5fcd0b132cd7e))
|
||||
* **ignore:** update zigbee-herdsman-converters to 21.34.0 ([#26285](https://github.com/Koenkk/zigbee2mqtt/issues/26285)) ([7a0964e](https://github.com/Koenkk/zigbee2mqtt/commit/7a0964ebc905903b4ca5b168d9bd1041b004b4cf))
|
||||
* **ignore:** update zigbee-herdsman-converters to 21.35.0 ([#26305](https://github.com/Koenkk/zigbee2mqtt/issues/26305)) ([30d13d6](https://github.com/Koenkk/zigbee2mqtt/commit/30d13d6e5ee9678f7da812bd287128824a535d48))
|
||||
* **ignore:** update zigbee-herdsman-converters to 21.36.0 ([#26333](https://github.com/Koenkk/zigbee2mqtt/issues/26333)) ([ba76533](https://github.com/Koenkk/zigbee2mqtt/commit/ba76533c2f241c8362f43ad07d10b2fe084479a0))
|
||||
* **ignore:** update zigbee-herdsman-converters to 21.37.0 ([#26367](https://github.com/Koenkk/zigbee2mqtt/issues/26367)) ([dec5ca8](https://github.com/Koenkk/zigbee2mqtt/commit/dec5ca8b3a3703e6289652192657af9a035fe026))
|
||||
* **ignore:** update zigbee-herdsman-converters to 21.38.0 ([#26400](https://github.com/Koenkk/zigbee2mqtt/issues/26400)) ([93af062](https://github.com/Koenkk/zigbee2mqtt/commit/93af062a9b84358c866a5a1191ae5895fc6e866f))
|
||||
* **ignore:** update zigbee-herdsman-converters to 22.0.1 ([#26418](https://github.com/Koenkk/zigbee2mqtt/issues/26418)) ([7d63a9a](https://github.com/Koenkk/zigbee2mqtt/commit/7d63a9a0f29445044bcfe0f3a4b629266db37654))
|
||||
* **ignore:** update zigbee-herdsman-converters to 22.1.0 ([#26445](https://github.com/Koenkk/zigbee2mqtt/issues/26445)) ([40f4147](https://github.com/Koenkk/zigbee2mqtt/commit/40f41477e776b4e75434251d03bf2844fceb7b13))
|
||||
* **ignore:** update zigbee-herdsman-converters to 22.2.0 ([#26469](https://github.com/Koenkk/zigbee2mqtt/issues/26469)) ([67e1a02](https://github.com/Koenkk/zigbee2mqtt/commit/67e1a0252517da28629a4c97b4dd610624d451a1))
|
||||
* **ignore:** update zigbee-herdsman-converters to 23.0.0 ([44f9fe7](https://github.com/Koenkk/zigbee2mqtt/commit/44f9fe7a1494a9450e247ad353ce788c621f901c))
|
||||
* **ignore:** update zigbee-herdsman-converters to 23.1.0 ([#26509](https://github.com/Koenkk/zigbee2mqtt/issues/26509)) ([db7b51a](https://github.com/Koenkk/zigbee2mqtt/commit/db7b51ac801829801656d978aff2605d8bd7b56a))
|
||||
* **ignore:** update zigbee-herdsman-converters to 23.1.1 ([#26510](https://github.com/Koenkk/zigbee2mqtt/issues/26510)) ([102857a](https://github.com/Koenkk/zigbee2mqtt/commit/102857a7fec60e3f65887d92b6fe7a2d81add110))
|
||||
* **ignore:** update zigbee-herdsman-converters to 23.2.0 ([#26549](https://github.com/Koenkk/zigbee2mqtt/issues/26549)) ([9f72c5c](https://github.com/Koenkk/zigbee2mqtt/commit/9f72c5c4555bda5b347a6a5406f619b9668813ec))
|
||||
* Make compatible with pnpm 10 ([#26546](https://github.com/Koenkk/zigbee2mqtt/issues/26546)) ([29d25ae](https://github.com/Koenkk/zigbee2mqtt/commit/29d25aed66a66912fb55ede8751ba8056efb673f))
|
||||
* Support FreeBSD for `update.sh` ([#26261](https://github.com/Koenkk/zigbee2mqtt/issues/26261)) ([5e55ef0](https://github.com/Koenkk/zigbee2mqtt/commit/5e55ef045427f78b410e3d292387c3592366f698))
|
||||
|
||||
## [2.1.1](https://github.com/Koenkk/zigbee2mqtt/compare/2.1.0...2.1.1) (2025-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ignore:** update dependencies ([#26126](https://github.com/Koenkk/zigbee2mqtt/issues/26126)) ([b960775](https://github.com/Koenkk/zigbee2mqtt/commit/b960775160b33dcd276376efaa7dc1ca3f791a53))
|
||||
* Rename `internal_temperature` to `internalTemperature` to correct typo ([#26114](https://github.com/Koenkk/zigbee2mqtt/issues/26114)) ([e4e4f16](https://github.com/Koenkk/zigbee2mqtt/commit/e4e4f16ec8994a63ba148b36d7e18a09f49ac298))
|
||||
|
||||
## [2.1.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.0.0...2.1.0) (2025-02-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add a settings option to log to console in json format ([#25649](https://github.com/Koenkk/zigbee2mqtt/issues/25649)) ([26ef565](https://github.com/Koenkk/zigbee2mqtt/commit/26ef565c8a20d1b6c2e09c679eb96d3f56811a87))
|
||||
* Allow Home Assistant OTA Update entity to show progress while updating ([#25632](https://github.com/Koenkk/zigbee2mqtt/issues/25632)) ([318dc04](https://github.com/Koenkk/zigbee2mqtt/commit/318dc0413284d204d13a99afd400a5f00f5ef338))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add Home Assistant device class for flow rates (e.g., Sonoff SWV) ([#26035](https://github.com/Koenkk/zigbee2mqtt/issues/26035)) ([cdbd553](https://github.com/Koenkk/zigbee2mqtt/commit/cdbd55320acbf7d857c518e8aac8ab9e495eb6b5))
|
||||
* Add semver tags to Docker releases ([#25530](https://github.com/Koenkk/zigbee2mqtt/issues/25530)) ([0d30eb5](https://github.com/Koenkk/zigbee2mqtt/commit/0d30eb553b9cb027f8ae5424b9e740136742f74c))
|
||||
* Allow to set throttle via frontend ([#25997](https://github.com/Koenkk/zigbee2mqtt/issues/25997)) ([5cf4547](https://github.com/Koenkk/zigbee2mqtt/commit/5cf4547fe04e9a5627826225c3b26586aea3edc1))
|
||||
* Container: remove modifying capabilities of `/usr/bin/node` ([#25456](https://github.com/Koenkk/zigbee2mqtt/issues/25456)) ([43a5b24](https://github.com/Koenkk/zigbee2mqtt/commit/43a5b24dcc22af3ec4c0bb76b45a2706f898421f))
|
||||
* Enable use of MQTT username without password ([#25611](https://github.com/Koenkk/zigbee2mqtt/issues/25611)) ([a28a5ba](https://github.com/Koenkk/zigbee2mqtt/commit/a28a5baf17b8dd3fe4703cdb38d80796157521af))
|
||||
* Fix `Error: write after end` error ([#25737](https://github.com/Koenkk/zigbee2mqtt/issues/25737)) ([2d5e7bf](https://github.com/Koenkk/zigbee2mqtt/commit/2d5e7bf9ff611387f2334a55caab6991d65ad3fa))
|
||||
* Fix `Failed to call 'Frontend' 'stop' (TypeError: Cannot read properties of undefined (reading 'close')` https://github.com/Koenkk/zigbee2mqtt/issues/25715 ([9daeba1](https://github.com/Koenkk/zigbee2mqtt/commit/9daeba1f0ab350d9609bcd4c1e8bc291b5eaab8b))
|
||||
* Fix duplicate `action` publish when `advanced.output` has `attribute` ([#25963](https://github.com/Koenkk/zigbee2mqtt/issues/25963)) ([2af65cf](https://github.com/Koenkk/zigbee2mqtt/commit/2af65cf28eea397013a0f629761536339d6ace63))
|
||||
* **ignore:** Ensure config example is never out of sync with settings ([#25707](https://github.com/Koenkk/zigbee2mqtt/issues/25707)) ([23b709c](https://github.com/Koenkk/zigbee2mqtt/commit/23b709cee28a5926f1608e70e30703ba52011005))
|
||||
* **ignore:** Remove `adapter` `auto` from schema ([#25896](https://github.com/Koenkk/zigbee2mqtt/issues/25896)) ([5e60f1d](https://github.com/Koenkk/zigbee2mqtt/commit/5e60f1dc8dae96a04d1a76a677f89f426da64a94))
|
||||
* **ignore:** update dependencies ([#25535](https://github.com/Koenkk/zigbee2mqtt/issues/25535)) ([6f3524b](https://github.com/Koenkk/zigbee2mqtt/commit/6f3524b251ed8218ffed132df262eb0850687e61))
|
||||
* **ignore:** update dependencies ([#25760](https://github.com/Koenkk/zigbee2mqtt/issues/25760)) ([5b82d7e](https://github.com/Koenkk/zigbee2mqtt/commit/5b82d7e4df9fdc868689f8a745e502b5beea837a))
|
||||
* **ignore:** update dependencies ([#25890](https://github.com/Koenkk/zigbee2mqtt/issues/25890)) ([8da5fa6](https://github.com/Koenkk/zigbee2mqtt/commit/8da5fa663d2fbf4a68e797cff9938b57dd620935))
|
||||
* **ignore:** update dependencies ([#26005](https://github.com/Koenkk/zigbee2mqtt/issues/26005)) ([85460cd](https://github.com/Koenkk/zigbee2mqtt/commit/85460cd5ce2a17a2036a54d3984a5f59b75be41b))
|
||||
* Long startup time when `OnEvent` 'start' times out (repeatedly) ([#25693](https://github.com/Koenkk/zigbee2mqtt/issues/25693)) ([f1f73bd](https://github.com/Koenkk/zigbee2mqtt/commit/f1f73bd46425262fc1ff7e897d879faf16e3d8a9))
|
||||
* Only use endpoint-specific states if the device definition uses them ([#26019](https://github.com/Koenkk/zigbee2mqtt/issues/26019)) ([0462b94](https://github.com/Koenkk/zigbee2mqtt/commit/0462b94f5a346e64a85a90cb6c7e33a36ac98311))
|
||||
* Remove invalid `warn` `log_level` https://github.com/nurikk/zigbee2mqtt-frontend/issues/2369 ([#25634](https://github.com/Koenkk/zigbee2mqtt/issues/25634)) ([1667ba6](https://github.com/Koenkk/zigbee2mqtt/commit/1667ba64ac2b8272cb0dea7ce30e459450e1c847))
|
||||
|
||||
## [2.0.0](https://github.com/Koenkk/zigbee2mqtt/compare/1.42.0...2.0.0) (2025-01-03)
|
||||
|
||||
|
||||
|
||||
@@ -105,8 +105,9 @@ Zigbee2MQTT is made up of three modules, each developed in its own Github projec
|
||||
### Developing
|
||||
|
||||
Zigbee2MQTT uses TypeScript (partially for now). Therefore after making changes to files in the `lib/` directory you need to recompile Zigbee2MQTT. This can be done by executing `pnpm run build`. For faster development instead of running `pnpm run build` you can run `pnpm run build-watch` in another terminal session, this will recompile as you change files.
|
||||
In first time before building you need to run `pnpm install --include=dev`
|
||||
Before submitting changes run `pnpm run test-with-coverage`, `pnpm run pretty:check` and `pnpm run eslint`
|
||||
|
||||
Before running any of the commands, you'll first need to run `pnpm install --include=dev`.
|
||||
Before submitting changes run `pnpm run test:coverage`, `pnpm run pretty:check` and `pnpm run eslint`
|
||||
|
||||
## Supported devices
|
||||
|
||||
|
||||
+2
-3
@@ -12,7 +12,7 @@ FROM linux-${TARGETARCH}-alpine AS base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache tzdata eudev tini nodejs libcap
|
||||
RUN apk add --no-cache tzdata eudev tini nodejs
|
||||
|
||||
# Dependencies and build
|
||||
FROM base AS deps
|
||||
@@ -20,7 +20,7 @@ FROM base AS deps
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
# Make and such are needed to compile serialport for riscv64
|
||||
RUN apk add make gcc g++ python3 linux-headers npm && \
|
||||
npm install -g pnpm && \
|
||||
npm install -g pnpm@10.4.1 && \
|
||||
pnpm install --frozen-lockfile --no-optional && \
|
||||
# serialport has outdated prebuilds that appear to fail on some archs, force build on target platform
|
||||
rm -rf `find ./node_modules/.pnpm/ -wholename "*/@serialport/bindings-cpp/prebuilds" -type d` && \
|
||||
@@ -47,7 +47,6 @@ COPY package.json LICENSE index.js data/configuration.example.yaml ./
|
||||
|
||||
COPY docker/docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
RUN setcap 'cap_net_bind_service=+ep' /usr/bin/node
|
||||
|
||||
RUN mkdir /app/data
|
||||
|
||||
|
||||
@@ -243,6 +243,8 @@ export default class Availability extends Extension {
|
||||
options,
|
||||
state,
|
||||
device: device.zh,
|
||||
/* v8 ignore next */
|
||||
publish: (payload: KeyValue) => this.publishEntityState(device, payload),
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
import {addDefinition, removeExternalDefinitions} from 'zigbee-herdsman-converters';
|
||||
import {addExternalDefinition, removeExternalDefinitions} from 'zigbee-herdsman-converters';
|
||||
|
||||
import logger from '../util/logger';
|
||||
import ExternalJSExtension from './externalJS';
|
||||
|
||||
type ModuleExports = zhc.Definition | zhc.Definition[];
|
||||
type ModuleExports = zhc.ExternalDefinitionWithExtend | zhc.ExternalDefinitionWithExtend[];
|
||||
|
||||
export default class ExternalConverters extends ExternalJSExtension<ModuleExports> {
|
||||
constructor(
|
||||
@@ -43,10 +43,11 @@ export default class ExternalConverters extends ExternalJSExtension<ModuleExport
|
||||
try {
|
||||
removeExternalDefinitions(name);
|
||||
|
||||
for (const definition of this.getDefinitions(module)) {
|
||||
const definitions = Array.isArray(module) ? module : [module];
|
||||
for (const definition of definitions) {
|
||||
definition.externalConverterName = name;
|
||||
|
||||
addDefinition(definition);
|
||||
addExternalDefinition(definition);
|
||||
logger.info(`Loaded external converter '${name}'.`);
|
||||
}
|
||||
|
||||
@@ -61,8 +62,4 @@ export default class ExternalConverters extends ExternalJSExtension<ModuleExport
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private getDefinitions(module: ModuleExports): zhc.Definition[] {
|
||||
return Array.isArray(module) ? module : [module];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ export default class Frontend extends Extension {
|
||||
});
|
||||
this.wss?.close();
|
||||
|
||||
await new Promise((resolve) => this.server.close(resolve));
|
||||
await new Promise((resolve) => this.server?.close(resolve));
|
||||
}
|
||||
|
||||
@bind private onRequest(request: IncomingMessage, response: ServerResponse): void {
|
||||
|
||||
@@ -167,7 +167,13 @@ export default class Groups extends Extension {
|
||||
if (this.state.exists(device)) {
|
||||
const state = this.state.get(device);
|
||||
const endpointNames = device.isDevice() && device.getEndpointNames();
|
||||
const stateKey = endpointNames && endpointNames.length >= member.ID ? `state_${endpointNames[member.ID - 1]}` : 'state';
|
||||
const stateKey =
|
||||
endpointNames &&
|
||||
endpointNames.length >= member.ID &&
|
||||
device.definition?.meta?.multiEndpoint &&
|
||||
(!device.definition.meta.multiEndpointSkip || !device.definition.meta.multiEndpointSkip.includes('state'))
|
||||
? `state_${endpointNames[member.ID - 1]}`
|
||||
: 'state';
|
||||
|
||||
if (state[stateKey] === 'ON' || state[stateKey] === 'OPEN') {
|
||||
return false;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
import assert from 'node:assert';
|
||||
|
||||
import bind from 'bind-decorator';
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
import logger from '../util/logger';
|
||||
import * as settings from '../util/settings';
|
||||
import utils, {assertBinaryExpose, assertEnumExpose, assertNumericExpose, isBinaryExpose, isEnumExpose, isNumericExpose} from '../util/utils';
|
||||
@@ -170,8 +170,9 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
eco2: {device_class: 'carbon_dioxide', state_class: 'measurement'},
|
||||
eco_temperature: {entity_category: 'config', icon: 'mdi:thermometer'},
|
||||
energy: {device_class: 'energy', state_class: 'total_increasing'},
|
||||
external_temperature_input: {icon: 'mdi:thermometer'},
|
||||
external_temperature_input: {device_class: 'temperature', icon: 'mdi:thermometer'},
|
||||
formaldehyd: {state_class: 'measurement'},
|
||||
flow: {device_class: 'volume_flow_rate', state_class: 'measurement'},
|
||||
gas_density: {icon: 'mdi:google-circles-communities', state_class: 'measurement'},
|
||||
hcho: {icon: 'mdi:air-filter', state_class: 'measurement'},
|
||||
humidity: {device_class: 'humidity', state_class: 'measurement'},
|
||||
@@ -180,7 +181,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
humidity_min: {entity_category: 'config', icon: 'mdi:water-percent'},
|
||||
illuminance_calibration: {entity_category: 'config', icon: 'mdi:wrench-clock'},
|
||||
illuminance: {device_class: 'illuminance', state_class: 'measurement'},
|
||||
internal_temperature: {
|
||||
internalTemperature: {
|
||||
device_class: 'temperature',
|
||||
entity_category: 'diagnostic',
|
||||
state_class: 'measurement',
|
||||
@@ -543,6 +544,13 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
if (colorModes.length) {
|
||||
discoveryEntry.discovery_payload.supported_color_modes = colorModes;
|
||||
} else {
|
||||
/**
|
||||
* All bulbs support brightness, note that `brightness` cannot be combined
|
||||
* with other color modes.
|
||||
* https://github.com/Koenkk/zigbee2mqtt/issues/26520#issuecomment-2692432058
|
||||
*/
|
||||
discoveryEntry.discovery_payload.supported_color_modes = ['brightness'];
|
||||
}
|
||||
|
||||
if (hasColorTemp) {
|
||||
@@ -871,15 +879,17 @@ export default class HomeAssistant extends Extension {
|
||||
discovery_payload: {
|
||||
name: null,
|
||||
state_topic: true,
|
||||
state_value_template: '{{ value_json.fan_state }}',
|
||||
command_topic: true,
|
||||
command_topic_postfix: 'fan_state',
|
||||
},
|
||||
};
|
||||
|
||||
const speed = (firstExpose as zhc.Fan).features.filter(isEnumExpose).find((e) => e.name === 'mode');
|
||||
const modeEmulatedSpeed = (firstExpose as zhc.Fan).features.filter(isEnumExpose).find((e) => e.name === 'mode');
|
||||
const nativeSpeed = (firstExpose as zhc.Fan).features.filter(isNumericExpose).find((e) => e.name === 'speed');
|
||||
|
||||
if (speed) {
|
||||
// Exactly one mode needs to be active (logical xor)
|
||||
assert(!modeEmulatedSpeed != !nativeSpeed, 'Fans need to be either mode- or speed-controlled');
|
||||
|
||||
if (modeEmulatedSpeed) {
|
||||
// A fan entity in Home Assistant 2021.3 and above may have a speed,
|
||||
// controlled by a percentage from 1 to 100, and/or non-speed presets.
|
||||
// The MQTT Fan integration allows the speed percentage to be mapped
|
||||
@@ -893,9 +903,9 @@ export default class HomeAssistant extends Extension {
|
||||
// ZCL. This supports a generic ZCL HVAC Fan Control fan. "Off" is
|
||||
// always a valid speed.
|
||||
let speeds = ['off'].concat(
|
||||
['low', 'medium', 'high', '1', '2', '3', '4', '5', '6', '7', '8', '9'].filter((s) => speed.values.includes(s)),
|
||||
['low', 'medium', 'high', '1', '2', '3', '4', '5', '6', '7', '8', '9'].filter((s) => modeEmulatedSpeed.values.includes(s)),
|
||||
);
|
||||
let presets = ['on', 'auto', 'smart'].filter((s) => speed.values.includes(s));
|
||||
let presets = ['on', 'auto', 'smart'].filter((s) => modeEmulatedSpeed.values.includes(s));
|
||||
|
||||
if (['99432'].includes(definition!.model)) {
|
||||
// The Hampton Bay 99432 fan implements 4 speeds using the ZCL
|
||||
@@ -907,22 +917,37 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
const allowed = [...speeds, ...presets];
|
||||
speed.values.forEach((s) => assert(allowed.includes(s.toString())));
|
||||
modeEmulatedSpeed.values.forEach((s) => assert(allowed.includes(s.toString())));
|
||||
const percentValues = speeds.map((s, i) => `'${s}':${i}`).join(', ');
|
||||
const percentCommands = speeds.map((s, i) => `${i}:'${s}'`).join(', ');
|
||||
const presetList = presets.map((s) => `'${s}'`).join(', ');
|
||||
|
||||
discoveryEntry.discovery_payload.percentage_state_topic = true;
|
||||
discoveryEntry.discovery_payload.percentage_command_topic = true;
|
||||
discoveryEntry.discovery_payload.percentage_value_template = `{{ {${percentValues}}[value_json.${speed.property}] | default('None') }}`;
|
||||
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_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.${speed.property} if value_json.${speed.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
|
||||
discoveryEntry.discovery_payload.state_value_template = '{{ value_json.fan_state }}';
|
||||
discoveryEntry.discovery_payload.command_topic_postfix = 'fan_state';
|
||||
} 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_command_template = `{{ value | default('') }}`;
|
||||
discoveryEntry.discovery_payload.speed_range_min = nativeSpeed.value_min;
|
||||
discoveryEntry.discovery_payload.speed_range_max = nativeSpeed.value_max;
|
||||
|
||||
// Speed-controlled fans generally have an onOff cluster, use that for state
|
||||
discoveryEntry.discovery_payload.state_value_template = '{{ value_json.state }}';
|
||||
discoveryEntry.discovery_payload.command_topic_postfix = 'state';
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
@@ -1018,6 +1043,14 @@ export default class HomeAssistant extends Extension {
|
||||
if (firstExpose.unit && ['Wh', 'kWh'].includes(firstExpose.unit)) {
|
||||
Object.assign(extraAttrs, {device_class: 'energy', state_class: 'total_increasing'});
|
||||
}
|
||||
// If a variable includes A or mA, mark it as current
|
||||
else if (firstExpose.unit && ['A', 'mA'].includes(firstExpose.unit)) {
|
||||
Object.assign(extraAttrs, {device_class: 'current', state_class: 'measurement'});
|
||||
}
|
||||
// If a variable includes mW, W, kW mark it as power
|
||||
else if (firstExpose.unit && ['mW', 'W', 'kW'].includes(firstExpose.unit)) {
|
||||
Object.assign(extraAttrs, {device_class: 'power', state_class: 'measurement'});
|
||||
}
|
||||
|
||||
let key = firstExpose.name;
|
||||
|
||||
@@ -1294,7 +1327,7 @@ export default class HomeAssistant extends Extension {
|
||||
* Whenever a device publish an {action: *} we discover an MQTT device trigger sensor
|
||||
* and republish it to zigbee2mqtt/my_device/action
|
||||
*/
|
||||
if (entity.isDevice() && entity.definition && data.message.action) {
|
||||
if (settings.get().advanced.output === 'json' && entity.isDevice() && entity.definition && data.message.action) {
|
||||
const value = data.message['action'].toString();
|
||||
await this.publishDeviceTriggerDiscover(entity, 'action', value);
|
||||
await this.mqtt.publish(`${data.entity.name}/action`, value, {});
|
||||
@@ -1405,16 +1438,12 @@ export default class HomeAssistant extends Extension {
|
||||
discovery_payload: {
|
||||
name: null,
|
||||
entity_picture: 'https://github.com/Koenkk/zigbee2mqtt/raw/master/images/logo.png',
|
||||
latest_version_topic: true,
|
||||
state_topic: true,
|
||||
device_class: 'firmware',
|
||||
entity_category: 'config',
|
||||
command_topic: `${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/update`,
|
||||
payload_install: `{"id": "${entity.ieeeAddr}"}`,
|
||||
value_template: `{{ value_json['update']['installed_version'] }}`,
|
||||
latest_version_template: `{{ value_json['update']['latest_version'] }}`,
|
||||
json_attributes_topic: `${settings.get().mqtt.base_topic}/${entity.name}`, // state topic
|
||||
json_attributes_template: `{"in_progress": {{ iif(value_json['update']['state'] == 'updating', 'true', 'false') }} }`,
|
||||
value_template: `{"latest_version":"{{ value_json['update']['latest_version'] }}","installed_version":"{{ value_json['update']['installed_version'] }}","update_percentage":{{ value_json['update'].get('progress', 'null') }},"in_progress":{{ (value_json['update']['state'] == 'updating')|lower }}}`,
|
||||
},
|
||||
};
|
||||
configs.push(updateSensor);
|
||||
@@ -1607,10 +1636,6 @@ export default class HomeAssistant extends Extension {
|
||||
payload.fan_mode_state_topic = stateTopic;
|
||||
}
|
||||
|
||||
if (payload.latest_version_topic) {
|
||||
payload.latest_version_topic = stateTopic;
|
||||
}
|
||||
|
||||
if (payload.fan_mode_command_topic) {
|
||||
payload.fan_mode_command_topic = `${baseTopic}/${commandTopicPrefix}set/fan_mode`;
|
||||
}
|
||||
@@ -1628,7 +1653,7 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
if (payload.percentage_command_topic) {
|
||||
payload.percentage_command_topic = `${baseTopic}/${commandTopicPrefix}set/fan_mode`;
|
||||
payload.percentage_command_topic = `${baseTopic}/${commandTopicPrefix}set/${payload.percentage_command_topic}`;
|
||||
}
|
||||
|
||||
if (payload.preset_mode_state_topic) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import Extension from './extension';
|
||||
export default class OnEvent extends Extension {
|
||||
override async start(): Promise<void> {
|
||||
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
|
||||
await this.callOnEvent(device, 'start', {});
|
||||
this.callOnEvent(device, 'start', {}).catch(utils.noop);
|
||||
}
|
||||
|
||||
this.eventBus.onDeviceMessage(this, (data) => this.callOnEvent(data.device, 'message', this.convertData(data)));
|
||||
|
||||
@@ -231,6 +231,8 @@ export default class Publish extends Extension {
|
||||
state: entityState,
|
||||
membersState,
|
||||
mapped: definition,
|
||||
/* v8 ignore next */
|
||||
publish: (payload: KeyValue) => this.publishEntityState(re, payload),
|
||||
};
|
||||
|
||||
// Strip endpoint name from meta.message properties.
|
||||
|
||||
+13
-2
@@ -1,10 +1,18 @@
|
||||
import assert from 'node:assert';
|
||||
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import {access, Numeric} from 'zigbee-herdsman-converters';
|
||||
import {CustomClusters} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype';
|
||||
|
||||
import * as settings from '../util/settings';
|
||||
|
||||
const LINKQUALITY = new Numeric('linkquality', access.STATE)
|
||||
.withUnit('lqi')
|
||||
.withDescription('Link quality (signal strength)')
|
||||
.withValueMin(0)
|
||||
.withValueMax(255)
|
||||
.withCategory('diagnostic');
|
||||
|
||||
export default class Device {
|
||||
public zh: zh.Device;
|
||||
public definition?: zhc.Definition;
|
||||
@@ -38,13 +46,16 @@ export default class Device {
|
||||
}
|
||||
|
||||
exposes(): zhc.Expose[] {
|
||||
const exposes: zhc.Expose[] = [];
|
||||
assert(this.definition, 'Cannot retreive exposes before definition is resolved');
|
||||
if (typeof this.definition.exposes == 'function') {
|
||||
const options: KeyValue = this.options;
|
||||
return this.definition.exposes(this.zh, options);
|
||||
exposes.push(...this.definition.exposes(this.zh, options));
|
||||
} else {
|
||||
return this.definition.exposes;
|
||||
exposes.push(...this.definition.exposes);
|
||||
}
|
||||
exposes.push(LINKQUALITY);
|
||||
return exposes;
|
||||
}
|
||||
|
||||
async resolveDefinition(ignoreCache: boolean = false): Promise<void> {
|
||||
|
||||
@@ -67,6 +67,9 @@ export default class MQTT {
|
||||
logger.debug(`Using MQTT login with username: ${mqttSettings.user}`);
|
||||
options.username = mqttSettings.user;
|
||||
options.password = mqttSettings.password;
|
||||
} else if (mqttSettings.user) {
|
||||
logger.debug(`Using MQTT login with username only: ${mqttSettings.user}`);
|
||||
options.username = mqttSettings.user;
|
||||
} else {
|
||||
logger.debug(`Using MQTT anonymous login`);
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -168,6 +168,7 @@ declare global {
|
||||
device_options: KeyValue;
|
||||
advanced: {
|
||||
log_rotation: boolean;
|
||||
log_console_json: boolean;
|
||||
log_symlink_current: boolean;
|
||||
log_output: ('console' | 'file' | 'syslog')[];
|
||||
log_directory: string;
|
||||
@@ -177,6 +178,7 @@ declare global {
|
||||
log_syslog: KeyValue;
|
||||
log_debug_to_mqtt_frontend: boolean;
|
||||
log_debug_namespace_ignore: string;
|
||||
log_directories_to_keep: number;
|
||||
pan_id: number | 'GENERATE';
|
||||
ext_pan_id: number[] | 'GENERATE';
|
||||
channel: number;
|
||||
|
||||
+18
-16
@@ -56,13 +56,15 @@ class Logger {
|
||||
this.logger.add(
|
||||
new winston.transports.Console({
|
||||
silent: consoleSilenced,
|
||||
// winston.config.syslog.levels sets 'warning' as 'red'
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize({colors: {debug: 'blue', info: 'green', warning: 'yellow', error: 'red'}}),
|
||||
winston.format.printf((info) => {
|
||||
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
|
||||
}),
|
||||
),
|
||||
format: settings.get().advanced.log_console_json
|
||||
? winston.format.json()
|
||||
: winston.format.combine(
|
||||
// winston.config.syslog.levels sets 'warning' as 'red'
|
||||
winston.format.colorize({colors: {debug: 'blue', info: 'green', warning: 'yellow', error: 'red'}}),
|
||||
winston.format.printf((info) => {
|
||||
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -230,7 +232,7 @@ class Logger {
|
||||
});
|
||||
|
||||
directories.sort((a: KeyValue, b: KeyValue) => b.birth - a.birth);
|
||||
directories = directories.slice(10, directories.length);
|
||||
directories = directories.slice(settings.get().advanced.log_directories_to_keep, directories.length);
|
||||
directories.forEach((dir) => {
|
||||
this.debug(`Removing old log directory '${dir.path}'`);
|
||||
rimrafSync(dir.path);
|
||||
@@ -242,12 +244,11 @@ class Logger {
|
||||
// https://github.com/Koenkk/zigbee2mqtt/pull/10905
|
||||
/* v8 ignore start */
|
||||
public async end(): Promise<void> {
|
||||
this.logger.end();
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
if (!this.fileTransport) {
|
||||
process.nextTick(resolve);
|
||||
} else {
|
||||
// Only flush the file transport, don't end logger itself as log() might still be called
|
||||
// causing a UnhandledPromiseRejection (`Error: write after end`). Flushing the file transport
|
||||
// ensures the log files are written before stopping.
|
||||
if (this.fileTransport) {
|
||||
await new Promise<void>((resolve) => {
|
||||
// @ts-expect-error workaround
|
||||
if (this.fileTransport._dest) {
|
||||
// @ts-expect-error workaround
|
||||
@@ -256,8 +257,9 @@ class Logger {
|
||||
// @ts-expect-error workaround
|
||||
this.fileTransport.on('open', () => this.fileTransport._dest.on('finish', resolve));
|
||||
}
|
||||
}
|
||||
});
|
||||
this.fileTransport.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
}
|
||||
|
||||
@@ -219,9 +219,8 @@
|
||||
},
|
||||
"adapter": {
|
||||
"type": ["string"],
|
||||
"enum": ["deconz", "zstack", "zigate", "ezsp", "auto", "ember", "zboss"],
|
||||
"enum": ["deconz", "zstack", "zigate", "ezsp", "ember", "zboss"],
|
||||
"title": "Adapter",
|
||||
"default": "auto",
|
||||
"requiresRestart": true,
|
||||
"description": "Adapter type, not needed unless you are experiencing problems"
|
||||
},
|
||||
@@ -452,6 +451,13 @@
|
||||
"description": "Log rotation",
|
||||
"default": true
|
||||
},
|
||||
"log_console_json": {
|
||||
"type": "boolean",
|
||||
"title": "Console json log",
|
||||
"requiresRestart": true,
|
||||
"description": "Console json log",
|
||||
"default": false
|
||||
},
|
||||
"log_symlink_current": {
|
||||
"type": "boolean",
|
||||
"title": "Log symlink current",
|
||||
@@ -486,7 +492,7 @@
|
||||
},
|
||||
"log_level": {
|
||||
"type": "string",
|
||||
"enum": ["error", "warning", "info", "debug", "warn"],
|
||||
"enum": ["error", "warning", "info", "debug"],
|
||||
"title": "Log level",
|
||||
"description": "Logging level",
|
||||
"default": "info"
|
||||
@@ -519,6 +525,14 @@
|
||||
"default": "",
|
||||
"examples": ["^zhc:legacy:fz:(tuya|moes)", "^zhc:legacy:fz:(tuya|moes)|^zh:ember:uart:|^zh:controller"]
|
||||
},
|
||||
"log_directories_to_keep": {
|
||||
"type": "number",
|
||||
"title": "Number of past log folders to keep",
|
||||
"description": "Number of log directories to keep before deleting the oldest one",
|
||||
"default": 10,
|
||||
"minimum": 5,
|
||||
"maximum": 1000
|
||||
},
|
||||
"log_syslog": {
|
||||
"requiresRestart": true,
|
||||
"oneOf": [
|
||||
@@ -754,10 +768,17 @@
|
||||
"title": "QoS",
|
||||
"description": "QoS level for MQTT messages of this device"
|
||||
},
|
||||
"throttle": {
|
||||
"type": "number",
|
||||
"title": "Throttle",
|
||||
"description": "The minimum time between payloads in seconds. Payloads received whilst the device is being throttled will be discarded",
|
||||
"requiresRestart": true
|
||||
},
|
||||
"debounce": {
|
||||
"type": "number",
|
||||
"title": "Debounce",
|
||||
"description": "Debounces messages of this device"
|
||||
"description": "Debounces messages of this device",
|
||||
"requiresRestart": true
|
||||
},
|
||||
"debounce_ignore": {
|
||||
"type": "array",
|
||||
|
||||
@@ -86,6 +86,7 @@ export const defaults: RecursivePartial<Settings> = {
|
||||
device_options: {},
|
||||
advanced: {
|
||||
log_rotation: true,
|
||||
log_console_json: false,
|
||||
log_symlink_current: false,
|
||||
log_output: ['console', 'file'],
|
||||
log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'),
|
||||
@@ -95,6 +96,7 @@ export const defaults: RecursivePartial<Settings> = {
|
||||
log_syslog: {},
|
||||
log_debug_to_mqtt_frontend: false,
|
||||
log_debug_namespace_ignore: '',
|
||||
log_directories_to_keep: 10,
|
||||
pan_id: 0x1a62,
|
||||
ext_pan_id: [0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd],
|
||||
channel: 11,
|
||||
|
||||
+25
-19
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "zigbee2mqtt",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.3",
|
||||
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
|
||||
"main": "index.js",
|
||||
"packageManager": "pnpm@10.4.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
|
||||
@@ -54,44 +55,49 @@
|
||||
"mqtt": "^5.10.3",
|
||||
"object-assign-deep": "^0.4.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"semver": "^7.6.3",
|
||||
"semver": "^7.7.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"throttleit": "^2.1.0",
|
||||
"winston": "^3.17.0",
|
||||
"winston-syslog": "^2.7.1",
|
||||
"winston-transport": "^4.9.0",
|
||||
"ws": "^8.18.0",
|
||||
"zigbee-herdsman": "3.2.1",
|
||||
"zigbee-herdsman-converters": "21.12.0",
|
||||
"ws": "^8.18.1",
|
||||
"zigbee-herdsman": "3.2.7",
|
||||
"zigbee-herdsman-converters": "23.2.0",
|
||||
"zigbee2mqtt-frontend": "0.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/core": "^0.9.1",
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@eslint/core": "^0.12.0",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||
"@types/finalhandler": "^1.2.3",
|
||||
"@types/humanize-duration": "^3.27.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/object-assign-deep": "^0.4.3",
|
||||
"@types/readable-stream": "4.0.18",
|
||||
"@types/sd-notify": "^2.8.2",
|
||||
"@types/serve-static": "^1.15.7",
|
||||
"@types/ws": "8.5.13",
|
||||
"@vitest/coverage-v8": "^2.1.8",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"prettier": "^3.4.2",
|
||||
"@types/ws": "8.5.14",
|
||||
"@vitest/coverage-v8": "^3.0.7",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"prettier": "^3.5.2",
|
||||
"tmp": "^0.2.3",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vitest": "^2.1.8"
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.25.0",
|
||||
"vitest": "^3.0.7"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"zigbee-herdsman": "$zigbee-herdsman"
|
||||
}
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"@serialport/bindings-cpp",
|
||||
"esbuild",
|
||||
"sd-notify",
|
||||
"unix-dgram"
|
||||
]
|
||||
},
|
||||
"bin": {
|
||||
"zigbee2mqtt": "cli.js"
|
||||
|
||||
Generated
+641
-642
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,8 @@ if (fs.existsSync(commitUserFile)) {
|
||||
commitUserLookup = JSON.parse(fs.readFileSync(commitUserFile, 'utf8'));
|
||||
}
|
||||
|
||||
const whiteLabels = zhc.definitions.filter((d) => d.whiteLabel).flatMap((d) => d.whiteLabel);
|
||||
const definitions = require('zigbee-herdsman-converters/devices/index').default.map((d) => zhc.prepareDefinition(d));
|
||||
const whiteLabels = definitions.filter((d) => d.whiteLabel).flatMap((d) => d.whiteLabel);
|
||||
const capitalizeFirstChar = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
||||
|
||||
for (const changelog of changelogs) {
|
||||
@@ -97,7 +98,7 @@ for (const changelog of changelogs) {
|
||||
|
||||
if (localContext === 'add') {
|
||||
for (const model of message.split(',')) {
|
||||
const definition = zhc.definitions.find((d) => d.model === model.trim());
|
||||
const definition = definitions.find((d) => d.model === model.trim());
|
||||
const whiteLabel = whiteLabels.find((d) => d.model === model.trim());
|
||||
const match = definition || whiteLabel;
|
||||
if (match) {
|
||||
|
||||
@@ -157,6 +157,46 @@ describe('Controller', () => {
|
||||
expect(mockMQTTConnectAsync).toHaveBeenCalledWith('mqtt://localhost', expected);
|
||||
});
|
||||
|
||||
it('Start controller with only username MQTT settings', async () => {
|
||||
const ca = tmp.fileSync().name;
|
||||
fs.writeFileSync(ca, 'ca');
|
||||
const key = tmp.fileSync().name;
|
||||
fs.writeFileSync(key, 'key');
|
||||
const cert = tmp.fileSync().name;
|
||||
fs.writeFileSync(cert, 'cert');
|
||||
|
||||
const configuration = {
|
||||
base_topic: 'zigbee2mqtt',
|
||||
server: 'mqtt://localhost',
|
||||
keepalive: 30,
|
||||
ca,
|
||||
cert,
|
||||
key,
|
||||
user: 'user1',
|
||||
client_id: 'my_client_id',
|
||||
reject_unauthorized: false,
|
||||
version: 5,
|
||||
maximum_packet_size: 20000,
|
||||
};
|
||||
settings.set(['mqtt'], configuration);
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
expect(mockMQTTConnectAsync).toHaveBeenCalledTimes(1);
|
||||
const expected = {
|
||||
will: {payload: Buffer.from('{"state":"offline"}'), retain: true, topic: 'zigbee2mqtt/bridge/state', qos: 1},
|
||||
keepalive: 30,
|
||||
ca: Buffer.from([99, 97]),
|
||||
key: Buffer.from([107, 101, 121]),
|
||||
cert: Buffer.from([99, 101, 114, 116]),
|
||||
username: 'user1',
|
||||
clientId: 'my_client_id',
|
||||
rejectUnauthorized: false,
|
||||
protocolVersion: 5,
|
||||
properties: {maximumPacketSize: 20000},
|
||||
};
|
||||
expect(mockMQTTConnectAsync).toHaveBeenCalledWith('mqtt://localhost', expected);
|
||||
});
|
||||
|
||||
it('Should generate network_key, pan_id and ext_pan_id when set to GENERATE', async () => {
|
||||
settings.set(['advanced', 'network_key'], 'GENERATE');
|
||||
settings.set(['advanced', 'pan_id'], 'GENERATE');
|
||||
|
||||
@@ -118,9 +118,11 @@ describe('Extension: Bridge', () => {
|
||||
log_level: 'info',
|
||||
log_namespaced_levels: {},
|
||||
log_output: ['console', 'file'],
|
||||
log_console_json: false,
|
||||
log_rotation: true,
|
||||
log_symlink_current: false,
|
||||
log_syslog: {},
|
||||
log_directories_to_keep: 10,
|
||||
output: 'json',
|
||||
pan_id: 6754,
|
||||
timestamp_format: 'YYYY-MM-DD HH:mm:ss',
|
||||
@@ -140,6 +142,7 @@ describe('Extension: Bridge', () => {
|
||||
retain: false,
|
||||
},
|
||||
'0x000b57fffec6a5b7': {friendly_name: 'bulb_2', retain: false},
|
||||
'0x00124b00cfcf3298': {friendly_name: 'fanbee', retain: true},
|
||||
'0x0017880104a44559': {friendly_name: 'J1_cover'},
|
||||
'0x0017880104e43559': {friendly_name: 'U202DST600ZB'},
|
||||
'0x0017880104e44559': {friendly_name: '3157100_thermostat'},
|
||||
@@ -314,28 +317,22 @@ describe('Extension: Bridge', () => {
|
||||
|
||||
it('Should publish devices on startup', async () => {
|
||||
await resetExtension();
|
||||
// console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]);
|
||||
// console.log(mockMQTTPublishAsync.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]);
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bridge/devices',
|
||||
stringify([
|
||||
{
|
||||
date_code: undefined,
|
||||
// definition: undefined,
|
||||
disabled: false,
|
||||
endpoints: {1: {bindings: [], clusters: {input: [], output: []}, configured_reportings: [], scenes: []}},
|
||||
endpoints: {'1': {bindings: [], clusters: {input: [], output: []}, configured_reportings: [], scenes: []}},
|
||||
friendly_name: 'Coordinator',
|
||||
ieee_address: '0x00124b00120144ae',
|
||||
interview_completed: false,
|
||||
interviewing: false,
|
||||
model_id: undefined,
|
||||
network_address: 0,
|
||||
power_source: undefined,
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'Coordinator',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'TRADFRI bulb E26/E27, white spectrum, globe, opal, 980 lm',
|
||||
exposes: [
|
||||
@@ -551,7 +548,7 @@ describe('Extension: Bridge', () => {
|
||||
description: 'this is my bulb',
|
||||
disabled: false,
|
||||
endpoints: {
|
||||
1: {
|
||||
'1': {
|
||||
bindings: [],
|
||||
clusters: {
|
||||
input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl'],
|
||||
@@ -576,7 +573,6 @@ describe('Extension: Bridge', () => {
|
||||
model_id: 'TRADFRI bulb E27 WS opal 980lm',
|
||||
network_address: 40369,
|
||||
power_source: 'Mains (single phase)',
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'Router',
|
||||
},
|
||||
@@ -753,7 +749,7 @@ describe('Extension: Bridge', () => {
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {
|
||||
1: {
|
||||
'1': {
|
||||
bindings: [],
|
||||
clusters: {
|
||||
input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl'],
|
||||
@@ -776,7 +772,6 @@ describe('Extension: Bridge', () => {
|
||||
type: 'Router',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'Hue dimmer switch',
|
||||
exposes: [
|
||||
@@ -880,7 +875,7 @@ describe('Extension: Bridge', () => {
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {
|
||||
1: {
|
||||
'1': {
|
||||
bindings: [
|
||||
{cluster: 'genLevelCtrl', target: {endpoint: 1, ieee_address: '0x000b57fffec6a5b3', type: 'endpoint'}},
|
||||
{cluster: 'genOnOff', target: {endpoint: 1, ieee_address: '0x000b57fffec6a5b3', type: 'endpoint'}},
|
||||
@@ -892,7 +887,7 @@ describe('Extension: Bridge', () => {
|
||||
configured_reportings: [],
|
||||
scenes: [],
|
||||
},
|
||||
2: {bindings: [], clusters: {input: ['genBasic'], output: ['genOta', 'genOnOff']}, configured_reportings: [], scenes: []},
|
||||
'2': {bindings: [], clusters: {input: ['genBasic'], output: ['genOta', 'genOnOff']}, configured_reportings: [], scenes: []},
|
||||
},
|
||||
friendly_name: 'remote',
|
||||
ieee_address: '0x0017880104e45517',
|
||||
@@ -901,12 +896,10 @@ describe('Extension: Bridge', () => {
|
||||
model_id: 'RWL021',
|
||||
network_address: 6535,
|
||||
power_source: 'Battery',
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'EndDevice',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'Automatically generated definition',
|
||||
exposes: [
|
||||
@@ -981,7 +974,7 @@ describe('Extension: Bridge', () => {
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {
|
||||
1: {
|
||||
'1': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic'], output: ['genBasic', 'genOnOff', 'genLevelCtrl', 'genScenes']},
|
||||
configured_reportings: [],
|
||||
@@ -996,12 +989,10 @@ describe('Extension: Bridge', () => {
|
||||
model_id: 'notSupportedModelID',
|
||||
network_address: 6536,
|
||||
power_source: 'Battery',
|
||||
software_build_id: undefined,
|
||||
supported: false,
|
||||
type: 'EndDevice',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'Wireless mini switch',
|
||||
exposes: [
|
||||
@@ -1085,7 +1076,7 @@ describe('Extension: Bridge', () => {
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {
|
||||
1: {
|
||||
'1': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic'], output: ['genBasic', 'genOnOff', 'genLevelCtrl', 'genScenes']},
|
||||
configured_reportings: [
|
||||
@@ -1107,12 +1098,10 @@ describe('Extension: Bridge', () => {
|
||||
model_id: 'lumi.sensor_switch.aq2',
|
||||
network_address: 6537,
|
||||
power_source: 'Battery',
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'EndDevice',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'Temperature and humidity sensor',
|
||||
exposes: [
|
||||
@@ -1242,7 +1231,7 @@ describe('Extension: Bridge', () => {
|
||||
vendor: 'Aqara',
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {1: {bindings: [], clusters: {input: ['genBasic'], output: []}, configured_reportings: [], scenes: []}},
|
||||
endpoints: {'1': {bindings: [], clusters: {input: ['genBasic'], output: []}, configured_reportings: [], scenes: []}},
|
||||
friendly_name: 'weather_sensor',
|
||||
ieee_address: '0x0017880104e45522',
|
||||
interview_completed: true,
|
||||
@@ -1250,12 +1239,10 @@ describe('Extension: Bridge', () => {
|
||||
model_id: 'lumi.weather',
|
||||
network_address: 6539,
|
||||
power_source: 'Battery',
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'EndDevice',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'Mi smart plug',
|
||||
exposes: [
|
||||
@@ -1391,7 +1378,7 @@ describe('Extension: Bridge', () => {
|
||||
vendor: 'Xiaomi',
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {1: {bindings: [], clusters: {input: ['genBasic', 'genOnOff'], output: []}, configured_reportings: [], scenes: []}},
|
||||
endpoints: {'1': {bindings: [], clusters: {input: ['genBasic', 'genOnOff'], output: []}, configured_reportings: [], scenes: []}},
|
||||
friendly_name: 'power_plug',
|
||||
ieee_address: '0x0017880104e45524',
|
||||
interview_completed: true,
|
||||
@@ -1399,12 +1386,10 @@ describe('Extension: Bridge', () => {
|
||||
model_id: 'lumi.plug',
|
||||
network_address: 6540,
|
||||
power_source: 'Mains (single phase)',
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'Router',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'zigfred plus smart in-wall switch',
|
||||
exposes: [
|
||||
@@ -1435,18 +1420,6 @@ describe('Extension: Bridge', () => {
|
||||
'button_4_release',
|
||||
],
|
||||
},
|
||||
{
|
||||
access: 1,
|
||||
category: 'diagnostic',
|
||||
description: 'Link quality (signal strength)',
|
||||
label: 'Linkquality',
|
||||
name: 'linkquality',
|
||||
property: 'linkquality',
|
||||
type: 'numeric',
|
||||
unit: 'lqi',
|
||||
value_max: 255,
|
||||
value_min: 0,
|
||||
},
|
||||
{
|
||||
endpoint: 'l1',
|
||||
features: [
|
||||
@@ -1683,6 +1656,18 @@ describe('Extension: Bridge', () => {
|
||||
],
|
||||
type: 'cover',
|
||||
},
|
||||
{
|
||||
access: 1,
|
||||
category: 'diagnostic',
|
||||
description: 'Link quality (signal strength)',
|
||||
label: 'Linkquality',
|
||||
name: 'linkquality',
|
||||
property: 'linkquality',
|
||||
type: 'numeric',
|
||||
unit: 'lqi',
|
||||
value_max: 255,
|
||||
value_min: 0,
|
||||
},
|
||||
],
|
||||
model: 'ZFP-1A-CH',
|
||||
options: [
|
||||
@@ -1850,43 +1835,43 @@ describe('Extension: Bridge', () => {
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {
|
||||
10: {
|
||||
'10': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl'], output: []},
|
||||
configured_reportings: [],
|
||||
scenes: [],
|
||||
},
|
||||
11: {
|
||||
'11': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic', 'genScenes', 'closuresWindowCovering'], output: []},
|
||||
configured_reportings: [],
|
||||
scenes: [],
|
||||
},
|
||||
12: {
|
||||
'12': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic', 'genScenes', 'closuresWindowCovering'], output: []},
|
||||
configured_reportings: [],
|
||||
scenes: [],
|
||||
},
|
||||
5: {
|
||||
'5': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl'], output: []},
|
||||
configured_reportings: [],
|
||||
scenes: [],
|
||||
},
|
||||
7: {
|
||||
'7': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl'], output: []},
|
||||
configured_reportings: [],
|
||||
scenes: [],
|
||||
},
|
||||
8: {
|
||||
'8': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl'], output: []},
|
||||
configured_reportings: [],
|
||||
scenes: [],
|
||||
},
|
||||
9: {
|
||||
'9': {
|
||||
bindings: [],
|
||||
clusters: {input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl'], output: []},
|
||||
configured_reportings: [],
|
||||
@@ -1901,12 +1886,10 @@ describe('Extension: Bridge', () => {
|
||||
model_id: 'zigfred plus',
|
||||
network_address: 6589,
|
||||
power_source: 'Mains (single phase)',
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'Router',
|
||||
},
|
||||
{
|
||||
date_code: undefined,
|
||||
definition: {
|
||||
description: 'TRADFRI bulb E26/E27, white spectrum, globe, opal, 980 lm',
|
||||
exposes: [
|
||||
@@ -2121,7 +2104,7 @@ describe('Extension: Bridge', () => {
|
||||
},
|
||||
disabled: false,
|
||||
endpoints: {
|
||||
1: {
|
||||
'1': {
|
||||
bindings: [],
|
||||
clusters: {
|
||||
input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl'],
|
||||
@@ -2135,11 +2118,9 @@ describe('Extension: Bridge', () => {
|
||||
ieee_address: '0x000b57fffec6a5c2',
|
||||
interview_completed: true,
|
||||
interviewing: false,
|
||||
manufacturer: undefined,
|
||||
model_id: 'TRADFRI bulb E27 WS opal 980lm',
|
||||
network_address: 40369,
|
||||
power_source: 'Mains (single phase)',
|
||||
software_build_id: undefined,
|
||||
supported: true,
|
||||
type: 'Router',
|
||||
},
|
||||
@@ -3193,14 +3174,14 @@ describe('Extension: Bridge', () => {
|
||||
data: {
|
||||
id: '0x0017880104e45524',
|
||||
source:
|
||||
"const {onOff} = require('zigbee-herdsman-converters/lib/modernExtend');\n" +
|
||||
"const m = require('zigbee-herdsman-converters/lib/modernExtend');\n" +
|
||||
'\n' +
|
||||
'const definition = {\n' +
|
||||
" zigbeeModel: ['lumi.plug'],\n" +
|
||||
" model: 'lumi.plug',\n" +
|
||||
" vendor: '',\n" +
|
||||
" description: 'Automatically generated definition',\n" +
|
||||
' extend: [onOff({"powerOnBehavior":false})],\n' +
|
||||
' extend: [m.onOff({"powerOnBehavior":false})],\n' +
|
||||
' meta: {},\n' +
|
||||
'};\n' +
|
||||
'\n' +
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('Extension: ExternalConverters', () => {
|
||||
const rmSyncSpy = vi.spyOn(fs, 'rmSync');
|
||||
const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync');
|
||||
|
||||
const zhcAddDefinitionSpy = vi.spyOn(zhc, 'addDefinition');
|
||||
const zhcAddExternalDefinitionSpy = vi.spyOn(zhc, 'addExternalDefinition');
|
||||
const zhcRemoveExternalDefinitionsSpy = vi.spyOn(zhc, 'removeExternalDefinitions');
|
||||
|
||||
const mocksClear = [
|
||||
@@ -43,7 +43,7 @@ describe('Extension: ExternalConverters', () => {
|
||||
mkdirSyncSpy,
|
||||
rmSyncSpy,
|
||||
writeFileSyncSpy,
|
||||
zhcAddDefinitionSpy,
|
||||
zhcAddExternalDefinitionSpy,
|
||||
zhcRemoveExternalDefinitionsSpy,
|
||||
];
|
||||
|
||||
@@ -129,7 +129,7 @@ describe('Extension: ExternalConverters', () => {
|
||||
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(2);
|
||||
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, 'mock-external-converter-multiple.js');
|
||||
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(2, 'mock-external-converter.js');
|
||||
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
|
||||
expect(zhcAddExternalDefinitionSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
mock: 1,
|
||||
@@ -139,7 +139,7 @@ describe('Extension: ExternalConverters', () => {
|
||||
description: 'external_1',
|
||||
}),
|
||||
);
|
||||
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
|
||||
expect(zhcAddExternalDefinitionSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
mock: 2,
|
||||
@@ -149,7 +149,7 @@ describe('Extension: ExternalConverters', () => {
|
||||
description: 'external_2',
|
||||
}),
|
||||
);
|
||||
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
|
||||
expect(zhcAddExternalDefinitionSpy).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.objectContaining({
|
||||
mock: true,
|
||||
@@ -206,7 +206,7 @@ describe('Extension: ExternalConverters', () => {
|
||||
expect(writeFileSyncSpy).toHaveBeenCalledWith(converterFilePath, converterCode, 'utf8');
|
||||
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, converterName);
|
||||
expect(zhcAddDefinitionSpy).toHaveBeenCalledWith(
|
||||
expect(zhcAddExternalDefinitionSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mock: true,
|
||||
zigbeeModel: ['external_converter_device'],
|
||||
@@ -288,7 +288,7 @@ describe('Extension: ExternalConverters', () => {
|
||||
|
||||
const errorMsg = `Invalid definition`;
|
||||
|
||||
zhcAddDefinitionSpy.mockImplementationOnce(() => {
|
||||
zhcAddExternalDefinitionSpy.mockImplementationOnce(() => {
|
||||
throw new Error(errorMsg);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {devices, groups, events as mockZHEvents, resetGroupMembers, returnDevice
|
||||
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
|
||||
import {toZigbee as zhcToZigbee} from 'zigbee-herdsman-converters';
|
||||
import * as zhcGlobalStore from 'zigbee-herdsman-converters/lib/store';
|
||||
|
||||
import {Controller} from '../../lib/controller';
|
||||
import * as settings from '../../lib/util/settings';
|
||||
@@ -19,6 +19,7 @@ returnDevices.push(
|
||||
devices.bulb_color_2.ieeeAddr,
|
||||
devices.bulb_2.ieeeAddr,
|
||||
devices.GLEDOPTO_2ID.ieeeAddr,
|
||||
devices.InovelliVZM31SN.ieeeAddr,
|
||||
);
|
||||
|
||||
describe('Extension: Groups', () => {
|
||||
@@ -41,7 +42,7 @@ describe('Extension: Groups', () => {
|
||||
settings.reRead();
|
||||
mockMQTTPublishAsync.mockClear();
|
||||
groups.gledopto_group.command.mockClear();
|
||||
zhcToZigbee.__clearStore__();
|
||||
zhcGlobalStore.clear();
|
||||
// @ts-expect-error private
|
||||
controller.state.state = {};
|
||||
});
|
||||
@@ -335,6 +336,31 @@ describe('Extension: Groups', () => {
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
|
||||
});
|
||||
|
||||
it('Should publish state change off if all lights within turn off with non default-ep, but device state does not use them', async () => {
|
||||
const device_1 = devices.bulb_color;
|
||||
const device_2 = devices.InovelliVZM31SN;
|
||||
const endpoint_1 = device_1.getEndpoint(1)!;
|
||||
const endpoint_2 = device_2.getEndpoint(2)!;
|
||||
const group = groups.group_1;
|
||||
group.members.push(endpoint_1);
|
||||
group.members.push(endpoint_2);
|
||||
settings.set(['groups'], {
|
||||
1: {friendly_name: 'group_1', retain: false},
|
||||
});
|
||||
|
||||
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
|
||||
await flushPromises();
|
||||
mockMQTTPublishAsync.mockClear();
|
||||
|
||||
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
|
||||
await mockMQTTEvents.message('zigbee2mqtt/wall_switch_double/set', stringify({state_left: 'OFF'}));
|
||||
await flushPromises();
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/wall_switch_double', stringify({state_left: 'OFF'}), {retain: false, qos: 0});
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
|
||||
});
|
||||
|
||||
it('Should not publish state change off if any lights within are still on when changed via shared group', async () => {
|
||||
const device_1 = devices.bulb_color;
|
||||
const device_2 = devices.bulb;
|
||||
|
||||
@@ -9,6 +9,9 @@ import assert from 'node:assert';
|
||||
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import {KeyValueAny} from 'zigbee-herdsman-converters/lib/types';
|
||||
|
||||
import {Controller} from '../../lib/controller';
|
||||
import HomeAssistant from '../../lib/extension/homeassistant';
|
||||
import * as settings from '../../lib/util/settings';
|
||||
@@ -79,7 +82,12 @@ describe('Extension: HomeAssistant', () => {
|
||||
|
||||
it('Should not have duplicate type/object_ids in a mapping', async () => {
|
||||
const duplicated: string[] = [];
|
||||
(await import('zigbee-herdsman-converters')).definitions.forEach((d) => {
|
||||
|
||||
(await import('zigbee-herdsman-converters/devices/index')).default.forEach((baseDefinition) => {
|
||||
const d = zhc.prepareDefinition(
|
||||
// @ts-expect-error inferred type is wrong
|
||||
baseDefinition,
|
||||
);
|
||||
const exposes = typeof d.exposes == 'function' ? d.exposes(undefined, undefined) : d.exposes;
|
||||
const device = {
|
||||
definition: d,
|
||||
@@ -157,6 +165,33 @@ describe('Extension: HomeAssistant', () => {
|
||||
qos: 1,
|
||||
});
|
||||
|
||||
payload = {
|
||||
availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}],
|
||||
brightness: true,
|
||||
brightness_scale: 254,
|
||||
command_topic: 'zigbee2mqtt/bulb_enddevice/set',
|
||||
device: {
|
||||
identifiers: ['zigbee2mqtt_0x0017880104e45553'],
|
||||
manufacturer: 'Sengled',
|
||||
model: 'Element classic (A19)',
|
||||
model_id: 'E11-G13',
|
||||
name: 'bulb_enddevice',
|
||||
via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae',
|
||||
},
|
||||
name: null,
|
||||
object_id: 'bulb_enddevice',
|
||||
origin: origin,
|
||||
schema: 'json',
|
||||
state_topic: 'zigbee2mqtt/bulb_enddevice',
|
||||
supported_color_modes: ['brightness'],
|
||||
unique_id: '0x0017880104e45553_light_zigbee2mqtt',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/light/0x0017880104e45553/light/config', stringify(payload), {
|
||||
retain: true,
|
||||
qos: 1,
|
||||
});
|
||||
|
||||
payload = {
|
||||
availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}],
|
||||
command_topic: 'zigbee2mqtt/ha_discovery_group/set',
|
||||
@@ -873,6 +908,42 @@ describe('Extension: HomeAssistant', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should discover devices with speed-controlled fan', async () => {
|
||||
const payload = {
|
||||
state_topic: 'zigbee2mqtt/fanbee',
|
||||
state_value_template: '{{ value_json.state }}',
|
||||
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_command_template: "{{ value | default('') }}",
|
||||
speed_range_min: 1,
|
||||
speed_range_max: 254,
|
||||
name: null,
|
||||
object_id: 'fanbee',
|
||||
unique_id: '0x00124b00cfcf3298_fan_zigbee2mqtt',
|
||||
origin: origin,
|
||||
device: {
|
||||
identifiers: ['zigbee2mqtt_0x00124b00cfcf3298'],
|
||||
name: 'fanbee',
|
||||
model: 'Fan with valve',
|
||||
model_id: 'FanBee',
|
||||
manufacturer: 'Lorenz Brun',
|
||||
via_device: 'zigbee2mqtt_bridge_0x00124b00120144ae',
|
||||
},
|
||||
availability: [
|
||||
{
|
||||
topic: 'zigbee2mqtt/bridge/state',
|
||||
value_template: '{{ value_json.state }}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const idx = mockMQTTPublishAsync.mock.calls.findIndex((c) => c[0] === 'homeassistant/fan/0x00124b00cfcf3298/fan/config');
|
||||
expect(idx).not.toBe(-1);
|
||||
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[idx][1])).toStrictEqual(payload);
|
||||
});
|
||||
|
||||
it('Should discover thermostat devices', async () => {
|
||||
const payload = {
|
||||
action_template:
|
||||
@@ -1674,17 +1745,14 @@ describe('Extension: HomeAssistant', () => {
|
||||
device_class: 'firmware',
|
||||
entity_category: 'config',
|
||||
entity_picture: 'https://github.com/Koenkk/zigbee2mqtt/raw/master/images/logo.png',
|
||||
json_attributes_template: `{"in_progress": {{ iif(value_json['update']['state'] == 'updating', 'true', 'false') }} }`,
|
||||
json_attributes_topic: 'zigbee2mqtt/bulb',
|
||||
latest_version_template: "{{ value_json['update']['latest_version'] }}",
|
||||
latest_version_topic: 'zigbee2mqtt/bulb',
|
||||
name: null,
|
||||
object_id: 'bulb',
|
||||
origin,
|
||||
payload_install: `{"id": "0x000b57fffec6a5b2"}`,
|
||||
state_topic: 'zigbee2mqtt/bulb',
|
||||
unique_id: '0x000b57fffec6a5b2_update_zigbee2mqtt',
|
||||
value_template: "{{ value_json['update']['installed_version'] }}",
|
||||
value_template:
|
||||
"{\"latest_version\":\"{{ value_json['update']['latest_version'] }}\",\"installed_version\":\"{{ value_json['update']['installed_version'] }}\",\"update_percentage\":{{ value_json['update'].get('progress', 'null') }},\"in_progress\":{{ (value_json['update']['state'] == 'updating')|lower }}}",
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/update/0x000b57fffec6a5b2/update/config', stringify(payload), {
|
||||
@@ -1727,8 +1795,6 @@ describe('Extension: HomeAssistant', () => {
|
||||
{retain: true, qos: 1},
|
||||
);
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/button/action', 'single', {retain: false, qos: 0});
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/button',
|
||||
stringify({
|
||||
@@ -1752,8 +1818,6 @@ describe('Extension: HomeAssistant', () => {
|
||||
{retain: true, qos: 1},
|
||||
);
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/button/action', 'single', {retain: false, qos: 0});
|
||||
|
||||
// Shouldn't rediscover when already discovered in previous session
|
||||
clearDiscoveredTrigger('0x0017880104e45520');
|
||||
await mockMQTTEvents.message(
|
||||
@@ -1792,6 +1856,19 @@ describe('Extension: HomeAssistant', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test.each(['attribute_and_json', 'json', 'attribute'])('Should publish /action for MQTT device trigger', async (output) => {
|
||||
settings.set(['advanced', 'output'], output);
|
||||
mockMQTTPublishAsync.mockClear();
|
||||
|
||||
const device = devices.WXKG11LM;
|
||||
const payload1 = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
|
||||
await mockZHEvents.message(payload1);
|
||||
await flushPromises();
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/button/action', 'single', {retain: false, qos: 0});
|
||||
expect(mockMQTTPublishAsync.mock.calls.filter((c) => c[1] === 'single')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('Should not discover device_automation when disabled', async () => {
|
||||
settings.set(['device_options'], {
|
||||
homeassistant: {device_automation: null},
|
||||
@@ -2250,7 +2327,7 @@ describe('Extension: HomeAssistant', () => {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
|
||||
let payload = {
|
||||
let payload: KeyValueAny = {
|
||||
name: 'Chill scene',
|
||||
command_topic: 'zigbee2mqtt/bulb_color_2/set',
|
||||
payload_on: '{ "scene_recall": 1 }',
|
||||
|
||||
@@ -11,8 +11,6 @@ import * as settings from '../../lib/util/settings';
|
||||
|
||||
const mockOnEvent = vi.fn();
|
||||
const mockLivoloOnEvent = vi.fn();
|
||||
const mappedLivolo = zhc.findByModel('TI0001')!;
|
||||
mappedLivolo.onEvent = mockLivoloOnEvent;
|
||||
// @ts-expect-error mock
|
||||
zhc.onEvent = mockOnEvent;
|
||||
|
||||
@@ -21,6 +19,11 @@ const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug];
|
||||
describe('Extension: OnEvent', () => {
|
||||
let controller: Controller;
|
||||
|
||||
beforeAll(async () => {
|
||||
const mappedLivolo = (await zhc.findByDevice(devices.LIVOLO))!;
|
||||
mappedLivolo.onEvent = mockLivoloOnEvent;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.useFakeTimers();
|
||||
data.writeDefaultConfiguration();
|
||||
|
||||
@@ -7,7 +7,7 @@ import {devices, groups, events as mockZHEvents} from '../mocks/zigbeeHerdsman';
|
||||
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
|
||||
import {toZigbee} from 'zigbee-herdsman-converters';
|
||||
import * as zhcGlobalStore from 'zigbee-herdsman-converters/lib/store';
|
||||
|
||||
import {Controller} from '../../lib/controller';
|
||||
import {loadTopicGetSetRegex} from '../../lib/extension/publish';
|
||||
@@ -58,7 +58,7 @@ describe('Extension: Publish', () => {
|
||||
g.command.mockClear();
|
||||
});
|
||||
|
||||
toZigbee.__clearStore__();
|
||||
zhcGlobalStore.clear();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -1511,7 +1511,7 @@ describe('Extension: Publish', () => {
|
||||
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', brightness: 20, transition: 0.0}));
|
||||
await flushPromises();
|
||||
|
||||
toZigbee.__clearStore__();
|
||||
zhcGlobalStore.clear();
|
||||
|
||||
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', transition: 1.0}));
|
||||
await flushPromises();
|
||||
|
||||
+40
-1
@@ -46,7 +46,7 @@ describe('Logger', () => {
|
||||
expect(dirs.length).toBe(1);
|
||||
});
|
||||
|
||||
it('Should cleanup', () => {
|
||||
it('Should cleanup (default setting)', () => {
|
||||
for (const d of fs.readdirSync(dir.name)) {
|
||||
rimrafSync(path.join(dir.name, d));
|
||||
}
|
||||
@@ -60,6 +60,21 @@ describe('Logger', () => {
|
||||
expect(fs.readdirSync(dir.name).length).toBe(10);
|
||||
});
|
||||
|
||||
it('Should cleanup (15 folders setting)', () => {
|
||||
for (const d of fs.readdirSync(dir.name)) {
|
||||
rimrafSync(path.join(dir.name, d));
|
||||
}
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
fs.mkdirSync(path.join(dir.name, `log_${i}`));
|
||||
}
|
||||
|
||||
settings.set(['advanced', 'log_directories_to_keep'], 15);
|
||||
expect(fs.readdirSync(dir.name).length).toBe(20);
|
||||
logger.init();
|
||||
expect(fs.readdirSync(dir.name).length).toBe(15);
|
||||
});
|
||||
|
||||
it('Should not cleanup when there is no timestamp set', () => {
|
||||
for (const d of fs.readdirSync(dir.name)) {
|
||||
rimrafSync(path.join(dir.name, d));
|
||||
@@ -397,4 +412,28 @@ describe('Logger', () => {
|
||||
expect(logSpy).toHaveBeenLastCalledWith('debug', `z2m:test: ${splatChars}`);
|
||||
expect(consoleWriteSpy.mock.calls[1][0]).toMatch(new RegExp(`^.*\tz2m:test: ${splatChars}`));
|
||||
});
|
||||
|
||||
it('Logs to console in JSON when configured', () => {
|
||||
settings.set(['advanced', 'log_console_json'], true);
|
||||
logger.init();
|
||||
|
||||
consoleWriteSpy.mockClear();
|
||||
logger.info(`Test JSON message`, 'z2m');
|
||||
|
||||
const outputJSON = JSON.parse(consoleWriteSpy.mock.calls[0][0]);
|
||||
expect(outputJSON).toStrictEqual({
|
||||
level: 'info',
|
||||
message: 'z2m: Test JSON message',
|
||||
timestamp: expect.any(String),
|
||||
});
|
||||
|
||||
settings.set(['advanced', 'log_console_json'], false);
|
||||
logger.init();
|
||||
|
||||
consoleWriteSpy.mockClear();
|
||||
logger.info(`Test JSON message`, 'z2m');
|
||||
|
||||
const outputStr: string = consoleWriteSpy.mock.calls[0][0];
|
||||
expect(outputStr.trim().endsWith('\u001b[32minfo\u001b[39m: \tz2m: Test JSON message')).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -205,6 +205,10 @@ export const DEFAULT_CONFIGURATION = {
|
||||
'0x000b57cdfec6a5b3': {
|
||||
friendly_name: 'hue_twilight',
|
||||
},
|
||||
'0x00124b00cfcf3298': {
|
||||
friendly_name: 'fanbee',
|
||||
retain: true,
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
1: {
|
||||
|
||||
@@ -1117,6 +1117,35 @@ export const devices = {
|
||||
undefined,
|
||||
CUSTOM_CLUSTERS,
|
||||
),
|
||||
InovelliVZM31SN: new Device(
|
||||
'Router',
|
||||
'0xb43a31fffe2f1f6a',
|
||||
59545,
|
||||
4655,
|
||||
[
|
||||
new Endpoint(1, [], [], '0xb43a31fffe2f1f6a', [], {}, [], 1, 1, {multiEndpointSkip: ['state', 'power', 'energy', 'brightness']}),
|
||||
new Endpoint(2, [], [], '0xb43a31fffe2f1f6a', [], {}, [], 1, 1, {multiEndpointSkip: ['state', 'power', 'energy', 'brightness']}),
|
||||
new Endpoint(3, [], [], '0xb43a31fffe2f1f6a', [], {}, [], 1, 1, {multiEndpointSkip: ['state', 'power', 'energy', 'brightness']}),
|
||||
],
|
||||
true,
|
||||
'Mains (single phase)',
|
||||
'VZM31-SN',
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
CUSTOM_CLUSTERS,
|
||||
),
|
||||
fanbee: new Device(
|
||||
'Router',
|
||||
'0x00124b00cfcf3298',
|
||||
18129,
|
||||
0xfff1,
|
||||
[new Endpoint(8, [0, 3, 4, 5, 6, 8], []), new Endpoint(242, [], [33])],
|
||||
true,
|
||||
'DC Source',
|
||||
'FanBee1',
|
||||
),
|
||||
};
|
||||
|
||||
export const mockController = {
|
||||
|
||||
@@ -52,6 +52,37 @@ describe('Settings', () => {
|
||||
clearEnvironmentVariables();
|
||||
});
|
||||
|
||||
it('Ensures configuration.example.yaml is never out of sync with settings', () => {
|
||||
let path = './data/configuration.example.yaml';
|
||||
|
||||
// allow running in single-test mode
|
||||
if (!fs.existsSync('./data')) {
|
||||
path = '../data/configuration.example.yaml';
|
||||
}
|
||||
|
||||
const exampleYaml = read(path) as Record<string, unknown>;
|
||||
|
||||
// force keeping an eye on example yaml whenever CURRENT_VERSION changes
|
||||
expect(exampleYaml).toStrictEqual({
|
||||
version: settings.CURRENT_VERSION,
|
||||
homeassistant: {
|
||||
enabled: false,
|
||||
},
|
||||
frontend: {
|
||||
enabled: true,
|
||||
},
|
||||
mqtt: {
|
||||
base_topic: 'zigbee2mqtt',
|
||||
server: 'mqtt://localhost',
|
||||
},
|
||||
advanced: {
|
||||
network_key: 'GENERATE',
|
||||
pan_id: 'GENERATE',
|
||||
ext_pan_id: 'GENERATE',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return default settings', () => {
|
||||
write(configurationFile, {});
|
||||
const s = settings.get();
|
||||
@@ -554,6 +585,25 @@ describe('Settings', () => {
|
||||
expect(settings.get().groups).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('Should allow username without password', () => {
|
||||
const contentConfiguration = {
|
||||
mqtt: {
|
||||
server: 'my.mqtt.server',
|
||||
user: 'myusername',
|
||||
},
|
||||
};
|
||||
const expected = {
|
||||
base_topic: 'zigbee2mqtt',
|
||||
include_device_information: false,
|
||||
maximum_packet_size: 1048576,
|
||||
force_disable_retain: false,
|
||||
server: 'my.mqtt.server',
|
||||
user: 'myusername',
|
||||
};
|
||||
write(configurationFile, contentConfiguration);
|
||||
expect(settings.get().mqtt).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it('Should add groups with specific ID', () => {
|
||||
write(configurationFile, {});
|
||||
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
#!/bin/bash -e
|
||||
#!/usr/bin/env bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
NEED_RESTART=0
|
||||
|
||||
if which systemctl 2> /dev/null > /dev/null; then
|
||||
OSNAME="$(uname -s)"
|
||||
if [ "$OSNAME" == "FreeBSD" ]; then
|
||||
echo "Checking Zigbee2MQTT status..."
|
||||
if systemctl is-active --quiet zigbee2mqtt; then
|
||||
if service zigbee2mqtt status >/dev/null; then
|
||||
echo "Stopping Zigbee2MQTT..."
|
||||
sudo systemctl stop zigbee2mqtt
|
||||
service zigbee2mqtt stop
|
||||
NEED_RESTART=1
|
||||
fi
|
||||
else
|
||||
echo "Skipped stopping Zigbee2MQTT, no systemctl found"
|
||||
if which systemctl 2> /dev/null > /dev/null; then
|
||||
echo "Checking Zigbee2MQTT status..."
|
||||
if systemctl is-active --quiet zigbee2mqtt; then
|
||||
echo "Stopping Zigbee2MQTT..."
|
||||
sudo systemctl stop zigbee2mqtt
|
||||
NEED_RESTART=1
|
||||
fi
|
||||
else
|
||||
echo "Skipped stopping Zigbee2MQTT, no systemctl found"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Updating..."
|
||||
@@ -25,7 +35,11 @@ pnpm run build
|
||||
|
||||
if [ $NEED_RESTART -eq 1 ]; then
|
||||
echo "Starting Zigbee2MQTT..."
|
||||
sudo systemctl start zigbee2mqtt
|
||||
if [ "$OSNAME" == "FreeBSD" ]; then
|
||||
service zigbee2mqtt start
|
||||
else
|
||||
sudo systemctl start zigbee2mqtt
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Done!"
|
||||
|
||||
Reference in New Issue
Block a user