Compare commits

...
48 Commits
Author SHA1 Message Date
Koen Kanters 5e9d53f0aa chore(dev): release 2.1.0 (#25439) 2025-02-01 21:23:35 +01:00
Koen Kanters 8c7961de1f Update zigbee-herdsman-converters to 21.27.1 (#26102) 2025-02-01 20:01:44 +00:00
Koen Kanters ff466f817f chore: merge master to dev 2025-02-01 20:57:03 +01:00
Koen Kanters c7a7c3d8c2 chore: lint codeql.yaml 2025-02-01 20:56:47 +01:00
Koen Kanters 7540a0d02f chore: merge master to dev 2025-02-01 20:54:57 +01:00
Koen Kanters 33d5f0595d chore: Create codeql.yaml 2025-02-01 20:54:42 +01:00
Koen Kanters c4510d7382 chore: merge master to dev 2025-02-01 20:52:04 +01:00
Koen Kanters e670524241 chore: remove codeql.yaml 2025-02-01 20:51:44 +01:00
Koen Kanters 75f8ed07ed Update zigbee-herdsman to 3.2.5 (#26101) 2025-02-01 19:47:49 +00:00
Koen Kanters 0706a5e039 Update zigbee-herdsman-converters to 21.27.0 (#26071) 2025-01-30 20:15:53 +00:00
Koen Kanters 32329695d0 Update zigbee-herdsman-converters to 21.26.0 (#26043) 2025-01-28 18:37:44 +00:00
Tatham Oddie cdbd55320a fix: Add Home Assistant device class for flow rates (e.g., Sonoff SWV) (#26035) 2025-01-28 19:12:00 +01:00
Koen Kanters 7b199f898e Update zigbee-herdsman-converters to 21.25.0 (#26032) 2025-01-27 20:00:24 +00:00
Rob Coleman 0462b94f5a fix: Only use endpoint-specific states if the device definition uses them (#26019) 2025-01-27 20:54:01 +01:00
Koen Kanters 85460cd5ce fix(ignore): update dependencies (#26005) 2025-01-27 19:44:50 +01:00
Koen Kanters 420a9f2ff6 Update zigbee-herdsman-converters to 21.24.0 (#26000) 2025-01-25 19:04:47 +00:00
gracianodias3 5cf4547fe0 fix: Allow to set throttle via frontend (#25997) 2025-01-25 20:00:09 +01:00
Koen Kanters 2af65cf28e fix: Fix duplicate action publish when advanced.output has attribute (#25963) 2025-01-23 21:01:56 +01:00
Koen Kanters e9f74f0958 Update zigbee-herdsman-converters to 21.23.0 (#25951) 2025-01-22 19:28:32 +00:00
Koen Kanters 2567b4c39e Update zigbee-herdsman to 3.2.4 (#25919) 2025-01-20 21:01:53 +00:00
Koen Kanters ac1671609c Update zigbee-herdsman-converters to 21.22.0 (#25918) 2025-01-20 20:58:50 +00:00
Koen Kanters 8da5fa663d fix(ignore): update dependencies (#25890) 2025-01-19 21:23:36 +01:00
Koen Kanters 3d3c68b54a Update zigbee-herdsman-converters to 21.21.1 (#25872) 2025-01-19 13:04:23 +00:00
Koen Kanters 5e60f1dc8d fix(ignore): Remove adapter auto from schema (#25896) 2025-01-19 12:58:35 +00:00
Koen Kanters a1f74a312a Update zigbee-herdsman to 3.2.3 (#25895) 2025-01-19 12:32:15 +00:00
Koen Kanters b21167b326 Update zigbee-herdsman-converters to 21.20.0 (#25837) 2025-01-15 19:54:49 +00:00
Koen Kanters be17e698a1 Update zigbee-herdsman-converters to 21.19.0 (#25797) 2025-01-13 20:07:23 +00:00
Koen Kanters 7392557af2 Update zigbee-herdsman-converters to 21.18.0 (#25779) 2025-01-12 21:18:15 +00:00
Koen Kanters 360be57079 Update zigbee-herdsman-converters to 21.17.0 (#25770) 2025-01-12 13:43:05 +00:00
Koen Kanters 361f9eef8b Update zigbee-herdsman to 3.2.2 (#25769) 2025-01-12 13:17:20 +00:00
Koen Kanters 5b82d7e4df fix(ignore): update dependencies (#25760) 2025-01-12 14:12:38 +01:00
Koen Kanters 9daeba1f0a fix: Fix Failed to call 'Frontend' 'stop' (TypeError: Cannot read properties of undefined (reading 'close') https://github.com/Koenkk/zigbee2mqtt/issues/25715 2025-01-11 14:38:21 +01:00
Koen Kanters 2d5e7bf9ff fix: Fix Error: write after end error (#25737) 2025-01-11 14:11:26 +01:00
Koen Kanters b5bef45cb2 Update zigbee-herdsman-converters to 21.16.0 (#25735) 2025-01-10 20:15:20 +00:00
NerivecandKoen Kanters f1f73bd464 fix: Long startup time when OnEvent 'start' times out (repeatedly) (#25693)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-01-09 22:22:19 +01:00
Nerivec 23b709cee2 fix(ignore): Ensure config example is never out of sync with settings (#25707) 2025-01-09 20:37:01 +01:00
Koen Kanters 31bdf02a72 Update zigbee-herdsman-converters to 21.15.0 (#25690) 2025-01-08 21:27:05 +00:00
Jens-Uwe MagerandNerivec 26ef565c8a feat: Add a settings option to log to console in json format (#25649)
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
2025-01-08 19:22:26 +00:00
Koen Kanters 6f3524b251 fix(ignore): update dependencies (#25535) 2025-01-07 19:08:30 +00:00
Jan BouwhuisandKoen Kanters 318dc04132 feat: Allow Home Assistant OTA Update entity to show progress while updating (#25632)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2025-01-07 20:04:45 +01:00
Koen Kanters bf354f2121 Update zigbee-herdsman-converters to 21.14.0 (#25635) 2025-01-06 21:15:58 +00:00
Koen Kanters 1667ba64ac fix: Remove invalid warn log_level https://github.com/nurikk/zigbee2mqtt-frontend/issues/2369 (#25634) 2025-01-06 21:45:55 +01:00
SteffenBeermann a28a5baf17 fix: Enable use of MQTT username without password (#25611) 2025-01-06 20:19:18 +01:00
Koen Kanters c33946ffc3 Update zigbee-herdsman-converters to 21.13.0 (#25563) 2025-01-05 13:59:54 +00:00
RoboMagus 0d30eb553b fix: Add semver tags to Docker releases (#25530) 2025-01-05 13:03:38 +00:00
Devin Buhl 43a5b24dcc fix: Container: remove modifying capabilities of /usr/bin/node (#25456) 2025-01-05 13:36:57 +01:00
Koen Kanters 3c46cfb2e9 chore: promote to dev 2025-01-03 22:00:11 +01:00
Koen Kanters 7972950c84 chore: fix ci.yaml 2025-01-03 21:59:40 +01:00
25 changed files with 977 additions and 673 deletions
+14 -3
View File
@@ -85,6 +85,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 +106,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 +168,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
+75 -9
View File
@@ -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}}'
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "2.0.0"
".": "2.1.0"
}
+29
View File
@@ -1,5 +1,34 @@
# Changelog
## [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)
+3 -2
View File
@@ -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
+1 -2
View File
@@ -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
@@ -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
+1 -1
View File
@@ -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 {
+7 -1
View File
@@ -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;
+3 -10
View File
@@ -172,6 +172,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
energy: {device_class: 'energy', state_class: 'total_increasing'},
external_temperature_input: {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'},
@@ -1294,7 +1295,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 +1406,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') }}}`,
},
};
configs.push(updateSensor);
@@ -1607,10 +1604,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`;
}
+1 -1
View File
@@ -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)));
+13 -2
View File
@@ -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> {
+3
View File
@@ -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`);
}
+1
View File
@@ -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;
+17 -15
View File
@@ -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}`;
}),
),
}),
);
@@ -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 */
}
+17 -4
View File
@@ -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"
@@ -754,10 +760,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",
+1
View File
@@ -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%'),
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "2.0.0",
"version": "2.1.0",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"repository": {
@@ -61,32 +61,32 @@
"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",
"zigbee-herdsman": "3.2.5",
"zigbee-herdsman-converters": "21.27.1",
"zigbee2mqtt-frontend": "0.9.4"
},
"devDependencies": {
"@eslint/core": "^0.9.1",
"@eslint/js": "^9.17.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@eslint/core": "^0.10.0",
"@eslint/js": "^9.19.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@types/eslint__js": "^8.42.3",
"@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.10.10",
"@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",
"@types/ws": "8.5.14",
"@vitest/coverage-v8": "^3.0.4",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"prettier": "^3.4.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.21.0",
"vitest": "^3.0.4"
},
"pnpm": {
"overrides": {
+569 -545
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -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');
+33 -54
View File
@@ -118,6 +118,7 @@ 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: {},
@@ -314,28 +315,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 +546,7 @@ describe('Extension: Bridge', () => {
description: 'this is my bulb',
disabled: false,
endpoints: {
1: {
'1': {
bindings: [],
clusters: {
input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl'],
@@ -576,7 +571,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 +747,7 @@ describe('Extension: Bridge', () => {
},
disabled: false,
endpoints: {
1: {
'1': {
bindings: [],
clusters: {
input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl'],
@@ -776,7 +770,6 @@ describe('Extension: Bridge', () => {
type: 'Router',
},
{
date_code: undefined,
definition: {
description: 'Hue dimmer switch',
exposes: [
@@ -880,7 +873,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 +885,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 +894,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 +972,7 @@ describe('Extension: Bridge', () => {
},
disabled: false,
endpoints: {
1: {
'1': {
bindings: [],
clusters: {input: ['genBasic'], output: ['genBasic', 'genOnOff', 'genLevelCtrl', 'genScenes']},
configured_reportings: [],
@@ -996,12 +987,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 +1074,7 @@ describe('Extension: Bridge', () => {
},
disabled: false,
endpoints: {
1: {
'1': {
bindings: [],
clusters: {input: ['genBasic'], output: ['genBasic', 'genOnOff', 'genLevelCtrl', 'genScenes']},
configured_reportings: [
@@ -1107,12 +1096,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 +1229,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 +1237,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 +1376,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 +1384,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 +1418,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 +1654,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 +1833,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 +1884,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 +2102,7 @@ describe('Extension: Bridge', () => {
},
disabled: false,
endpoints: {
1: {
'1': {
bindings: [],
clusters: {
input: ['genBasic', 'genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl'],
@@ -2135,11 +2116,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 +3172,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' +
+26
View File
@@ -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', () => {
@@ -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;
+15 -9
View File
@@ -1674,17 +1674,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') }}}",
};
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('homeassistant/update/0x000b57fffec6a5b2/update/config', stringify(payload), {
@@ -1727,8 +1724,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 +1747,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 +1785,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},
+24
View File
@@ -397,4 +397,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);
});
});
+19
View File
@@ -1117,6 +1117,25 @@ 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,
),
};
export const mockController = {
+50
View File
@@ -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, {});