From 36ea1a48a90df4164c129c3bbbbb6137cffb2ebf Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:11:18 +0200 Subject: [PATCH 01/10] Update .npmignore --- .npmignore | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.npmignore b/.npmignore index 85cd9dd..02f4f67 100644 --- a/.npmignore +++ b/.npmignore @@ -147,7 +147,19 @@ __test__ .eslintrc .eslintrc.json .eslintignore +eslint.config.js # prettier .prettierignore .prettierrc.json +prettier.config.js + +# jest +jest.config.js + +# screenshot +screenshot + +# other stuff +TODO.md +yellow-button.png \ No newline at end of file From c4028d63d1988910a7eac9b4af4b5dfa9180b98c Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:11:24 +0200 Subject: [PATCH 02/10] Create matterbridge-zigbee2mqtt.schema.json --- matterbridge-zigbee2mqtt.schema.json | 102 +++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 matterbridge-zigbee2mqtt.schema.json diff --git a/matterbridge-zigbee2mqtt.schema.json b/matterbridge-zigbee2mqtt.schema.json new file mode 100644 index 0000000..1c5d718 --- /dev/null +++ b/matterbridge-zigbee2mqtt.schema.json @@ -0,0 +1,102 @@ +{ + "title": "Matterbridge zigbee2mqtt plugin", + "description": "matterbridge-zigbee2mqtt v. 2.0.17 by https://github.com/Luligu", + "type": "object", + "required": [ + "host", + "port", + "topic" + ], + "properties": { + "name": { + "description": "Plugin name", + "type": "string", + "readOnly": true + }, + "type": { + "description": "Plugin type", + "type": "string", + "readOnly": true + }, + "host": { + "description": "Host", + "type": "string" + }, + "username": { + "description": "Username", + "type": "string" + }, + "password": { + "description": "Password", + "type": "string" + }, + "port": { + "description": "Port", + "type": "number" + }, + "topic": { + "description": "Topic", + "type": "string" + }, + "blackList": { + "description": "The devices in the list will not be exposed.", + "type": "array", + "items": { + "type": "string" + } + }, + "whiteList": { + "description": "Only the devices in the list will be exposed.", + "type": "array", + "items": { + "type": "string" + } + }, + "switchList": { + "description": "The devices in the list will be exposed like switches.", + "type": "array", + "items": { + "type": "string" + } + }, + "lightList": { + "description": "The devices in the list will be exposed like lights.", + "type": "array", + "items": { + "type": "string" + } + }, + "outletList": { + "description": "The devices in the list will be exposed like outlets.", + "type": "array", + "items": { + "type": "string" + } + }, + "featureBlackList": { + "description": "The features in the list will not be exposed for all devices.", + "type": "array", + "items": { + "type": "string" + } + }, + "deviceFeatureBlackList": { + "description": "List of features not to be exposed for a single device. Enter in the first field the name of the device and in the second field add all the features to exclude.", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "debug": { + "description": "Enable the debug for the plugin (development only)", + "type": "boolean" + }, + "unregisterOnShutdown": { + "description": "Unregister all devices on shutdown (development only)", + "type": "boolean" + } + } +} \ No newline at end of file From ac021486e2c59a1d8efab47d42714d4daff34103 Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Mon, 17 Jun 2024 23:02:58 +0200 Subject: [PATCH 03/10] Update dependencies --- package-lock.json | 114 +++++++++++++++++++++++----------------------- package.json | 6 +-- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf22d90..136bc67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,16 +18,16 @@ "@eslint/js": "^9.5.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.12", - "@types/node": "^20.14.2", + "@types/node": "^20.14.3", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "prettier": "^3.3.2", "rimraf": "^5.0.7", - "ts-jest": "^29.1.4", + "ts-jest": "^29.1.5", "typescript": "^5.4.5", - "typescript-eslint": "^7.13.0" + "typescript-eslint": "^7.13.1" }, "engines": { "node": ">=18.0.0" @@ -1617,9 +1617,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", - "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "version": "20.14.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.3.tgz", + "integrity": "sha512-Nuzqa6WAxeGnve6SXqiPAM9rA++VQs+iLZ1DDd56y0gdvygSZlQvZuvdFPR3yLqkVxPu4WrO02iDEyH1g+wazw==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -1669,17 +1669,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.0.tgz", - "integrity": "sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.1.tgz", + "integrity": "sha512-kZqi+WZQaZfPKnsflLJQCz6Ze9FFSMfXrrIOcyargekQxG37ES7DJNpJUE9Q/X5n3yTIP/WPutVNzgknQ7biLg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.13.0", - "@typescript-eslint/type-utils": "7.13.0", - "@typescript-eslint/utils": "7.13.0", - "@typescript-eslint/visitor-keys": "7.13.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/type-utils": "7.13.1", + "@typescript-eslint/utils": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1703,16 +1703,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.0.tgz", - "integrity": "sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.1.tgz", + "integrity": "sha512-1ELDPlnLvDQ5ybTSrMhRTFDfOQEOXNM+eP+3HT/Yq7ruWpciQw+Avi73pdEbA4SooCawEWo3dtYbF68gN7Ed1A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.13.0", - "@typescript-eslint/types": "7.13.0", - "@typescript-eslint/typescript-estree": "7.13.0", - "@typescript-eslint/visitor-keys": "7.13.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "debug": "^4.3.4" }, "engines": { @@ -1732,14 +1732,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz", - "integrity": "sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.1.tgz", + "integrity": "sha512-adbXNVEs6GmbzaCpymHQ0MB6E4TqoiVbC0iqG3uijR8ZYfpAXMGttouQzF4Oat3P2GxDVIrg7bMI/P65LiQZdg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.13.0", - "@typescript-eslint/visitor-keys": "7.13.0" + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1750,14 +1750,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.0.tgz", - "integrity": "sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.1.tgz", + "integrity": "sha512-aWDbLu1s9bmgPGXSzNCxELu+0+HQOapV/y+60gPXafR8e2g1Bifxzevaa+4L2ytCWm+CHqpELq4CSoN9ELiwCg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.13.0", - "@typescript-eslint/utils": "7.13.0", + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/utils": "7.13.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1778,9 +1778,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.0.tgz", - "integrity": "sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.1.tgz", + "integrity": "sha512-7K7HMcSQIAND6RBL4kDl24sG/xKM13cA85dc7JnmQXw2cBDngg7c19B++JzvJHRG3zG36n9j1i451GBzRuHchw==", "dev": true, "license": "MIT", "engines": { @@ -1792,14 +1792,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz", - "integrity": "sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.1.tgz", + "integrity": "sha512-uxNr51CMV7npU1BxZzYjoVz9iyjckBduFBP0S5sLlh1tXYzHzgZ3BR9SVsNed+LmwKrmnqN3Kdl5t7eZ5TS1Yw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.13.0", - "@typescript-eslint/visitor-keys": "7.13.0", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1847,16 +1847,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.0.tgz", - "integrity": "sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.1.tgz", + "integrity": "sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.13.0", - "@typescript-eslint/types": "7.13.0", - "@typescript-eslint/typescript-estree": "7.13.0" + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1870,13 +1870,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz", - "integrity": "sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.1.tgz", + "integrity": "sha512-k/Bfne7lrP7hcb7m9zSsgcBmo+8eicqqfNAJ7uUY+jkTFpKeH2FSkWpFRtimBxgkyvqfu9jTPRbYOvud6isdXA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.13.0", + "@typescript-eslint/types": "7.13.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -5699,9 +5699,9 @@ } }, "node_modules/ts-jest": { - "version": "29.1.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.4.tgz", - "integrity": "sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==", + "version": "29.1.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", + "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", "dev": true, "license": "MIT", "dependencies": { @@ -5810,15 +5810,15 @@ } }, "node_modules/typescript-eslint": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.13.0.tgz", - "integrity": "sha512-upO0AXxyBwJ4BbiC6CRgAJKtGYha2zw4m1g7TIVPSonwYEuf7vCicw3syjS1OxdDMTz96sZIXl3Jx3vWJLLKFw==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.13.1.tgz", + "integrity": "sha512-pvLEuRs8iS9s3Cnp/Wt//hpK8nKc8hVa3cLljHqzaJJQYP8oys8GUyIFqtlev+2lT/fqMPcyQko+HJ6iYK3nFA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "7.13.0", - "@typescript-eslint/parser": "7.13.0", - "@typescript-eslint/utils": "7.13.0" + "@typescript-eslint/eslint-plugin": "7.13.1", + "@typescript-eslint/parser": "7.13.1", + "@typescript-eslint/utils": "7.13.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" diff --git a/package.json b/package.json index e1ee6d2..8b8b62a 100644 --- a/package.json +++ b/package.json @@ -92,15 +92,15 @@ "@eslint/js": "^9.5.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.12", - "@types/node": "^20.14.2", + "@types/node": "^20.14.3", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "prettier": "^3.3.2", "rimraf": "^5.0.7", - "ts-jest": "^29.1.4", + "ts-jest": "^29.1.5", "typescript": "^5.4.5", - "typescript-eslint": "^7.13.0" + "typescript-eslint": "^7.13.1" } } From 3b78c7e675ca70bb3e566e1e25f868c30da1d03b Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Mon, 17 Jun 2024 23:03:14 +0200 Subject: [PATCH 04/10] Added soil_moisture as humidity sensor --- src/entity.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/entity.ts b/src/entity.ts index 22ff35f..2effa41 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -554,6 +554,7 @@ export const z2ms: ZigbeeToMatter[] = [ { type: '', name: 'carbon_monoxide', property: 'carbon_monoxide', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value } }, { type: '', name: 'temperature', property: 'temperature', deviceType: DeviceTypes.TEMPERATURE_SENSOR, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100) } }, { type: '', name: 'humidity', property: 'humidity', deviceType: DeviceTypes.HUMIDITY_SENSOR, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100) } }, + { type: '', name: 'soil_moisture', property: 'soil_moisture', deviceType: DeviceTypes.HUMIDITY_SENSOR, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100) } }, { type: '', name: 'pressure', property: 'pressure', deviceType: DeviceTypes.PRESSURE_SENSOR, cluster: PressureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return value } }, { type: '', name: 'air_quality', property: 'air_quality', deviceType: airQualitySensor, cluster: AirQuality.Cluster.id, attribute: 'airQuality', valueLookup: ['unknown', 'excellent', 'good', 'moderate', 'poor', 'unhealthy', 'out_of_range'] }, { type: '', name: 'voc', property: 'voc', deviceType: airQualitySensor, cluster: TvocMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.min(65535, value) } }, From 8e0a6b9b9a5a2358735a48beacd2509905b40fa1 Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:26:09 +0200 Subject: [PATCH 05/10] Release 2.0.18 --- CHANGELOG.md | 17 +++++++++ package.json | 4 +-- src/entity.ts | 97 +++++++++++++++++++++++++-------------------------- 3 files changed, 67 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1851734..48ca4a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. +## [2.0.18] - 2024-06-17 + +### Added + +- [dependencies]: Update dependencies +- [schema]: Added schema to the root directory of the plugin +- [properties]: Added soil_moisture properties as humidity sensor +- [properties]: Added transition if the zigbee device supports it and the controller sends it. You can disable this globally adding transition to the featureBlackList or only for the single device adding transition to the deviceFeatureBlackList. (Thanks Stefan Schweiger) + +### Fixed +- [schema]: Username and password are no more required fields (Thanks Stefan Schweiger) +- [LevelControl]: Fixed the commandHandler for LevelControl in child endpoint (Thanks jpadie). + + + Buy me a coffee + + ## [2.0.17] - 2024-06-16 ### Fixed diff --git a/package.json b/package.json index 8b8b62a..2eb2a8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-zigbee2mqtt", - "version": "2.0.17", + "version": "2.0.18", "description": "Matterbridge zigbee2mqtt plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", @@ -103,4 +103,4 @@ "typescript": "^5.4.5", "typescript-eslint": "^7.13.1" } -} +} \ No newline at end of file diff --git a/src/entity.ts b/src/entity.ts index 2effa41..88ad04f 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -56,7 +56,6 @@ import { Endpoint, AtLeastOne, FixedLabelCluster, - EndpointNumber, SwitchCluster, ElectricalMeasurement, EveHistory, @@ -90,6 +89,7 @@ export class ZigbeeEntity extends EventEmitter { private lastPayload: Payload = {}; private lastSeen = 0; protected ignoreFeatures: string[] = []; + protected transition = false; constructor(platform: ZigbeePlatform, entity: BridgeDevice | BridgeGroup) { super(); @@ -644,10 +644,12 @@ export class ZigbeeDevice extends ZigbeeEntity { if (platform.featureBlackList) this.ignoreFeatures = [...this.ignoreFeatures, ...platform.featureBlackList]; if (platform.deviceFeatureBlackList[device.friendly_name]) this.ignoreFeatures = [...this.ignoreFeatures, ...platform.deviceFeatureBlackList[device.friendly_name]]; - // this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - types[${types.length}]: ${debugStringify(types)}`); - // this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - endpoints[${endpoints.length}]: ${debugStringify(endpoints)}`); - // this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - names[${names.length}]: ${debugStringify(names)}`); - // this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - properties[${properties.length}]: ${debugStringify(properties)}`); + /* + this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - types[${types.length}]: ${debugStringify(types)}`); + this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - endpoints[${endpoints.length}]: ${debugStringify(endpoints)}`); + this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - names[${names.length}]: ${debugStringify(names)}`); + this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} - properties[${properties.length}]: ${debugStringify(properties)}`); + */ names.forEach((name, index) => { if (platform.featureBlackList.includes(name)) { this.log.debug(`Device ${this.en}${device.friendly_name}${db} feature ${name} is globally blacklisted`); @@ -657,6 +659,10 @@ export class ZigbeeDevice extends ZigbeeEntity { this.log.debug(`Device ${this.en}${device.friendly_name}${db} feature ${name} is blacklisted`); return; } + if (name === 'transition') { + this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} transition is supported`); + this.transition = true; + } const type = types[index]; const endpoint = endpoints[index]; const z2m = z2ms.find((z2m) => z2m.type === type && z2m.name === name); @@ -726,28 +732,44 @@ export class ZigbeeDevice extends ZigbeeEntity { if (this.bridgedDevice.hasClusterServer(OnOff.Complete) || this.bridgedDevice.hasEndpoints) { this.bridgedDevice.addCommandHandler('on', async (data) => { this.log.debug(`Command on called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`); - const payload = this.bridgedDevice?.getChildPayload(data.endpoint.number, 'state', 'ON'); - if (payload) this.publishCommand('on', device.friendly_name, payload); + const payload: Payload = {}; + const label = this.bridgedDevice?.getEndpointLabel(data.endpoint.number); + label === undefined ? (payload['state'] = 'ON') : (payload['state_' + label] = 'ON'); + this.publishCommand('on', device.friendly_name, payload); }); this.bridgedDevice.addCommandHandler('off', async (data) => { this.log.debug(`Command off called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`); - const payload = this.bridgedDevice?.getChildPayload(data.endpoint.number, 'state', 'OFF'); - if (payload) this.publishCommand('off', device.friendly_name, payload); + const payload: Payload = {}; + const label = this.bridgedDevice?.getEndpointLabel(data.endpoint.number); + label === undefined ? (payload['state'] = 'OFF') : (payload['state_' + label] = 'OFF'); + this.publishCommand('off', device.friendly_name, payload); }); this.bridgedDevice.addCommandHandler('toggle', async (data) => { this.log.debug(`Command toggle called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`); - const payload = this.bridgedDevice?.getChildPayload(data.endpoint.number, 'state', 'TOGGLE'); - if (payload) this.publishCommand('toggle', device.friendly_name, payload); + const payload: Payload = {}; + const label = this.bridgedDevice?.getEndpointLabel(data.endpoint.number); + label === undefined ? (payload['state'] = 'TOGGLE') : (payload['state_' + label] = 'TOGGLE'); + this.publishCommand('toggle', device.friendly_name, payload); }); } - if (this.bridgedDevice.hasClusterServer(LevelControl.Complete)) { - this.bridgedDevice.addCommandHandler('moveToLevel', async ({ request: { level }, attributes: { currentLevel } }) => { - this.log.debug(`Command moveToLevel called for ${this.ien}${device.friendly_name}${rs}${db} request: ${level} attributes: ${currentLevel}`); - this.publishCommand('moveToLevel', device.friendly_name, { brightness: level }); + if (this.bridgedDevice.hasClusterServer(LevelControl.Complete) || this.bridgedDevice.hasEndpoints) { + this.bridgedDevice.addCommandHandler('moveToLevel', async ({ request: { level, transitionTime }, attributes: { currentLevel }, endpoint: { number } }) => { + this.log.debug(`Command moveToLevel called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${number} request: ${level} transition: ${transitionTime} attributes: ${currentLevel.getLocal()}`); + const payload: Payload = {}; + const label = this.bridgedDevice?.getEndpointLabel(number); + label === undefined ? (payload['brightness'] = level) : (payload['brightness_' + label] = level); + // transitionTime = 100; + if (this.transition && transitionTime && transitionTime / 10 >= 1) payload['transition'] = Math.round(transitionTime / 10); + this.publishCommand('moveToLevel', device.friendly_name, payload); }); - this.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async ({ request: { level }, attributes: { currentLevel } }) => { - this.log.debug(`Command moveToLevelWithOnOff called for ${this.ien}${device.friendly_name}${rs}${db} request: ${level} attributes: ${currentLevel}`); - this.publishCommand('moveToLevelWithOnOff', device.friendly_name, { brightness: level }); + this.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async ({ request: { level, transitionTime }, attributes: { currentLevel }, endpoint: { number } }) => { + this.log.debug(`Command moveToLevelWithOnOff called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${number} request: ${level} transition: ${transitionTime} attributes: ${currentLevel.getLocal()}`); + const payload: Payload = {}; + const label = this.bridgedDevice?.getEndpointLabel(number); + label === undefined ? (payload['brightness'] = level) : (payload['brightness_' + label] = level); + // transitionTime = 100; + if (this.transition && transitionTime && transitionTime / 10 >= 1) payload['transition'] = Math.round(transitionTime / 10); + this.publishCommand('moveToLevelWithOnOff', device.friendly_name, payload); }); } if (this.bridgedDevice.hasClusterServer(ColorControl.Complete) && this.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('colorTemperatureMireds')) { @@ -1075,6 +1097,14 @@ export class BridgedBaseDevice extends MatterbridgeDevice { this.addChildEndpoint(child); } + // Add device type to the child endpoint if is new + const deviceTypes = child.getDeviceTypes(); + if (deviceType && !deviceTypes.includes(deviceType)) { + this.log.debug(`addDeviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); + deviceTypes.push(deviceType); + child.setDeviceTypes(deviceTypes); + } + includeServerList.forEach((clusterId) => { this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`); }); @@ -1130,37 +1160,6 @@ export class BridgedBaseDevice extends MatterbridgeDevice { return child; } - getChildPayload(endpointNumber: EndpointNumber | undefined, key: string, value: string): Payload { - const payload: Payload = {}; - if (!endpointNumber) { - payload[key] = value; - this.log.debug('getChildStatePayload payload:', payload); - return payload; - } - const endpoint = this.getChildEndpoint(endpointNumber); - // this.log.debug('getChildStatePayload endpoint:', endpoint); - if (!endpoint) { - payload[key] = value; - this.log.debug('getChildStatePayload payload:', payload); - return payload; - } - const labelList = endpoint.getClusterServer(FixedLabelCluster)?.getLabelListAttribute(); - this.log.debug('getChildStatePayload labelList:', labelList); - if (!labelList) { - payload[key] = value; - this.log.debug('getChildStatePayload payload:', payload); - return payload; - } - for (const entry of labelList) { - if (entry.label === 'endpointName') { - payload['state_' + entry.value] = value; - this.log.debug('getChildStatePayload payload:', payload); - return payload; - } - } - return { unknown: value }; - } - configure() { if (this.getClusterServerById(WindowCovering.Cluster.id)) { this.log.debug(`Configuring ${this.deviceName} WindowCovering`); From 9de8622a39d7a449346b2ea0defb2503727d1ed1 Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:49:48 +0200 Subject: [PATCH 06/10] Release 2.0.18 --- src/entity.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/entity.ts b/src/entity.ts index 88ad04f..383f1f5 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -731,6 +731,7 @@ export class ZigbeeDevice extends ZigbeeEntity { }); if (this.bridgedDevice.hasClusterServer(OnOff.Complete) || this.bridgedDevice.hasEndpoints) { this.bridgedDevice.addCommandHandler('on', async (data) => { + if (!data.endpoint.number) return; this.log.debug(`Command on called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`); const payload: Payload = {}; const label = this.bridgedDevice?.getEndpointLabel(data.endpoint.number); @@ -738,6 +739,7 @@ export class ZigbeeDevice extends ZigbeeEntity { this.publishCommand('on', device.friendly_name, payload); }); this.bridgedDevice.addCommandHandler('off', async (data) => { + if (!data.endpoint.number) return; this.log.debug(`Command off called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`); const payload: Payload = {}; const label = this.bridgedDevice?.getEndpointLabel(data.endpoint.number); @@ -745,6 +747,7 @@ export class ZigbeeDevice extends ZigbeeEntity { this.publishCommand('off', device.friendly_name, payload); }); this.bridgedDevice.addCommandHandler('toggle', async (data) => { + if (!data.endpoint.number) return; this.log.debug(`Command toggle called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`); const payload: Payload = {}; const label = this.bridgedDevice?.getEndpointLabel(data.endpoint.number); @@ -754,20 +757,20 @@ export class ZigbeeDevice extends ZigbeeEntity { } if (this.bridgedDevice.hasClusterServer(LevelControl.Complete) || this.bridgedDevice.hasEndpoints) { this.bridgedDevice.addCommandHandler('moveToLevel', async ({ request: { level, transitionTime }, attributes: { currentLevel }, endpoint: { number } }) => { + if (!number) return; this.log.debug(`Command moveToLevel called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${number} request: ${level} transition: ${transitionTime} attributes: ${currentLevel.getLocal()}`); const payload: Payload = {}; const label = this.bridgedDevice?.getEndpointLabel(number); label === undefined ? (payload['brightness'] = level) : (payload['brightness_' + label] = level); - // transitionTime = 100; if (this.transition && transitionTime && transitionTime / 10 >= 1) payload['transition'] = Math.round(transitionTime / 10); this.publishCommand('moveToLevel', device.friendly_name, payload); }); this.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async ({ request: { level, transitionTime }, attributes: { currentLevel }, endpoint: { number } }) => { + if (!number) return; this.log.debug(`Command moveToLevelWithOnOff called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${number} request: ${level} transition: ${transitionTime} attributes: ${currentLevel.getLocal()}`); const payload: Payload = {}; const label = this.bridgedDevice?.getEndpointLabel(number); label === undefined ? (payload['brightness'] = level) : (payload['brightness_' + label] = level); - // transitionTime = 100; if (this.transition && transitionTime && transitionTime / 10 >= 1) payload['transition'] = Math.round(transitionTime / 10); this.publishCommand('moveToLevelWithOnOff', device.friendly_name, payload); }); @@ -916,9 +919,10 @@ export class BridgedBaseDevice extends MatterbridgeDevice { constructor(entity: ZigbeeEntity, definition: AtLeastOne, includeServerList: ClusterId[] = [], includeClientList?: ClusterId[]) { super(definition[0]); this.log = entity.log; + this.log.debug(`new BridgedBaseDevice ${entity.isDevice ? entity.device?.friendly_name : entity.group?.friendly_name}${db}`); definition.forEach((deviceType) => { this.addDeviceType(deviceType); - this.log.debug(`new BridgedBaseDevice ${entity.isDevice ? entity.device?.friendly_name : entity.group?.friendly_name} deviceType: ${hk}${deviceType.name}${db}`); + this.log.debug(`- with deviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); }); // Log all server clusters in the includelist From b5f98fc5f8db3446f6f2e21622d7a2412430dc44 Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:26:53 +0200 Subject: [PATCH 07/10] Refactor BridgeDevice to use methods from MatterbridgeDevice --- src/entity.ts | 61 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/entity.ts b/src/entity.ts index 383f1f5..e11ea05 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -62,6 +62,9 @@ import { getClusterNameById, FlowMeasurement, DoorLockCluster, + logEndpoint, + AttributeInitialValues, + powerSource, } from 'matterbridge'; import { AnsiLogger, TimestampFormat, gn, dn, ign, idn, rs, db, wr, debugStringify, hk, zb, or, nf } from 'node-ansi-logger'; @@ -561,13 +564,13 @@ export const z2ms: ZigbeeToMatter[] = [ //{ type: '', name: 'action', property: 'action', deviceType: DeviceTypes.GENERIC_SWITCH, cluster: Switch.Cluster.id, attribute: 'currentPosition' }, { type: '', name: 'cpu_temperature', property: 'temperature', deviceType: DeviceTypes.TEMPERATURE_SENSOR, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100) }}, { type: '', name: 'device_temperature', property: 'device_temperature', deviceType: DeviceTypes.TEMPERATURE_SENSOR, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100) } }, - { type: '', name: '', property: 'battery', deviceType: undefined, cluster: PowerSource.Cluster.id, attribute: 'batPercentRemaining', converter: (value) => { return Math.round(value * 2) } }, - { type: '', name: '', property: 'battery_low', deviceType: undefined, cluster: PowerSource.Cluster.id, attribute: 'batChargeLevel', converter: (value) => { return value === true ? PowerSource.BatChargeLevel.Critical : PowerSource.BatChargeLevel.Ok } }, - { type: '', name: '', property: 'battery_voltage', deviceType: undefined, cluster: PowerSource.Cluster.id, attribute: 'batVoltage', converter: (value) => { return value } }, - { type: '', name: 'energy', property: 'energy', deviceType: undefined, cluster: EveHistory.Cluster.id, attribute: 'TotalConsumption', converter: (value) => { return value } }, - { type: '', name: 'power', property: 'power', deviceType: undefined, cluster: EveHistory.Cluster.id, attribute: 'Consumption', converter: (value) => { return value } }, - { type: '', name: 'voltage', property: 'voltage', deviceType: undefined, cluster: EveHistory.Cluster.id, attribute: 'Voltage', converter: (value) => { return value } }, - { type: '', name: 'current', property: 'current', deviceType: undefined, cluster: EveHistory.Cluster.id, attribute: 'Current', converter: (value) => { return value } }, + { type: '', name: '', property: 'battery', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batPercentRemaining', converter: (value) => { return Math.round(value * 2) } }, + { type: '', name: '', property: 'battery_low', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batChargeLevel', converter: (value) => { return value === true ? PowerSource.BatChargeLevel.Critical : PowerSource.BatChargeLevel.Ok } }, + { type: '', name: '', property: 'battery_voltage', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batVoltage', converter: (value) => { return value } }, + { type: '', name: 'energy', property: 'energy', deviceType: powerSource, cluster: EveHistory.Cluster.id, attribute: 'TotalConsumption', converter: (value) => { return value } }, + { type: '', name: 'power', property: 'power', deviceType: powerSource, cluster: EveHistory.Cluster.id, attribute: 'Consumption', converter: (value) => { return value } }, + { type: '', name: 'voltage', property: 'voltage', deviceType: powerSource, cluster: EveHistory.Cluster.id, attribute: 'Voltage', converter: (value) => { return value } }, + { type: '', name: 'current', property: 'current', deviceType: powerSource, cluster: EveHistory.Cluster.id, attribute: 'Current', converter: (value) => { return value } }, //{ type: '', name: 'transmit_power', property: 'transmit_power', deviceType: DeviceTypes.DOOR_LOCK, cluster: DoorLock.Cluster.id, attribute: 'lockState' }, ]; /* eslint-enable */ @@ -715,7 +718,8 @@ export class ZigbeeDevice extends ZigbeeEntity { const deviceTypes = this.bridgedDevice.getDeviceTypes(); deviceTypes.forEach((deviceType) => { deviceType.requiredServerClusters.forEach((clusterId) => { - if (!this.bridgedDevice?.getClusterServerById(clusterId)) { + if (!this.bridgedDevice) return; + if (!this.bridgedDevice.getClusterServerById(clusterId)) { this.log.error(`Device type ${deviceType.name} (0x${deviceType.code.toString(16)}) requires cluster server ${getClusterNameById(clusterId)}(0x${clusterId.toString(16)}) but it is not present on endpoint`); this.bridgedDevice = undefined; } @@ -920,16 +924,15 @@ export class BridgedBaseDevice extends MatterbridgeDevice { super(definition[0]); this.log = entity.log; this.log.debug(`new BridgedBaseDevice ${entity.isDevice ? entity.device?.friendly_name : entity.group?.friendly_name}${db}`); + // Log and add all device type definitions definition.forEach((deviceType) => { this.addDeviceType(deviceType); this.log.debug(`- with deviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); }); - - // Log all server clusters in the includelist + // Log and add all server clusters in the includelist includeServerList.forEach((clusterId) => { this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`); }); - // Add all server clusters in the includelist this.addDeviceClusterServer(includeServerList); // Add BridgedDevice with PowerSourceInformation device type @@ -972,12 +975,13 @@ export class BridgedBaseDevice extends MatterbridgeDevice { * Adds mandatory clusters to the device * * @protected - * @param attributeInitialValues Optional object with initial attribute values for automatically added clusters * @param includeServerList List of clusters to include + * @param attributeInitialValues Optional object with initial attribute values for automatically added clusters */ - // attributeInitialValues?: { [key: ClusterId]: AttributeInitialValues } - // TODO use the base method from MatterbridgeDevice - protected addDeviceClusterServer(includeServerList: ClusterId[] = []) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars + protected addDeviceClusterServer(includeServerList: ClusterId[] = [], attributeInitialValues?: Record>) { + this.addClusterServerFromList(this, includeServerList); + /* if (includeServerList.includes(Identify.Cluster.id) && !this.hasClusterServer(Identify.Complete)) { this.createDefaultIdentifyClusterServer(); } @@ -1044,25 +1048,29 @@ export class BridgedBaseDevice extends MatterbridgeDevice { if (includeServerList.includes(TimeSync.Cluster.id) && !this.hasClusterServer(TimeSync.Complete)) { this.createDefaultTimeSyncClusterServer(); } + */ } /** * Adds mandatory client clusters to the device * * @protected - * @param attributeInitialValues Optional object with initial attribute values for automatically added clusters * @param includeClientList List of clusters to include + * @param attributeInitialValues Optional object with initial attribute values for automatically added clusters */ - // attributeInitialValues?: { [key: ClusterId]: AttributeInitialValues }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected addDeviceClusterClient(includeClientList: ClusterId[] = []) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + protected addDeviceClusterClient(includeClientList: ClusterId[] = [], attributeInitialValues?: Record>) { /* Not implemented since not supported by matter.js */ } public addDeviceTypeAndClusterServer(deviceType: DeviceTypeDefinition | undefined, serverList: ClusterId[]) { - this.log.debug(`addDeviceTypeAndClusterServer ${deviceType ? 'deviceType: ' + hk + deviceType.name + db : ''}`); - if (deviceType) this.addDeviceType(deviceType); - // Log all server clusters in the serverList + this.log.debug(`addDeviceTypeAndClusterServer`); + if (deviceType) { + // Log and add all device type definitions + this.log.debug(`- with deviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); + this.addDeviceType(deviceType); + } + // Log and add all server clusters in the serverList serverList.forEach((clusterId) => { this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`); }); @@ -1092,7 +1100,7 @@ export class BridgedBaseDevice extends MatterbridgeDevice { // Not found? Create a new one if (!child) { this.log.debug(`addChildDeviceTypeAndClusterServer: Child endpoint created: ${zb}${endpointName}${db}`); - child = new Endpoint([deviceType ?? DeviceTypes.ON_OFF_PLUGIN_UNIT]); + child = new Endpoint([deviceType ?? DeviceTypes.ON_OFF_PLUGIN_UNIT], { uniqueStorageKey: endpointName }); if (!deviceType) includeServerList.push(Identify.Cluster.id); if (!deviceType) includeServerList.push(Scenes.Cluster.id); if (!deviceType) includeServerList.push(Groups.Cluster.id); @@ -1101,18 +1109,20 @@ export class BridgedBaseDevice extends MatterbridgeDevice { this.addChildEndpoint(child); } - // Add device type to the child endpoint if is new + // Log and add device type to the child endpoint if it is new const deviceTypes = child.getDeviceTypes(); if (deviceType && !deviceTypes.includes(deviceType)) { this.log.debug(`addDeviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); deviceTypes.push(deviceType); child.setDeviceTypes(deviceTypes); } - + // Log and add all server clusters in the includelist includeServerList.forEach((clusterId) => { this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`); }); + this.addClusterServerFromList(child, includeServerList); + /* if (includeServerList.includes(Identify.Cluster.id)) { child.addClusterServer(this.getDefaultIdentifyClusterServer()); } @@ -1161,6 +1171,7 @@ export class BridgedBaseDevice extends MatterbridgeDevice { if (includeServerList.includes(ElectricalMeasurement.Cluster.id) && !this.hasClusterServer(ElectricalMeasurement.Complete)) { child.addClusterServer(this.getDefaultElectricalMeasurementClusterServer()); } + */ return child; } From aea1b26a4b4f2ab13df4f0cece029e7d046ccff3 Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:15:13 +0200 Subject: [PATCH 08/10] Release 2.0.18 --- src/entity.ts | 175 +++++++++----------------------------------------- 1 file changed, 30 insertions(+), 145 deletions(-) diff --git a/src/entity.ts b/src/entity.ts index e11ea05..2d59b35 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -31,8 +31,6 @@ import { dimmableSwitch, onOffSwitch, Identify, - Groups, - Scenes, OnOff, LevelControl, ColorControl, @@ -52,17 +50,13 @@ import { BridgedDeviceBasicInformation, ThermostatCluster, Thermostat, - TimeSync, Endpoint, AtLeastOne, FixedLabelCluster, SwitchCluster, - ElectricalMeasurement, EveHistory, getClusterNameById, - FlowMeasurement, DoorLockCluster, - logEndpoint, AttributeInitialValues, powerSource, } from 'matterbridge'; @@ -518,7 +512,7 @@ export interface ZigbeeToMatter { type: string; name: string; property: string; - deviceType: DeviceTypeDefinition | undefined; + deviceType: DeviceTypeDefinition; cluster: number; attribute: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -672,12 +666,12 @@ export class ZigbeeDevice extends ZigbeeEntity { if (z2m) { this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${zb}${endpoint}${db} type: ${zb}${type}${db} property: ${zb}${name}${db} => deviceType: ${z2m.deviceType?.name} cluster: ${z2m.cluster} attribute: ${z2m.attribute}`); if (endpoint === '') { - if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [z2m.deviceType ?? DeviceTypes.BRIDGED_DEVICE_WITH_POWERSOURCE_INFO], z2m.deviceType ? [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)] : [ClusterId(z2m.cluster)]); - else this.bridgedDevice.addDeviceTypeAndClusterServer(z2m.deviceType, z2m.deviceType ? [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)] : [ClusterId(z2m.cluster)]); + if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [DeviceTypes.BRIDGED_DEVICE_WITH_POWERSOURCE_INFO], [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]); + else this.bridgedDevice.addDeviceTypeAndClusterServer(z2m.deviceType, [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]); if (type !== '') this.bridgedDevice.addFixedLabel('type', type); } else { if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [DeviceTypes.BRIDGED_DEVICE_WITH_POWERSOURCE_INFO]); - const childEndpoint = this.bridgedDevice.addChildDeviceTypeAndClusterServer(endpoint, z2m.deviceType, z2m.deviceType ? [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)] : [ClusterId(z2m.cluster)]); + const childEndpoint = this.bridgedDevice.addChildDeviceTypeAndClusterServer(endpoint, z2m.deviceType, [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]); if (type !== '') childEndpoint.addFixedLabel('type', type); this.bridgedDevice.addFixedLabel('composed', type); } @@ -712,8 +706,7 @@ export class ZigbeeDevice extends ZigbeeEntity { }); this.log.setLogDebug(debugEnabled); - /* Verify that all required server clusters are present */ - // TODO: check also endpoints + /* Verify that all required server clusters are present in the main endpoint and in the child endpoints */ if (this.bridgedDevice) { const deviceTypes = this.bridgedDevice.getDeviceTypes(); deviceTypes.forEach((deviceType) => { @@ -726,6 +719,21 @@ export class ZigbeeDevice extends ZigbeeEntity { }); }); } + if (this.bridgedDevice) { + const childEndpoints = this.bridgedDevice.getChildEndpoints(); + childEndpoints.forEach((childEndpoint) => { + const deviceTypes = childEndpoint.getDeviceTypes(); + deviceTypes.forEach((deviceType) => { + deviceType.requiredServerClusters.forEach((clusterId) => { + if (!this.bridgedDevice) return; + if (!childEndpoint.getClusterServerById(clusterId)) { + this.log.error(`Device type ${deviceType.name} (0x${deviceType.code.toString(16)}) requires cluster server ${getClusterNameById(clusterId)}(0x${clusterId.toString(16)}) but it is not present on child endpoint`); + this.bridgedDevice = undefined; + } + }); + }); + }); + } if (!this.bridgedDevice) return; // Command handlers @@ -981,74 +989,6 @@ export class BridgedBaseDevice extends MatterbridgeDevice { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars protected addDeviceClusterServer(includeServerList: ClusterId[] = [], attributeInitialValues?: Record>) { this.addClusterServerFromList(this, includeServerList); - /* - if (includeServerList.includes(Identify.Cluster.id) && !this.hasClusterServer(Identify.Complete)) { - this.createDefaultIdentifyClusterServer(); - } - if (includeServerList.includes(Groups.Cluster.id) && !this.hasClusterServer(Groups.Complete)) { - this.createDefaultGroupsClusterServer(); - } - if (includeServerList.includes(Scenes.Cluster.id) && !this.hasClusterServer(Scenes.Complete)) { - this.createDefaultScenesClusterServer(); - } - if (includeServerList.includes(OnOff.Cluster.id) && !this.hasClusterServer(OnOff.Complete)) { - this.createDefaultOnOffClusterServer(); - } - if (includeServerList.includes(LevelControl.Cluster.id) && !this.hasClusterServer(LevelControl.Complete)) { - this.createDefaultLevelControlClusterServer(); - } - if (includeServerList.includes(ColorControl.Cluster.id) && !this.hasClusterServer(ColorControl.Complete)) { - this.createDefaultColorControlClusterServer(); - } - if (includeServerList.includes(WindowCovering.Cluster.id) && !this.hasClusterServer(WindowCovering.Complete)) { - this.createDefaultWindowCoveringClusterServer(); - } - if (includeServerList.includes(Switch.Cluster.id) && !this.hasClusterServer(Switch.Complete)) { - this.createDefaultSwitchClusterServer(); - } - if (includeServerList.includes(ElectricalMeasurement.Cluster.id) && !this.hasClusterServer(ElectricalMeasurement.Complete)) { - this.createDefaultElectricalMeasurementClusterServer(); - } - if (includeServerList.includes(EveHistory.Cluster.id) && !this.hasClusterServer(EveHistory.Complete)) { - this.addClusterServer(this.getDefaultStaticEveHistoryClusterServer()); - } - if (includeServerList.includes(TemperatureMeasurement.Cluster.id) && !this.hasClusterServer(TemperatureMeasurement.Complete)) { - this.createDefaultTemperatureMeasurementClusterServer(); - } - if (includeServerList.includes(RelativeHumidityMeasurement.Cluster.id) && !this.hasClusterServer(RelativeHumidityMeasurement.Complete)) { - this.createDefaultRelativeHumidityMeasurementClusterServer(); - } - if (includeServerList.includes(PressureMeasurement.Cluster.id) && !this.hasClusterServer(PressureMeasurement.Complete)) { - this.createDefaultPressureMeasurementClusterServer(); - } - if (includeServerList.includes(FlowMeasurement.Cluster.id) && !this.hasClusterServer(FlowMeasurement.Complete)) { - this.createDefaultFlowMeasurementClusterServer(); - } - if (includeServerList.includes(BooleanState.Cluster.id) && !this.hasClusterServer(BooleanState.Complete)) { - this.createDefaultBooleanStateClusterServer(true); - } - if (includeServerList.includes(OccupancySensing.Cluster.id) && !this.hasClusterServer(OccupancySensing.Complete)) { - this.createDefaultOccupancySensingClusterServer(false); - } - if (includeServerList.includes(IlluminanceMeasurement.Cluster.id) && !this.hasClusterServer(IlluminanceMeasurement.Complete)) { - this.createDefaultIlluminanceMeasurementClusterServer(); - } - if (includeServerList.includes(AirQuality.Cluster.id) && !this.hasClusterServer(AirQuality.Complete)) { - this.createDefaultAirQualityClusterServer(); - } - if (includeServerList.includes(TvocMeasurement.Cluster.id) && !this.hasClusterServer(TvocMeasurement.Complete)) { - this.createDefaultTvocMeasurementClusterServer(); - } - if (includeServerList.includes(DoorLock.Cluster.id) && !this.hasClusterServer(DoorLock.Complete)) { - this.createDefaultDoorLockClusterServer(); - } - if (includeServerList.includes(Thermostat.Cluster.id) && !this.hasClusterServer(Thermostat.Complete)) { - this.createDefaultThermostatClusterServer(); - } - if (includeServerList.includes(TimeSync.Cluster.id) && !this.hasClusterServer(TimeSync.Complete)) { - this.createDefaultTimeSyncClusterServer(); - } - */ } /** @@ -1063,13 +1003,11 @@ export class BridgedBaseDevice extends MatterbridgeDevice { /* Not implemented since not supported by matter.js */ } - public addDeviceTypeAndClusterServer(deviceType: DeviceTypeDefinition | undefined, serverList: ClusterId[]) { + public addDeviceTypeAndClusterServer(deviceType: DeviceTypeDefinition, serverList: ClusterId[]) { this.log.debug(`addDeviceTypeAndClusterServer`); - if (deviceType) { - // Log and add all device type definitions - this.log.debug(`- with deviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); - this.addDeviceType(deviceType); - } + // Log and add all device type definitions + this.log.debug(`- with deviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); + this.addDeviceType(deviceType); // Log and add all server clusters in the serverList serverList.forEach((clusterId) => { this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`); @@ -1077,7 +1015,7 @@ export class BridgedBaseDevice extends MatterbridgeDevice { this.addDeviceClusterServer(serverList); } - public addChildDeviceTypeAndClusterServer(endpointName: string, deviceType: DeviceTypeDefinition | undefined, includeServerList: ClusterId[]) { + public addChildDeviceTypeAndClusterServer(endpointName: string, deviceType: DeviceTypeDefinition, includeServerList: ClusterId[]) { this.hasEndpoints = true; // Look for existing child endpoint @@ -1100,19 +1038,16 @@ export class BridgedBaseDevice extends MatterbridgeDevice { // Not found? Create a new one if (!child) { this.log.debug(`addChildDeviceTypeAndClusterServer: Child endpoint created: ${zb}${endpointName}${db}`); - child = new Endpoint([deviceType ?? DeviceTypes.ON_OFF_PLUGIN_UNIT], { uniqueStorageKey: endpointName }); - if (!deviceType) includeServerList.push(Identify.Cluster.id); - if (!deviceType) includeServerList.push(Scenes.Cluster.id); - if (!deviceType) includeServerList.push(Groups.Cluster.id); - if (!deviceType) includeServerList.push(OnOff.Cluster.id); + this.log.debug(`- with deviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); + child = new Endpoint([deviceType], { uniqueStorageKey: endpointName }); child.addFixedLabel('endpointName', endpointName); this.addChildEndpoint(child); } - // Log and add device type to the child endpoint if it is new + // Log and add device type to the child endpoint const deviceTypes = child.getDeviceTypes(); - if (deviceType && !deviceTypes.includes(deviceType)) { - this.log.debug(`addDeviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); + if (!deviceTypes.includes(deviceType)) { + this.log.debug(`addChildDeviceTypeAndClusterServer: addDeviceType: ${hk}${deviceType.code}${db}-${hk}${deviceType.name}${db}`); deviceTypes.push(deviceType); child.setDeviceTypes(deviceTypes); } @@ -1122,56 +1057,6 @@ export class BridgedBaseDevice extends MatterbridgeDevice { }); this.addClusterServerFromList(child, includeServerList); - /* - if (includeServerList.includes(Identify.Cluster.id)) { - child.addClusterServer(this.getDefaultIdentifyClusterServer()); - } - if (includeServerList.includes(Groups.Cluster.id)) { - child.addClusterServer(this.getDefaultGroupsClusterServer()); - } - if (includeServerList.includes(Scenes.Cluster.id)) { - child.addClusterServer(this.getDefaultScenesClusterServer()); - } - if (includeServerList.includes(OnOff.Cluster.id)) { - child.addClusterServer(this.getDefaultOnOffClusterServer()); - } - if (includeServerList.includes(LevelControl.Cluster.id)) { - child.addClusterServer(this.getDefaultLevelControlClusterServer()); - } - if (includeServerList.includes(ColorControl.Cluster.id)) { - child.addClusterServer(this.getDefaultColorControlClusterServer()); - } - if (includeServerList.includes(Switch.Cluster.id)) { - child.addClusterServer(this.getDefaultSwitchClusterServer()); - } - if (includeServerList.includes(TemperatureMeasurement.Cluster.id)) { - child.addClusterServer(this.getDefaultTemperatureMeasurementClusterServer()); - } - if (includeServerList.includes(RelativeHumidityMeasurement.Cluster.id)) { - child.addClusterServer(this.getDefaultRelativeHumidityMeasurementClusterServer()); - } - if (includeServerList.includes(PressureMeasurement.Cluster.id)) { - child.addClusterServer(this.getDefaultPressureMeasurementClusterServer()); - } - if (includeServerList.includes(FlowMeasurement.Cluster.id)) { - child.addClusterServer(this.getDefaultFlowMeasurementClusterServer()); - } - if (includeServerList.includes(BooleanState.Cluster.id)) { - child.addClusterServer(this.getDefaultBooleanStateClusterServer()); - } - if (includeServerList.includes(OccupancySensing.Cluster.id)) { - child.addClusterServer(this.getDefaultOccupancySensingClusterServer()); - } - if (includeServerList.includes(IlluminanceMeasurement.Cluster.id)) { - child.addClusterServer(this.getDefaultIlluminanceMeasurementClusterServer()); - } - if (includeServerList.includes(EveHistory.Cluster.id) && !this.hasClusterServer(EveHistory.Complete)) { - child.addClusterServer(this.getDefaultStaticEveHistoryClusterServer()); - } - if (includeServerList.includes(ElectricalMeasurement.Cluster.id) && !this.hasClusterServer(ElectricalMeasurement.Complete)) { - child.addClusterServer(this.getDefaultElectricalMeasurementClusterServer()); - } - */ return child; } From bfe112b14c7f494586af99160719a0c86dacecdd Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:46:34 +0200 Subject: [PATCH 09/10] Release 2.1.0 --- CHANGELOG.md | 3 ++- matterbridge-zigbee2mqtt.schema.json | 8 ++---- package.json | 2 +- src/entity.ts | 39 +--------------------------- src/platform.ts | 6 ++--- 5 files changed, 9 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ca4a5..662a501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,11 @@ All notable changes to this project will be documented in this file. - [dependencies]: Update dependencies - [schema]: Added schema to the root directory of the plugin -- [properties]: Added soil_moisture properties as humidity sensor +- [properties]: Added soil_moisture property as humidity sensor - [properties]: Added transition if the zigbee device supports it and the controller sends it. You can disable this globally adding transition to the featureBlackList or only for the single device adding transition to the deviceFeatureBlackList. (Thanks Stefan Schweiger) ### Fixed + - [schema]: Username and password are no more required fields (Thanks Stefan Schweiger) - [LevelControl]: Fixed the commandHandler for LevelControl in child endpoint (Thanks jpadie). diff --git a/matterbridge-zigbee2mqtt.schema.json b/matterbridge-zigbee2mqtt.schema.json index 1c5d718..c736af3 100644 --- a/matterbridge-zigbee2mqtt.schema.json +++ b/matterbridge-zigbee2mqtt.schema.json @@ -2,11 +2,7 @@ "title": "Matterbridge zigbee2mqtt plugin", "description": "matterbridge-zigbee2mqtt v. 2.0.17 by https://github.com/Luligu", "type": "object", - "required": [ - "host", - "port", - "topic" - ], + "required": ["host", "port", "topic"], "properties": { "name": { "description": "Plugin name", @@ -99,4 +95,4 @@ "type": "boolean" } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 2eb2a8a..e32e9d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-zigbee2mqtt", - "version": "2.0.18", + "version": "2.1.0", "description": "Matterbridge zigbee2mqtt plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", diff --git a/src/entity.ts b/src/entity.ts index 2d59b35..01e493a 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -4,7 +4,7 @@ * @file entity.ts * @author Luca Liguori * @date 2023-12-29 - * @version 2.0.4 + * @version 2.1.0 * * Copyright 2023, 2024 Luca Liguori. * @@ -261,43 +261,6 @@ export class ZigbeeEntity extends EventEmitter { this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentSaturation', Math.round((hsl.s / 100) * 254)); this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation); } - /* Switch */ - /* - if (key === 'action') { - let position = undefined; - if (value === 'single') { - position = 1; - const cluster = this.bridgedDevice.getClusterServer(SwitchCluster.with(Switch.Feature.MomentarySwitch, Switch.Feature.MomentarySwitchRelease, Switch.Feature.MomentarySwitchLongPress, Switch.Feature.MomentarySwitchMultiPress)); - cluster?.setCurrentPositionAttribute(1); - cluster?.triggerInitialPressEvent({ newPosition: 1 }); - cluster?.setCurrentPositionAttribute(0); - cluster?.triggerShortReleaseEvent({ previousPosition: 1 }); - cluster?.setCurrentPositionAttribute(0); - cluster?.triggerMultiPressCompleteEvent({ previousPosition: 1, totalNumberOfPressesCounted: 1 }); - this.log.info(`Trigger 'single' event for ${this.ien}${this.entityName}${rs}`); - } - if (value === 'double') { - position = 2; - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.setCurrentPositionAttribute(position); - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.triggerMultiPressCompleteEvent({ previousPosition: 1, totalNumberOfPressesCounted: 2 }); - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.setCurrentPositionAttribute(0); - this.log.info(`Trigger 'double' event for ${this.ien}${this.entityName}${rs}`); - } - if (value === 'hold') { - position = 1; - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.setCurrentPositionAttribute(position); - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.triggerInitialPressEvent({ newPosition: 1 }); - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.triggerLongPressEvent({ newPosition: 1 }); - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.triggerLongReleaseEvent({ previousPosition: 1 }); - this.bridgedDevice.getClusterServerById(Switch.Cluster.id)?.setCurrentPositionAttribute(0); - this.log.info(`Trigger 'hold' event for ${this.ien}${this.entityName}${rs}`); - } - if (value === 'release') { - // this.bridgedDevice?.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: true }); - } - - } - */ }); this.log.setLogDebug(debugEnabled); }); diff --git a/src/platform.ts b/src/platform.ts index 2d2e661..c2303ca 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -21,7 +21,7 @@ * limitations under the License. * */ -import { BridgedDeviceBasicInformationCluster, DoorLock, DoorLockCluster, Level, Logger, Matterbridge, MatterbridgeDevice, MatterbridgeDynamicPlatform, PlatformConfig, waiter } from 'matterbridge'; +import { BridgedDeviceBasicInformationCluster, DoorLock, DoorLockCluster, Matterbridge, MatterbridgeDevice, MatterbridgeDynamicPlatform, PlatformConfig, waiter } from 'matterbridge'; import { AnsiLogger, dn, gn, db, wr, zb, payloadStringify, rs, debugStringify } from 'node-ansi-logger'; import { ZigbeeDevice, ZigbeeEntity, ZigbeeGroup, BridgedBaseDevice } from './entity.js'; @@ -319,7 +319,7 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform { this.log.debug('Setting flag to start when zigbee2mqtt sends devices: ', reason); } - if (this.debugEnabled) Logger.defaultLogLevel = Level.INFO; + // if (this.debugEnabled) Logger.defaultLogLevel = Level.INFO; if (!this.z2mDevicesRegistered && this.z2mBridgeDevices) { for (const device of this.z2mBridgeDevices) { @@ -335,7 +335,7 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform { this.z2mGroupsRegistered = true; } - if (this.debugEnabled) Logger.defaultLogLevel = Level.DEBUG; + // if (this.debugEnabled) Logger.defaultLogLevel = Level.DEBUG; } override async onConfigure() { From 621aeec941714821f9c56ee8bf2bad06933673a7 Mon Sep 17 00:00:00 2001 From: Luligu <132135057+Luligu@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:53:11 +0200 Subject: [PATCH 10/10] Release 2.1.0 --- CHANGELOG.md | 17 +++++++++++------ package-lock.json | 20 ++++++++++---------- package.json | 4 ++-- src/entity.ts | 26 +++++++++++++++----------- src/platform.ts | 37 +++++++++++++++++++------------------ src/zigbee2mqtt.ts | 6 +++++- 6 files changed, 62 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 662a501..a9136b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,24 @@ All notable changes to this project will be documented in this file. -## [2.0.18] - 2024-06-17 +## [2.1.0] - 2024-06-19 ### Added -- [dependencies]: Update dependencies -- [schema]: Added schema to the root directory of the plugin -- [properties]: Added soil_moisture property as humidity sensor -- [properties]: Added transition if the zigbee device supports it and the controller sends it. You can disable this globally adding transition to the featureBlackList or only for the single device adding transition to the deviceFeatureBlackList. (Thanks Stefan Schweiger) +- [dependencies]: Update dependencies. +- [schema]: Added schema to the root directory of the plugin. +- [z2m]: Added soil_moisture property as humidity sensor. +- [z2m]: Added transition if the zigbee device supports it and the controller sends it. You can disable this globally adding transition to the featureBlackList or only for the single device adding transition to the deviceFeatureBlackList. (Thanks Stefan Schweiger). + +### Changed + +- [matter]: Removed PowerSourceConfiguration cluster that is deprecated in Matter 1.3. ### Fixed -- [schema]: Username and password are no more required fields (Thanks Stefan Schweiger) +- [schema]: Username and password are no more required fields (Thanks Stefan Schweiger). - [LevelControl]: Fixed the commandHandler for LevelControl in child endpoint (Thanks jpadie). +- [availability]: Fixed the issue that caused the availability event sent before the start to be ignored. Buy me a coffee diff --git a/package-lock.json b/package-lock.json index 136bc67..4a5528a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,24 @@ { "name": "matterbridge-zigbee2mqtt", - "version": "2.0.17", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-zigbee2mqtt", - "version": "2.0.17", + "version": "2.1.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "moment": "^2.30.1", - "mqtt": "^5.7.0", + "mqtt": "^5.7.1", "node-ansi-logger": "^1.9.5" }, "devDependencies": { "@eslint/js": "^9.5.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.12", - "@types/node": "^20.14.3", + "@types/node": "^20.14.5", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-prettier": "^5.1.3", @@ -1617,9 +1617,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.3.tgz", - "integrity": "sha512-Nuzqa6WAxeGnve6SXqiPAM9rA++VQs+iLZ1DDd56y0gdvygSZlQvZuvdFPR3yLqkVxPu4WrO02iDEyH1g+wazw==", + "version": "20.14.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.5.tgz", + "integrity": "sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -4567,9 +4567,9 @@ } }, "node_modules/mqtt": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.7.0.tgz", - "integrity": "sha512-/o0CBYSjZzddmQDV2iglCafsA0xWKpqnS62tGbOLOliubBxszpXO1DAQPyfI7ZcPDG0b9ni7QITn+5FW1E2UTg==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.7.1.tgz", + "integrity": "sha512-6Gf5NH9/WxwGKkkXTyTI1lsM7S3s3zqSJe61Qp5w3B45BAOkOjM8p1GwNvgLE28+WO4d9OwrJzV8DWOMtWfy6w==", "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.5", diff --git a/package.json b/package.json index e32e9d1..4dbc4a9 100644 --- a/package.json +++ b/package.json @@ -85,14 +85,14 @@ }, "dependencies": { "moment": "^2.30.1", - "mqtt": "^5.7.0", + "mqtt": "^5.7.1", "node-ansi-logger": "^1.9.5" }, "devDependencies": { "@eslint/js": "^9.5.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.12", - "@types/node": "^20.14.3", + "@types/node": "^20.14.5", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-prettier": "^5.1.3", diff --git a/src/entity.ts b/src/entity.ts index 01e493a..8ebdb66 100644 --- a/src/entity.ts +++ b/src/entity.ts @@ -59,6 +59,7 @@ import { DoorLockCluster, AttributeInitialValues, powerSource, + bridgedNode, } from 'matterbridge'; import { AnsiLogger, TimestampFormat, gn, dn, ign, idn, rs, db, wr, debugStringify, hk, zb, or, nf } from 'node-ansi-logger'; @@ -393,7 +394,7 @@ export class ZigbeeGroup extends ZigbeeEntity { maxColorTemperature = Math.max(maxColorTemperature, feature.value_max); } }); - this.log.info(`Group: ${gn}${group.friendly_name}${rs} state: ${useState} brightness: ${useBrightness} color: ${useColor} color_temp: ${useColorTemperature}-${minColorTemperature}-${maxColorTemperature}`); + this.log.debug(`Group: ${gn}${group.friendly_name}${rs}${db} state: ${useState} brightness: ${useBrightness} color: ${useColor} color_temp: ${useColorTemperature}-${minColorTemperature}-${maxColorTemperature}`); let deviceType = DeviceTypes.ON_OFF_LIGHT; if (useBrightness) deviceType = DeviceTypes.ON_OFF_LIGHT; if (useColorTemperature || useColor) deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; @@ -541,7 +542,7 @@ export class ZigbeeDevice extends ZigbeeEntity { this.bridgedDevice.isRouter = true; } - const debugEnabled = this.platform.debugEnabled; + // const debugEnabled = this.platform.debugEnabled; // this.log.setLogDebug(true); // Get types and properties @@ -629,11 +630,11 @@ export class ZigbeeDevice extends ZigbeeEntity { if (z2m) { this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${zb}${endpoint}${db} type: ${zb}${type}${db} property: ${zb}${name}${db} => deviceType: ${z2m.deviceType?.name} cluster: ${z2m.cluster} attribute: ${z2m.attribute}`); if (endpoint === '') { - if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [DeviceTypes.BRIDGED_DEVICE_WITH_POWERSOURCE_INFO], [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]); + if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [z2m.deviceType], [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]); else this.bridgedDevice.addDeviceTypeAndClusterServer(z2m.deviceType, [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]); if (type !== '') this.bridgedDevice.addFixedLabel('type', type); } else { - if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [DeviceTypes.BRIDGED_DEVICE_WITH_POWERSOURCE_INFO]); + if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [bridgedNode]); const childEndpoint = this.bridgedDevice.addChildDeviceTypeAndClusterServer(endpoint, z2m.deviceType, [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]); if (type !== '') childEndpoint.addFixedLabel('type', type); this.bridgedDevice.addFixedLabel('composed', type); @@ -642,7 +643,7 @@ export class ZigbeeDevice extends ZigbeeEntity { if (name === 'action' && this.actions.length) { this.log.info(`Device ${this.ien}${device.friendly_name}${rs}${nf} has actions mapped to these switches on sub endpoints:`); this.log.info(' controller events <=> zigbee2mqtt actions'); - if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [DeviceTypes.BRIDGED_DEVICE_WITH_POWERSOURCE_INFO], []); + if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, [bridgedNode]); // Mapping actions const switchMap = ['Single Press', 'Double Press', 'Long Press ']; let count = 1; @@ -667,7 +668,7 @@ export class ZigbeeDevice extends ZigbeeEntity { this.bridgedDevice.addFixedLabel('composed', 'button'); } }); - this.log.setLogDebug(debugEnabled); + // this.log.setLogDebug(debugEnabled); /* Verify that all required server clusters are present in the main endpoint and in the child endpoints */ if (this.bridgedDevice) { @@ -701,7 +702,7 @@ export class ZigbeeDevice extends ZigbeeEntity { // Command handlers this.bridgedDevice.addCommandHandler('identify', async (data) => { - this.log.warn(`Command identify called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} identifyTime:${data.attributes.identifyTime.getLocal()} identifyType:${data.attributes.identifyType.getLocal()} `); + this.log.debug(`Command identify called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} identifyTime:${data.attributes.identifyTime.getLocal()} identifyType:${data.attributes.identifyType.getLocal()} `); // logEndpoint(this.bridgedDevice!); }); if (this.bridgedDevice.hasClusterServer(OnOff.Complete) || this.bridgedDevice.hasEndpoints) { @@ -906,8 +907,8 @@ export class BridgedBaseDevice extends MatterbridgeDevice { }); this.addDeviceClusterServer(includeServerList); - // Add BridgedDevice with PowerSourceInformation device type - this.addDeviceType(DeviceTypes.BRIDGED_DEVICE_WITH_POWERSOURCE_INFO); + // Add BridgedDevice device type + this.addDeviceType(bridgedNode); // Add BridgedDeviceBasicInformation cluster if (entity.isDevice && entity.device && entity.device.friendly_name === 'Coordinator') { @@ -918,8 +919,11 @@ export class BridgedBaseDevice extends MatterbridgeDevice { this.addBridgedDeviceBasicInformationCluster(entity.group.friendly_name, 'zigbee2MQTT', 'Group', `group-${entity.group.id}`); } + // Add BridgedDevice device type + this.addDeviceType(powerSource); + // Add PowerSource cluster - this.createDefaultPowerSourceConfigurationClusterServer(); // TODO remove this cause is deprecated in Matter 1.3 + // this.createDefaultPowerSourceConfigurationClusterServer(); // TODO remove this cause is deprecated in Matter 1.3 if (entity.isDevice) { if (entity.device?.power_source === 'Battery') this.createDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok); else this.createDefaultPowerSourceWiredClusterServer(); @@ -927,7 +931,7 @@ export class BridgedBaseDevice extends MatterbridgeDevice { this.createDefaultPowerSourceWiredClusterServer(); } - // Add all other client clusters in the includelist + // Add all other client clusters in the includelist (not supported by matter.js) this.addDeviceClusterClient(includeClientList); } diff --git a/src/platform.ts b/src/platform.ts index c2303ca..7846b04 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -4,7 +4,7 @@ * @file platform.ts * @author Luca Liguori * @date 2023-12-29 - * @version 2.0.4 + * @version 2.1.1 * * Copyright 2023, 2024 Luca Liguori. * @@ -67,6 +67,8 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform { private z2mBridgeInfo: BridgeInfo | undefined; private z2mBridgeDevices: BridgeDevice[] | undefined; private z2mBridgeGroups: BridgeGroup[] | undefined; + private z2mDeviceAvailability = new Map(); + private availabilityTimer: NodeJS.Timeout | undefined; constructor(matterbridge: Matterbridge, log: AnsiLogger, config: PlatformConfig) { super(matterbridge, log, config); @@ -143,6 +145,12 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform { this.updateAvailability(false); }); + this.z2m.on('availability', (device: string, available: boolean) => { + this.z2mDeviceAvailability.set(device, available); + if (available) this.log.info(`zigbee2MQTT device ${device} is ${available ? 'online' : 'offline'}`); + else this.log.warn(`zigbee2MQTT device ${device} is ${available ? 'online' : 'offline'}`); + }); + this.z2m.on('permit_join', async (device: string, time: number, status: boolean) => { this.log.info(`zigbee2MQTT sent permit_join device: ${device} time: ${time} status: ${status}`); for (const zigbeeEntity of this.zigbeeEntities) { @@ -366,6 +374,13 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform { device.configure(); } + this.availabilityTimer = setTimeout(() => { + for (const [device, available] of this.z2mDeviceAvailability) { + if (available) this.z2m.emit('ONLINE-' + device); + else this.z2m.emit('OFFLINE-' + device); + } + }, 60 * 1000); + if (this.config.injectPayloads) { this.injectTimer = setInterval(() => { const data = this.z2m.readConfig(path.join(this.matterbridge.matterbridgeDirectory, this.config.injectPayloads as string)); @@ -380,6 +395,9 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform { override async onShutdown(reason?: string) { this.log.debug('Shutting down zigbee2mqtt platform: ' + reason); if (this.injectTimer) clearInterval(this.injectTimer); + this.injectTimer = undefined; + if (this.availabilityTimer) clearInterval(this.availabilityTimer); + this.availabilityTimer = undefined; // this.updateAvailability(false); if (this.config.unregisterOnShutdown === true) await this.unregisterAllDevices(); this.z2m.stop(); @@ -482,23 +500,6 @@ export class ZigbeePlatform extends MatterbridgeDynamicPlatform { return matterGroup; } - /* - public async unregisterAll() { - this.log.warn(`Unregistering ${this.bridgedEntities.length} accessories`); - - for (const bridgedDevice of this.bridgedDevices) { - this.log.warn(`- ${bridgedDevice.deviceName} ${bridgedDevice.id} (${bridgedDevice.name})`); - this.matterAggregator?.removeBridgedDevice(bridgedDevice); - } - - this.bridgedDevices.splice(0); - for (const bridgedEntity of this.bridgedEntities) { - this.log.warn(`- ${bridgedEntity.bridgedDevice?.deviceName} ${bridgedEntity.bridgedDevice?.id} (${bridgedEntity.bridgedDevice?.name})`); - await this.unregisterDevice(bridgedEntity.bridgedDevice as unknown as MatterbridgeDevice); - } - this.bridgedEntities.splice(0); - } - */ private async unregisterZigbeeEntity(friendly_name: string) { /* for (const zigbeeEntity of this.zigbeeEntities) { diff --git a/src/zigbee2mqtt.ts b/src/zigbee2mqtt.ts index 017beb1..efe066a 100644 --- a/src/zigbee2mqtt.ts +++ b/src/zigbee2mqtt.ts @@ -4,7 +4,7 @@ * @file zigbee2mqtt.ts * @author Luca Liguori * @date 2023-06-30 - * @version 2.2.27 + * @version 2.2.28 * * Copyright 2023, 2024 Luca Liguori. * @@ -866,10 +866,12 @@ export class Zigbee2MQTT extends EventEmitter { this.z2mDevices[deviceIndex].isAvailabilityEnabled = true; this.z2mDevices[deviceIndex].isOnline = true; // this.log.warn('handleDeviceMessage availability payload: ', data); + this.emit('availability', entity, true); this.emit('ONLINE-' + entity); } else if (data.state === 'offline') { this.z2mDevices[deviceIndex].isOnline = false; // this.log.warn('handleDeviceMessage availability payload: ', data); + this.emit('availability', entity, false); this.emit('OFFLINE-' + entity); } } else if (service === 'get') { @@ -902,9 +904,11 @@ export class Zigbee2MQTT extends EventEmitter { if (data.state === 'online') { this.z2mGroups[groupIndex].isAvailabilityEnabled = true; this.z2mGroups[groupIndex].isOnline = true; + this.emit('availability', entity, true); this.emit('ONLINE-' + entity); } else if (data.state === 'offline') { this.z2mGroups[groupIndex].isOnline = false; + this.emit('availability', entity, false); this.emit('OFFLINE-' + entity); } } else if (service === 'get') {