diff --git a/CHANGELOG.md b/CHANGELOG.md index f1bd9754..9fa74450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,21 @@ The Home Assistant Community Add-ons and plugins are not verified to work with M If you want to run Matterbridge in Home Assistant please use the official add-on https://github.com/Luligu/matterbridge-home-assistant-addon that also has Ingress and side panel. -### New Apple firmware v. 18.x - -Please read this: https://github.com/Luligu/matterbridge/discussions/135 - ### Discord Tamer (https://github.com/tammeryousef1006) has created the Matterbridge Discord group: https://discord.gg/QX58CDe6hd. -Feel free to join (the link is now permanent)! + +## [1.6.3] - 2024-11-27 + +### Changed + +- [matterbridge]: Changed default minLevel to 0 in LevelControlCluster utility methods. + +### Fixed + +- [matter.js]: Temporary fix the crash of matter.js on close when using command line parameters. +- [matter.js]: Update to matter.js 0.11.6. ## [1.6.2] - 2024-11-25 @@ -30,6 +36,7 @@ Feel free to join (the link is now permanent)! - [config]: Added version to the config. - [frontend]: Added badge "edge" when running in edge mode. - [matterbridge]: Added addTagList method. +- [matterbridge]: Added minLevel, maxLevel and onLevel to LevelControlCluster utility methods. ### Changed @@ -44,7 +51,6 @@ Feel free to join (the link is now permanent)! - [frontend]: Update QRCode package and QRCode level to M. - [frontend]: Added font roboto. - [matterbridge]: Removed BasicInformationCluster from Aggregator. -- [matterbridge]: Added minLevel, maxLevel and onLevel to LevelControlCluster utility methods. ### Fixed diff --git a/README.md b/README.md index f017137d..55b85eb4 100644 --- a/README.md +++ b/README.md @@ -391,6 +391,7 @@ If you have more then one Apple TV or Home Pod, you can herve better results set So far is the only controller supporting some Matter 1.2 and 1.3 device type: - airQualitySensor code 0x002c (Matter 1.2) +- smokesmokeCoAlarm code 0x0076 (Matter 1.2) - waterFreezeDetector code 0x0041 (Matter 1.3 with only BooleanState cluster) - waterLeakDetector code 0x0043 (Matter 1.3 with only BooleanState cluster) - rainSensor code 0x0044 (Matter 1.3 with only BooleanState cluster) @@ -399,11 +400,11 @@ So far is the only controller supporting some Matter 1.2 and 1.3 device type: Electrical measurements: - electrical measurements from EveHistoryCluster (used in Matterbridge plugins) -- electricalSensor code 0x0510 with clusters: ElectricalPowerMeasurement and ElectricalEnergyMeasurement (still in dev but fully working!) +- electricalSensor code 0x0510 with clusters: ElectricalPowerMeasurement and ElectricalEnergyMeasurement Other supported cluster: -- ModeSelect +- modeSelect code 0x27 with ModeSelect cluster ## Home Assistant issues (Matter Server for HA is still in Beta) diff --git a/package-lock.json b/package-lock.json index 0baa5370..8644e7b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "matterbridge", - "version": "1.6.2", + "version": "1.6.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge", - "version": "1.6.2", + "version": "1.6.3", "license": "Apache-2.0", "dependencies": { - "@matter/main": "^0.11.5", - "@matter/nodejs": "^0.11.5", - "@project-chip/matter.js": "^0.11.5", + "@matter/main": "^0.11.6", + "@matter/nodejs": "^0.11.6", + "@project-chip/matter.js": "^0.11.6", "archiver": "7.0.1", "express": "4.21.1", "glob": "11.0.0", @@ -29,17 +29,17 @@ "@types/eslint__js": "8.42.3", "@types/express": "5.0.0", "@types/jest": "29.5.14", - "@types/node": "22.9.3", + "@types/node": "22.10.0", "@types/ws": "8.5.13", "eslint": "9.15.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-jest": "28.9.0", "eslint-plugin-prettier": "5.2.1", "jest": "29.7.0", - "prettier": "3.3.3", + "prettier": "3.4.1", "ts-jest": "29.2.5", "typescript": "5.7.2", - "typescript-eslint": "8.15.0" + "typescript-eslint": "8.16.0" }, "engines": { "node": ">=18.0.0" @@ -1317,64 +1317,64 @@ } }, "node_modules/@matter/general": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.11.5.tgz", - "integrity": "sha512-uYzFDOGpjNtZEPWZDW5NStmXxeuIDQ9t+c4sPp1HwkX4jC3ZP5p96Fqse5Lby0QfBbzO0HVKgJbEYjIYzdLc3w==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.11.6.tgz", + "integrity": "sha512-HPFTmaCREFAXPW5JLWwOU5Esbu1fAu2zBnlEbt0+c60vZzoLWbMLxXv0fKz2bSgNuvmly0ccX/1DF4CAlzeDbQ==", "license": "Apache-2.0", "dependencies": { - "@noble/curves": "^1.5.0" + "@noble/curves": "^1.7.0" } }, "node_modules/@matter/main": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.11.5.tgz", - "integrity": "sha512-d1N7jQZbyS+PKltLXyYu6faHk2CqtCoS2AlldArjpZSN1OY0F3We5kGTuUhAZj59wSXAqbX/tYNOrOyOGP13/Q==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.11.6.tgz", + "integrity": "sha512-NJRFAEF9Pgf2X8oMppAGOUEJdMUzi+evzkLpw4X96f2OpiMjfB3Nvef6C9QmYSzhh8hleCW4PV4ablmBIh9Kig==", "license": "Apache-2.0", "dependencies": { - "@matter/general": "0.11.5", - "@matter/model": "0.11.5", - "@matter/node": "0.11.5", - "@matter/protocol": "0.11.5", - "@matter/types": "0.11.5", - "@noble/curves": "^1.5.0" + "@matter/general": "0.11.6", + "@matter/model": "0.11.6", + "@matter/node": "0.11.6", + "@matter/protocol": "0.11.6", + "@matter/types": "0.11.6", + "@noble/curves": "^1.7.0" }, "optionalDependencies": { - "@matter/nodejs": "0.11.5" + "@matter/nodejs": "0.11.6" } }, "node_modules/@matter/model": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.11.5.tgz", - "integrity": "sha512-vBA3Q/X3wT6+1E8xHlQb4jZQW1vmK5u8tJ2e8EuoEmyK3w5VvROPh3KV0OzSqNL0d8QwqPLOljCAWIw78nnJwA==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.11.6.tgz", + "integrity": "sha512-D8w2datMmqoSwUejOlm9BONG4vSnc0+MF0GmicTEq1fUnvu1bOcgergtg0IcbyeIkm1n7ngJAAtBxcG5ENh99g==", "license": "Apache-2.0", "dependencies": { - "@matter/general": "0.11.5", - "@noble/curves": "^1.5.0" + "@matter/general": "0.11.6", + "@noble/curves": "^1.7.0" } }, "node_modules/@matter/node": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.11.5.tgz", - "integrity": "sha512-7kz+nkNSoSZHycTJw4Noaj5tJSBV/jfmYh/IUyboEHPhYhHV0IAWoeKWNo1o/zLuR27ebcE1eJzoHBvnOJ475g==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.11.6.tgz", + "integrity": "sha512-zMNWdEhG9TluKbz2J3yBEwtGPlmLYQQQR7Xg1nr2xzUgLaxSal/GXuyr20VdwMa5RvLcdMeW3zJ+uoVhS1Q9zw==", "license": "Apache-2.0", "dependencies": { - "@matter/general": "0.11.5", - "@matter/model": "0.11.5", - "@matter/protocol": "0.11.5", - "@matter/types": "0.11.5", - "@noble/curves": "^1.5.0" + "@matter/general": "0.11.6", + "@matter/model": "0.11.6", + "@matter/protocol": "0.11.6", + "@matter/types": "0.11.6", + "@noble/curves": "^1.7.0" } }, "node_modules/@matter/nodejs": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.11.5.tgz", - "integrity": "sha512-5zNKPxrhpH7hgGUF56P75Se5q6hmqMn6ysai7EdO4XYC8rsYWvc7c6/uR444bJdmEE087eNvovf3S+2CMcRLsQ==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.11.6.tgz", + "integrity": "sha512-nJCMh34AEigLM3/GvHJJ/V0n4+6i9rpH0n19PcdkbKW4Yxvo5cE3l/Nq5Yh9ajTIUMx5Wh13s1N0nlM62WuOdw==", "license": "Apache-2.0", "dependencies": { - "@matter/general": "0.11.5", - "@matter/node": "0.11.5", - "@matter/protocol": "0.11.5", - "@matter/types": "0.11.5", + "@matter/general": "0.11.6", + "@matter/node": "0.11.6", + "@matter/protocol": "0.11.6", + "@matter/types": "0.11.6", "node-localstorage": "^3.0.5" }, "engines": { @@ -1382,26 +1382,26 @@ } }, "node_modules/@matter/protocol": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.11.5.tgz", - "integrity": "sha512-U6sBOYIizgsyANTyI4ktZLak9BzjQFZppcxVW7IJrU3ub7LbrAzENQqijT17bu0KCijZkzEs+iRL9oso6yZ/Tw==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.11.6.tgz", + "integrity": "sha512-FA0FUBGhXljzNyevS5hmGfRUYQlHetfpX+9RuPs/WD+Xc5ZG3myjLvXkQWiwDdEkR5m1jHzF9uyex5hpUaDstA==", "license": "Apache-2.0", "dependencies": { - "@matter/general": "0.11.5", - "@matter/model": "0.11.5", - "@matter/types": "0.11.5", - "@noble/curves": "^1.5.0" + "@matter/general": "0.11.6", + "@matter/model": "0.11.6", + "@matter/types": "0.11.6", + "@noble/curves": "^1.7.0" } }, "node_modules/@matter/types": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.11.5.tgz", - "integrity": "sha512-e7nFqaQwbLpZMLizYHFE7aqCIE3HvYNpkjkw/wBmTte4mjyjje0rn1EkAyPdBER4Sd5DFxwc4x14mgPsXseBzQ==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.11.6.tgz", + "integrity": "sha512-TKaBh1+OFJ1IKuK2x6FG05/udWaNseH8DVJluF5hYIz17rNkkeCBEu6HjRtZpqFpLxOAOlgzrNGmp8BMK+9hJg==", "license": "Apache-2.0", "dependencies": { - "@matter/general": "0.11.5", - "@matter/model": "0.11.5", - "@noble/curves": "^1.5.0" + "@matter/general": "0.11.6", + "@matter/model": "0.11.6", + "@noble/curves": "^1.7.0" } }, "node_modules/@noble/curves": { @@ -1493,17 +1493,17 @@ } }, "node_modules/@project-chip/matter.js": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.11.5.tgz", - "integrity": "sha512-V6XvQOrqXZhsnS6q3SM8nolzYwrvVIQ5Uwwg4n6+ptuOREoSh03/RhW7EjQbABPzG7Y0/N0iCY6h5kGEuyQFBg==", + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.11.6.tgz", + "integrity": "sha512-546FM6h+lKjYkmaW/luB45HLGKlE07MP03/y1peiwy96TMGTcvhbJIUQE0WCGBJM/zBY2XeyR0OVqgw8OmDKzw==", "license": "Apache-2.0", "dependencies": { - "@matter/general": "0.11.5", - "@matter/model": "0.11.5", - "@matter/node": "0.11.5", - "@matter/protocol": "0.11.5", - "@matter/types": "0.11.5", - "@noble/curves": "^1.5.0" + "@matter/general": "0.11.6", + "@matter/model": "0.11.6", + "@matter/node": "0.11.6", + "@matter/protocol": "0.11.6", + "@matter/types": "0.11.6", + "@noble/curves": "^1.7.0" } }, "node_modules/@sinclair/typebox": { @@ -1733,13 +1733,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz", - "integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==", + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.8" + "undici-types": "~6.20.0" } }, "node_modules/@types/qs": { @@ -1824,17 +1824,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1858,16 +1858,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4" }, "engines": { @@ -1887,14 +1887,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1905,14 +1905,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1933,9 +1933,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", "dev": true, "license": "MIT", "engines": { @@ -1947,14 +1947,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2002,16 +2002,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2030,13 +2030,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.16.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3130,9 +3130,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.64", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz", - "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==", + "version": "1.5.65", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz", + "integrity": "sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==", "dev": true, "license": "ISC" }, @@ -5909,9 +5909,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, "license": "MIT", "bin": { @@ -6828,9 +6828,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.1.tgz", - "integrity": "sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.2.tgz", + "integrity": "sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==", "dev": true, "license": "MIT", "engines": { @@ -6960,15 +6960,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.15.0.tgz", - "integrity": "sha512-wY4FRGl0ZI+ZU4Jo/yjdBu0lVTSML58pu6PgGtJmCufvzfV565pUF6iACQt092uFOd49iLOTX/sEVmHtbSrS+w==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.16.0.tgz", + "integrity": "sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.15.0", - "@typescript-eslint/parser": "8.15.0", - "@typescript-eslint/utils": "8.15.0" + "@typescript-eslint/eslint-plugin": "8.16.0", + "@typescript-eslint/parser": "8.16.0", + "@typescript-eslint/utils": "8.16.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6987,9 +6987,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index c47c0e1f..29e8e171 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge", - "version": "1.6.2", + "version": "1.6.3", "description": "Matterbridge plugin manager for Matter", "author": "https://github.com/Luligu", "license": "Apache-2.0", @@ -121,9 +121,9 @@ "install:jest": "npm install --save-dev jest ts-jest @types/jest eslint-plugin-jest" }, "dependencies": { - "@matter/main": "^0.11.5", - "@matter/nodejs": "^0.11.5", - "@project-chip/matter.js": "^0.11.5", + "@matter/main": "^0.11.6", + "@matter/nodejs": "^0.11.6", + "@project-chip/matter.js": "^0.11.6", "archiver": "7.0.1", "express": "4.21.1", "glob": "11.0.0", @@ -138,16 +138,16 @@ "@types/eslint__js": "8.42.3", "@types/express": "5.0.0", "@types/jest": "29.5.14", - "@types/node": "22.9.3", + "@types/node": "22.10.0", "@types/ws": "8.5.13", "eslint": "9.15.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-jest": "28.9.0", "eslint-plugin-prettier": "5.2.1", "jest": "29.7.0", - "prettier": "3.3.3", + "prettier": "3.4.1", "ts-jest": "29.2.5", "typescript": "5.7.2", - "typescript-eslint": "8.15.0" + "typescript-eslint": "8.16.0" } -} +} \ No newline at end of file diff --git a/src/matterbridgeDevice.ts b/src/matterbridgeDevice.ts index c04b6f8a..08ea3580 100644 --- a/src/matterbridgeDevice.ts +++ b/src/matterbridgeDevice.ts @@ -110,7 +110,7 @@ import { } from '@matter/main/clusters'; import { Specification } from '@matter/main/model'; import { ClusterId, EndpointNumber, extendPublicHandlerMethods, VendorId, AtLeastOne, MakeMandatory } from '@matter/main'; -import { MeasurementType, getClusterNameById } from '@matter/main/types'; +import { MeasurementType, Semtag, getClusterNameById } from '@matter/main/types'; // @project-chip import { Device, DeviceTypeDefinition, Endpoint, EndpointOptions } from '@project-chip/matter.js/device'; @@ -121,7 +121,7 @@ import { AnsiLogger, LogLevel, TimestampFormat, CYAN, YELLOW, db, hk, or, zb, de // Node.js modules import { createHash } from 'crypto'; -import { bridgedNode } from './matterbridgeDeviceTypes.js'; +import { bridgedNode, MatterbridgeEndpointOptions } from './matterbridgeDeviceTypes.js'; interface MatterbridgeDeviceCommands { identify: MakeMandatory['identify']>; @@ -236,6 +236,11 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods, includeServerList: ClusterId[] = [], options: EndpointOptions = {}, debug = false): Endpoint { + addChildDeviceTypeWithClusterServer(endpointName: string, deviceTypes: AtLeastOne, includeServerList: ClusterId[] = [], options: MatterbridgeEndpointOptions = {}, debug = false): Endpoint { this.log.debug(`addChildDeviceTypeWithClusterServer: ${CYAN}${endpointName}${db}`); let child = this.getChildEndpoints().find((endpoint) => endpoint.uniqueStorageKey === endpointName); if (!child) { child = new Endpoint(deviceTypes, { uniqueStorageKey: endpointName }); + if ('tagList' in options) { + for (const tag of options.tagList as Semtag[]) { + this.log.debug(`- with tagList: mfgCode ${CYAN}${tag.mfgCode}${db} namespaceId ${CYAN}${tag.namespaceId}${db} tag ${CYAN}${tag.tag}${db} label ${CYAN}${tag.label}${db}`); + this.addTagList(child, tag.mfgCode, tag.namespaceId, tag.tag, tag.label); + } + } this.addChildEndpoint(child); } deviceTypes.forEach((deviceType) => { @@ -432,18 +443,18 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods} payload - The payload of the event to trigger. + * @param {AnsiLogger} [log] - Optional logger for error and info messages. + * @param {Endpoint} [endpoint] - Optional the child endpoint to retrieve the event from. + */ + triggerEvent(clusterId: ClusterId, event: string, payload: Record, log?: AnsiLogger, endpoint?: Endpoint) { + if (!endpoint) endpoint = this as Endpoint; + if (!endpoint.number) return; + + const clusterServer = endpoint.getClusterServerById(clusterId); + if (!clusterServer) { + this.log.error(`triggerEvent error: Cluster ${clusterId} not found on endpoint ${endpoint.name}:${endpoint.number}`); + return; + } + const capitalizedEventName = event.charAt(0).toUpperCase() + event.slice(1); + if (!clusterServer.isEventSupportedByName(event) && !clusterServer.isEventSupportedByName(capitalizedEventName)) { + this.log.error(`triggerEvent error: Event ${event} not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`); + return; + } + // Find the getter method + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (!(clusterServer as any)[`trigger${capitalizedEventName}Event`]) { + this.log.error(`triggerEvent error: Trigger trigger${capitalizedEventName}Event not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`); + return; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type + const trigger = (clusterServer as any)[`trigger${capitalizedEventName}Event`] as (payload: Record) => {}; + trigger(payload); + log?.info(`${db}Trigger event ${hk}${clusterServer.name}.${capitalizedEventName}${db} on endpoint ${or}${endpoint.name}:${endpoint.number}${db}`); + } + /** * Adds a tag to the tag list of the specified endpoint. * @@ -569,13 +615,13 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods { expect(await device.setAttribute(OnOffCluster.id, 'onOff', true, edge.log)).toBe(true); let state = device.getAttribute(OnOffCluster.id, 'onOff', edge.log); - const result = device.subscribeAttribute( + const result = await device.subscribeAttribute( OnOffCluster.id, 'onOff', (newValue: any, oldValue: any) => { diff --git a/src/matterbridgeEndpoint.ts b/src/matterbridgeEndpoint.ts index 75296d31..c916b36a 100644 --- a/src/matterbridgeEndpoint.ts +++ b/src/matterbridgeEndpoint.ts @@ -43,7 +43,7 @@ import { MatterbridgeThermostatServer, MatterbridgeWindowCoveringServer, } from './matterbridgeBehaviors.js'; -import { bridgedNode } from './matterbridgeDeviceTypes.js'; +import { bridgedNode, MatterbridgeEndpointOptions } from './matterbridgeDeviceTypes.js'; import { deepCopy, isValidNumber, waiter } from './utils/utils.js'; // @matter @@ -138,7 +138,7 @@ import { WindowCovering, WindowCoveringCluster, } from '@matter/main/clusters'; -import { ClusterType, MeasurementType, getClusterNameById, Semtag } from '@matter/main/types'; +import { ClusterType, MeasurementType, getClusterNameById, Semtag, BitSchema, TypeFromPartialBitSchema, Attributes, Commands, Events, Cluster } from '@matter/main/types'; import { Specification, DeviceClassification } from '@matter/main/model'; import { DescriptorServer } from '@matter/node/behaviors/descriptor'; import { IdentifyBehavior, IdentifyServer } from '@matter/node/behaviors/identify'; @@ -266,11 +266,9 @@ export class MatterbridgeEndpoint extends Endpoint { * Represents a MatterbridgeEndpoint. * @constructor * @param {DeviceTypeDefinition | AtLeastOne} definition - The DeviceTypeDefinition(s) of the endpoint. - * @param {EndpointOptions} [options={}] - The options for the device. + * @param {MatterbridgeEndpointOptions} [options={}] - The options for the device. */ - constructor(definition: DeviceTypeDefinition | AtLeastOne, options: EndpointOptions = {}, debug = false) { - const { uniqueStorageKey, endpointId, tagList, colorControlFeatures } = options as { uniqueStorageKey?: string; endpointId?: EndpointNumber; tagList?: Semtag[]; colorControlFeatures?: object }; - + constructor(definition: DeviceTypeDefinition | AtLeastOne, options: MatterbridgeEndpointOptions = {}, debug = false) { // Get the first DeviceTypeDefinition let firstDefinition: DeviceTypeDefinition; if (Array.isArray(definition)) firstDefinition = definition[0]; @@ -292,7 +290,7 @@ export class MatterbridgeEndpoint extends Endpoint { optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(firstDefinition.optionalClientClusters)), }, }, - behaviors: tagList ? SupportedBehaviors(DescriptorServer.with(Descriptor.Feature.TagList)) : {}, + behaviors: options.tagList ? SupportedBehaviors(DescriptorServer.with(Descriptor.Feature.TagList)) : {}, }; const endpointV8 = MutableEndpoint(deviceTypeDefinitionV8); @@ -300,20 +298,24 @@ export class MatterbridgeEndpoint extends Endpoint { // [{ mfgCode: null, namespaceId: 0x07, tag: 1, label: 'Switch1' }] // endpoint = endpoint.enable({features: { tagList: true }}); const optionsV8 = { - // id: uniqueStorageKey, - id: uniqueStorageKey?.replace(/[ .]/g, ''), - // id: uniqueStorageKey?.replace(/[^a-zA-Z0-9_-]/g, ''), - number: endpointId, - descriptor: tagList ? { tagList } : undefined, + id: options.uniqueStorageKey?.replace(/[ .]/g, ''), + number: options.endpointId, + descriptor: options.tagList ? { tagList: options.tagList } : undefined, // eslint-disable-next-line @typescript-eslint/no-explicit-any } as { id?: string; number?: EndpointNumber; descriptor?: Record }; super(endpointV8, optionsV8); - this.uniqueStorageKey = uniqueStorageKey; - this.tagList = tagList; + this.uniqueStorageKey = options.uniqueStorageKey; + this.tagList = options.tagList; + // console.log('MatterbridgeEndpoint.option', options); + // console.log('MatterbridgeEndpoint.endpointV8', endpointV8); + // console.log('MatterbridgeEndpoint.optionsV8', optionsV8); // Update the endpoint this.log = new AnsiLogger({ logName: 'MatterbridgeEndpoint', logTimestampFormat: TimestampFormat.TIME_MILLIS, logLevel: debug === true ? LogLevel.DEBUG : MatterbridgeEndpoint.logLevel }); - this.log.debug(`${YELLOW}new${db} MatterbridgeEndpoint: ${zb}${'0x' + firstDefinition.code.toString(16).padStart(4, '0')}${db}-${zb}${firstDefinition.name}${db} id: ${CYAN}${this.id}${db}`); + this.log.debug( + `${YELLOW}new${db} MatterbridgeEndpoint: ${zb}${'0x' + firstDefinition.code.toString(16).padStart(4, '0')}${db}-${zb}${firstDefinition.name}${db} ` + + `id: ${CYAN}${options.uniqueStorageKey}${db} number: ${CYAN}${options.endpointId}${db} taglist: ${CYAN}${options.tagList ? debugStringify(options.tagList) : 'undefined'}${db}`, + ); this.deviceTypes.set(firstDefinition.code, firstDefinition); // Add the other device types to the descriptor server @@ -363,7 +365,11 @@ export class MatterbridgeEndpoint extends Endpoint { if (clusterId === LevelControl.Cluster.id) return MatterbridgeLevelControlServer; if (clusterId === ColorControl.Cluster.id) return MatterbridgeColorControlServer; if (clusterId === DoorLock.Cluster.id) return MatterbridgeDoorLockServer; - if (clusterId === Thermostat.Cluster.id) return MatterbridgeThermostatServer; + + if (clusterId === Thermostat.Cluster.id && type === 'AutoModeThermostat') return MatterbridgeThermostatServer.with('AutoMode', 'Heating', 'Cooling'); + if (clusterId === Thermostat.Cluster.id && type === 'HeatingThermostat') return MatterbridgeThermostatServer.with('Heating'); + if (clusterId === Thermostat.Cluster.id && type === 'CoolingThermostat') return MatterbridgeThermostatServer.with('Cooling'); + if (clusterId === WindowCovering.Cluster.id) return MatterbridgeWindowCoveringServer; if (clusterId === FanControl.Cluster.id) return MatterbridgeFanControlServer; if (clusterId === Switch.Cluster.id && type === 'MomentarySwitch') return SwitchServer.with('MomentarySwitch', 'MomentarySwitchRelease', 'MomentarySwitchLongPress', 'MomentarySwitchMultiPress'); @@ -376,9 +382,9 @@ export class MatterbridgeEndpoint extends Endpoint { if (clusterId === BooleanStateConfiguration.Cluster.id) return MatterbridgeBooleanStateConfigurationServer; if (clusterId === OccupancySensing.Cluster.id) return OccupancySensingServer; if (clusterId === IlluminanceMeasurement.Cluster.id) return IlluminanceMeasurementServer; - if (clusterId === SmokeCoAlarm.Cluster.id) return SmokeCoAlarmServer.with(SmokeCoAlarm.Feature.SmokeAlarm, SmokeCoAlarm.Feature.CoAlarm); + if (clusterId === SmokeCoAlarm.Cluster.id) return SmokeCoAlarmServer.with('SmokeAlarm', 'CoAlarm'); - if (clusterId === AirQuality.Cluster.id) return AirQualityServer.with(AirQuality.Feature.Fair, AirQuality.Feature.Moderate, AirQuality.Feature.VeryPoor, AirQuality.Feature.ExtremelyPoor); + if (clusterId === AirQuality.Cluster.id) return AirQualityServer.with('Fair', 'Moderate', 'VeryPoor', 'ExtremelyPoor'); if (clusterId === CarbonMonoxideConcentrationMeasurement.Cluster.id) return CarbonMonoxideConcentrationMeasurementServer.with('NumericMeasurement'); if (clusterId === CarbonDioxideConcentrationMeasurement.Cluster.id) return CarbonDioxideConcentrationMeasurementServer.with('NumericMeasurement'); if (clusterId === NitrogenDioxideConcentrationMeasurement.Cluster.id) return NitrogenDioxideConcentrationMeasurementServer.with('NumericMeasurement'); @@ -516,15 +522,22 @@ export class MatterbridgeEndpoint extends Endpoint { * @param {string} endpointName - The name of the new enpoint to add. * @param {AtLeastOne} deviceTypes - The device types to add. * @param {ClusterId[]} [includeServerList=[]] - The list of cluster IDs to include. - * @param {EndpointOptions} [options={}] - The options for the device. + * @param {MatterbridgeEndpointOptions} [options={}] - The options for the device. * @param {boolean} [debug=false] - Whether to enable debug logging. * @returns {MatterbridgeEndpoint} - The child endpoint that was found or added. */ - addChildDeviceTypeWithClusterServer(endpointName: string, deviceTypes: AtLeastOne, includeServerList: ClusterId[] = [], options: EndpointOptions = {}, debug = false) { + addChildDeviceTypeWithClusterServer(endpointName: string, deviceTypes: AtLeastOne, includeServerList: ClusterId[] = [], options: MatterbridgeEndpointOptions = {}, debug = false) { this.log.debug(`addChildDeviceTypeWithClusterServer: ${CYAN}${endpointName}${db}`); let child = this.getChildEndpointByName(endpointName); if (!child) { - child = new MatterbridgeEndpoint(deviceTypes[0], { uniqueStorageKey: endpointName }, debug); + if ('tagList' in options) { + for (const tag of options.tagList as Semtag[]) { + this.log.debug(`- with tagList: mfgCode ${CYAN}${tag.mfgCode}${db} namespaceId ${CYAN}${tag.namespaceId}${db} tag ${CYAN}${tag.tag}${db} label ${CYAN}${tag.label}${db}`); + } + child = new MatterbridgeEndpoint(deviceTypes[0], { uniqueStorageKey: endpointName, tagList: options.tagList }, debug); + } else { + child = new MatterbridgeEndpoint(deviceTypes[0], { uniqueStorageKey: endpointName }, debug); + } } deviceTypes.forEach((deviceType) => { this.log.debug(`- with deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`); @@ -574,6 +587,10 @@ export class MatterbridgeEndpoint extends Endpoint { }); } + hasClusterServer, A extends Attributes, C extends Commands, E extends Events>(cluster: Cluster): boolean { + return this.clusterServers.has(cluster.id); + } + getClusterServer(cluster: T): ClusterServerObj | undefined { const clusterServer = this.clusterServers.get(cluster.id); if (clusterServer !== undefined) { @@ -585,6 +602,10 @@ export class MatterbridgeEndpoint extends Endpoint { return this.clusterServers.get(clusterId); } + addTagList(endpoint: Endpoint, mfgCode: VendorId | null, namespaceId: number, tag: number, label?: string | null) { + // Do nothing here only for old api compatibility + } + addClusterServer(cluster: ClusterServerObj) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const options: Record = {}; @@ -597,15 +618,29 @@ export class MatterbridgeEndpoint extends Endpoint { } } this.log.debug(`addClusterServer: ${hk}${'0x' + cluster.id.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(cluster.id)}${db} with options: ${debugStringify(options)}${rs}`); + if (this.clusterServers.has(cluster.id)) { + this.log.debug(`****cluster ${hk}${'0x' + cluster.id.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(cluster.id)}${db} already added`); + } let type = undefined; if (cluster.id === SwitchCluster.id && cluster.isEventSupportedByName('multiPressComplete')) type = 'MomentarySwitch'; if (cluster.id === SwitchCluster.id && cluster.isEventSupportedByName('switchLatched')) type = 'LatchingSwitch'; + if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('wiredCurrentType')) type = 'WiredPowerSource'; if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('batReplacementDescription')) type = 'BatteryReplaceablePowerSource'; if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('batChargeState')) type = 'BatteryRechargeablePowerSource'; + + if (cluster.id === ThermostatCluster.id && cluster.isAttributeSupportedByName('occupiedCoolingSetpoint')) type = 'CoolingThermostat'; + if (cluster.id === ThermostatCluster.id && cluster.isAttributeSupportedByName('occupiedHeatingSetpoint')) type = 'HeatingThermostat'; + if (cluster.id === ThermostatCluster.id && cluster.isAttributeSupportedByName('minSetpointDeadBand')) type = 'AutoModeThermostat'; + const behavior = MatterbridgeEndpoint.getBehaviourTypeFromClusterServerId(cluster.id, type); - if (cluster.id !== BasicInformationCluster.id) this.behaviors.require(behavior, options); + if (cluster.id === PowerTopologyCluster.id && this.clusterServers.has(cluster.id)) return; // TODO remove this workaround + if (cluster.id === ElectricalPowerMeasurementCluster.id && this.clusterServers.has(cluster.id)) return; // TODO remove this workaround + if (cluster.id === ElectricalEnergyMeasurementCluster.id && this.clusterServers.has(cluster.id)) return; // TODO remove this workaround + if (cluster.id === ThermostatCluster.id && this.clusterServers.has(cluster.id)) return; // TODO remove this workaround this.clusterServers.set(cluster.id, cluster as unknown as ClusterServerObj); + if (cluster.id === BasicInformationCluster.id) return; // Not used in Matterbridge edge for devices. Only on server node. + this.behaviors.require(behavior, options); } /** @@ -656,6 +691,7 @@ export class MatterbridgeEndpoint extends Endpoint { async addFixedLabel(label: string, value: string) { if (!this.clusterServers.get(FixedLabelCluster.id)) { + this.log.debug(`addFixedLabel: add cluster ${hk}FixedLabelCluster${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`); this.addClusterServer( ClusterServer( FixedLabelCluster, @@ -667,6 +703,8 @@ export class MatterbridgeEndpoint extends Endpoint { ); return; } + this.log.debug(`addFixedLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`); + // if (this.construction.status !== Lifecycle.Status.Active) await this.construction.ready; const labelList = (this.getAttribute(FixedLabelCluster.id, 'labelList', this.log) ?? []).filter((entryLabel: { label: string; value: string }) => entryLabel.label !== label); labelList.push({ label, value }); await this.setAttribute(FixedLabelCluster.id, 'labelList', labelList, this.log); @@ -674,6 +712,7 @@ export class MatterbridgeEndpoint extends Endpoint { async addUserLabel(label: string, value: string) { if (!this.clusterServers.get(UserLabelCluster.id)) { + this.log.debug(`addUserLabel: add cluster ${hk}UserLabelCluster${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`); this.addClusterServer( ClusterServer( UserLabelCluster, @@ -685,6 +724,8 @@ export class MatterbridgeEndpoint extends Endpoint { ); return; } + this.log.debug(`addUserLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`); + // if (this.construction.status !== Lifecycle.Status.Active) await this.construction.ready; const labelList = (this.getAttribute(UserLabelCluster.id, 'labelList', this.log) ?? []).filter((entryLabel: { label: string; value: string }) => entryLabel.label !== label); labelList.push({ label, value }); await this.setAttribute(UserLabelCluster.id, 'labelList', labelList, this.log); @@ -706,7 +747,7 @@ export class MatterbridgeEndpoint extends Endpoint { * @param {ClusterId} clusterId - The ID of the cluster to retrieve the attribute from. * @param {string} attribute - The name of the attribute to retrieve. * @param {AnsiLogger} [log] - Optional logger for error and info messages. - * @param {Endpoint} [endpoint] - Optional the child endpoint to retrieve the attribute from. + * @param {MatterbridgeEndpoint} [endpoint] - Optional the child endpoint to retrieve the attribute from. * @returns {any} The value of the attribute, or undefined if the attribute is not found. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -715,7 +756,7 @@ export class MatterbridgeEndpoint extends Endpoint { const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId)); if (endpoint.construction.status !== Lifecycle.Status.Active) { - log?.error(`getAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`); + this.log.error(`getAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`); return undefined; } @@ -723,12 +764,12 @@ export class MatterbridgeEndpoint extends Endpoint { const state = endpoint.state as Record>; if (!(clusterName in state)) { - log?.error(`getAttribute error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); + this.log.error(`getAttribute error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); return undefined; } attribute = this.lowercaseFirstLetter(attribute); if (!(attribute in state[clusterName])) { - log?.error(`getAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); + this.log.error(`getAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); return undefined; } const value = state[clusterName][attribute]; @@ -745,7 +786,8 @@ export class MatterbridgeEndpoint extends Endpoint { * @param {string} attribute - The name of the attribute. * @param {any} value - The value to set for the attribute. * @param {AnsiLogger} [log] - (Optional) The logger to use for logging errors and information. - * @param {Endpoint} [endpoint] - (Optional) The endpoint to set the attribute on. If not provided, the attribute will be set on the current endpoint. + * @param {MatterbridgeEndpoint} [endpoint] - (Optional) The endpoint to set the attribute on. If not provided, the attribute will be set on the current endpoint. + * @returns {Promise} - A promise that resolves to a boolean indicating whether the attribute was successfully set. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any async setAttribute(clusterId: ClusterId, attribute: string, value: any, log?: AnsiLogger, endpoint?: MatterbridgeEndpoint): Promise { @@ -753,7 +795,8 @@ export class MatterbridgeEndpoint extends Endpoint { const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId)); if (endpoint.construction.status !== Lifecycle.Status.Active) { - log?.error(`setAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`); + this.log.error(`setAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`); + // await endpoint.construction.ready; return false; } @@ -761,12 +804,12 @@ export class MatterbridgeEndpoint extends Endpoint { const state = endpoint.state as Record>; if (!(clusterName in state)) { - log?.error(`setAttribute ${hk}${attribute}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); + this.log.error(`setAttribute ${hk}${attribute}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); return false; } attribute = this.lowercaseFirstLetter(attribute); if (!(attribute in state[clusterName])) { - log?.error(`setAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); + this.log.error(`setAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); return false; } let oldValue = state[clusterName][attribute]; @@ -787,26 +830,30 @@ export class MatterbridgeEndpoint extends Endpoint { * @param {ClusterId} clusterId - The ID of the cluster. * @param {string} attribute - The name of the attribute to subscribe to. * @param {(newValue: any, oldValue: any) => void} listener - A callback function that will be called when the attribute value changes. - * @param {AnsiLogger} log - (Optional) An AnsiLogger instance for logging errors and information. - * @param {Endpoint} endpoint - (Optional) The endpoint to subscribe the attribute on. If not provided, the current endpoint will be used. - * @returns A boolean indicating whether the subscription was successful. + * @param {AnsiLogger} [log] - Optional logger for logging errors and information. + * @param {MatterbridgeEndpoint} [endpoint] - Optional endpoint to subscribe the attribute on. Defaults to the current endpoint. + * @returns {boolean} - A boolean indicating whether the subscription was successful. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - subscribeAttribute(clusterId: ClusterId, attribute: string, listener: (newValue: any, oldValue: any) => void, log?: AnsiLogger, endpoint?: MatterbridgeEndpoint): boolean { + async subscribeAttribute(clusterId: ClusterId, attribute: string, listener: (newValue: any, oldValue: any) => void, log?: AnsiLogger, endpoint?: MatterbridgeEndpoint): Promise { if (!endpoint) endpoint = this as MatterbridgeEndpoint; const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId)); + if (endpoint.construction.status !== Lifecycle.Status.Active) { + // this.log.error(`subscribeAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`); + await endpoint.construction.ready; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const events = endpoint.events as Record>; if (!(clusterName in events)) { - log?.error(`subscribeAttribute ${hk}${attribute}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); + this.log.error(`subscribeAttribute ${hk}${attribute}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); return false; } attribute = this.lowercaseFirstLetter(attribute) + '$Changed'; if (!(attribute in events[clusterName])) { - log?.error(`subscribeAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); + this.log.error(`subscribeAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); return false; } events[clusterName][attribute].on(listener); @@ -814,21 +861,31 @@ export class MatterbridgeEndpoint extends Endpoint { return true; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async triggerEvent(clusterId: ClusterId, event: string, payload: Record, log?: AnsiLogger, endpoint?: MatterbridgeEndpoint): Promise { + /** + * Triggers an event on the specified cluster. + * + * @param {ClusterId} clusterId - The ID of the cluster. + * @param {string} event - The name of the event to trigger. + * @param {Record} payload - The payload to pass to the event. + * @param {AnsiLogger} [log] - Optional logger for logging information. + * @param {MatterbridgeEndpoint} [endpoint] - Optional endpoint to trigger the event on. Defaults to the current endpoint. + * @returns {Promise} - A promise that resolves to a boolean indicating whether the event was successfully triggered. + */ + async triggerEvent(clusterId: ClusterId, event: string, payload: Record, log?: AnsiLogger, endpoint?: MatterbridgeEndpoint): Promise { if (!endpoint) endpoint = this as MatterbridgeEndpoint; const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId)); if (endpoint.construction.status !== Lifecycle.Status.Active) { - log?.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`); - return false; + // this.log.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`); + await endpoint.construction.ready; + // return false; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const events = endpoint.events as Record>; if (!(clusterName in events) || !(event in events[clusterName])) { - log?.error(`triggerEvent ${hk}${event}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); + this.log.error(`triggerEvent ${hk}${event}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`); return false; } @@ -839,6 +896,13 @@ export class MatterbridgeEndpoint extends Endpoint { return true; } + /** + * Adds a command handler for the specified command. + * + * @param {keyof MatterbridgeEndpointCommands} command - The command to add the handler for. + * @param {(data: any) => void} handler - The handler function to execute when the command is received. + * @returns {void} + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any addCommandHandler(command: keyof MatterbridgeEndpointCommands, handler: (data: any) => void): void { this.commandHandler.addHandler(command, handler); @@ -1344,11 +1408,11 @@ export class MatterbridgeEndpoint extends Endpoint { * Get a default level control cluster server. * * @param currentLevel - The current level (default: 254). - * @param minLevel - The minimum level (default: 1). + * @param minLevel - The minimum level (default: 0). * @param maxLevel - The maximum level (default: 254). * @param onLevel - The on level (default: null). */ - getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel: number | null = null) { + getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 0, maxLevel = 254, onLevel: number | null = null) { return ClusterServer( LevelControlCluster.with(LevelControl.Feature.OnOff), { @@ -1396,11 +1460,11 @@ export class MatterbridgeEndpoint extends Endpoint { * Creates a default level control cluster server. * * @param currentLevel - The current level (default: 254). - * @param minLevel - The minimum level (default: 1). + * @param minLevel - The minimum level (default: 0). * @param maxLevel - The maximum level (default: 254). * @param onLevel - The on level (default: null). */ - createDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel: number | null = null) { + createDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 0, maxLevel = 254, onLevel: number | null = null) { this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel, minLevel, maxLevel, onLevel)); }