[matter] Upgrades to matter.js v0.14.0, adds matter error codes and translations (#18786)

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
pull/18793/head
Dan Cunningham 2025-06-10 14:04:16 -07:00 committed by GitHub
parent 9f73acaf39
commit c9eaa00145
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 395 additions and 151 deletions

View File

@ -2,6 +2,8 @@
The Matter Binding for openHAB allows seamless integration with Matter-compatible devices. The Matter Binding for openHAB allows seamless integration with Matter-compatible devices.
It currently supports version 1.4.1 of the Matter specification and earlier.
## Supported functionality ## Supported functionality
This binding supports two different types of Matter functionality which operate independently of each other. This binding supports two different types of Matter functionality which operate independently of each other.
@ -17,7 +19,7 @@ For more information on the Matter specification, see the [Matter Ecosystem Over
## Matter.JS Runtime ## Matter.JS Runtime
This binding uses the excellent [matter.js](https://github.com/project-chip/matter.js) implementation of the the Matter 1.4 protocol. This binding uses the excellent [matter.js](https://github.com/project-chip/matter.js) implementation of the the Matter 1.4.1 protocol.
As such, this binding requires NodesJS 18+ and will attempt to download and cache an appropriate version when started if a version is not already installed on the system. As such, this binding requires NodesJS 18+ and will attempt to download and cache an appropriate version when started if a version is not already installed on the system.
Alpine Linux users (typically docker) and those on older Linux distributions will need to install this manually as the official NodeJS versions are not compatible. Alpine Linux users (typically docker) and those on older Linux distributions will need to install this manually as the official NodeJS versions are not compatible.

View File

@ -8,10 +8,12 @@
"name": "code-gen", "name": "code-gen",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@matter/main": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/main": "v0.14.0",
"handlebars": "^4.7.8" "handlebars": "^4.7.8"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.2.2" "typescript": "^5.2.2"
@ -122,85 +124,85 @@
} }
}, },
"node_modules/@matter/general": { "node_modules/@matter/general": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/general/-/general-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.14.0.tgz",
"integrity": "sha512-0rkdLhn/ETSW5w/MQqJQnYRPtOwFx2VKRHAiu5w1lHS7TIVtJ4emPLq2HMSiQ2HMAEDz9eYzfIO4OueEhCbW4A==", "integrity": "sha512-lTaJgeWRlwr+4JZr0RcuwSMqbZMa/uPpDWG5P2RC7N/QvmL3fPoRjfdWYjCKXZkGfeG2XXIezQGCkp0z2BWLLQ==",
"dependencies": { "dependencies": {
"@noble/curves": "^1.9.1" "@noble/curves": "^1.9.1"
} }
}, },
"node_modules/@matter/main": { "node_modules/@matter/main": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/main/-/main-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.14.0.tgz",
"integrity": "sha512-tu/XAD3wK0kzI9PfXHUK5T0z4Y2vNUMZhbxNe7kSB4kHHjWCHitv1NMG0/uEIWRkSrm4/e82WCdJUrWxLgtm0g==", "integrity": "sha512-fBXQtBm5+ySWg7yA4FyiCl/olbirWeBSt8ExXCBoMX2blByiYgKdj7HiHWUK0ksgNGXwh8qX62jS3+VFEdWNBQ==",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/model": "0.14.0",
"@matter/node": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/node": "0.14.0",
"@matter/protocol": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/protocol": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@matter/nodejs": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/nodejs": "0.14.0"
} }
}, },
"node_modules/@matter/model": { "node_modules/@matter/model": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/model/-/model-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.14.0.tgz",
"integrity": "sha512-NvXbecY1WUjXVuXSzZX78uP50rixPNKM8mKP6JRzUpO8sKys1JwZIiJl3kGKPdTuvwot2hwpFJURtnReDam/MA==", "integrity": "sha512-m4j+AY4lJCi9VE5bikUMwRIVuNWfjFIjhjD6VonTuYWVvqMGttWRQ3jTZV9qszkgur9YvOW2REmCJKvzB/KPqw==",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/general": "0.14.0"
} }
}, },
"node_modules/@matter/node": { "node_modules/@matter/node": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/node/-/node-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.14.0.tgz",
"integrity": "sha512-dJt1HoFeomJKfBwcEkK2iAETLJfUVebYzCZ9Ie2qJ37uNc7DfhRomy/Dvj+deHS2Icfalm/COrkMiJQf2G3SvA==", "integrity": "sha512-QlK8Dg4YRD3db+CNZGnp0KVC1PsIbBwMzIAGh4TiO4ln68ltjRiOjNwYxb62F9YTSQhTuEQNxxiSfhK2nkYe2A==",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/model": "0.14.0",
"@matter/protocol": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/protocol": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
} }
}, },
"node_modules/@matter/nodejs": { "node_modules/@matter/nodejs": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.14.0.tgz",
"integrity": "sha512-TTI7HfubBUQEZjcQAmcikoEerTfEt5iCy7ctSwzvt+M0PMToLyj3UrOgDEezezYw+hPzTBmitLWyb/VBnSpk0g==", "integrity": "sha512-/XkBV5yX8ZH2SOkR4DDLoBivSONpEKk88QNf7AcTCf2wDadvM+hETsQ0Kli6U8pInpKTG/vghec+0bQ50nvO/A==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/node": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/node": "0.14.0",
"@matter/protocol": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/protocol": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@matter/protocol": { "node_modules/@matter/protocol": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.14.0.tgz",
"integrity": "sha512-y3PNdjtuiA1mAHnfpA3U30y29zQsxW3BOB3anYmGnpKTLTQia6hpmtp3FGHMBMi8rMNW5+PgQT9Gx2f/G+WwFg==", "integrity": "sha512-lmnXEXiO9/dEKOL/0n4jTXnEhwDwZvkRIu9QU6miuETMjBoG0spUkV6+EPWbE1+j/vXr21m65NoIvUtDoS2SKw==",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/model": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
} }
}, },
"node_modules/@matter/types": { "node_modules/@matter/types": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/types/-/types-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.14.0.tgz",
"integrity": "sha512-72AgxSd1PQ5jxILMqt3JJ0Eg+duBekM/Bbjy7RIsXAB6eKPwXvvGypC4jjHn/FrAFbS7s0UVIvezT+xvxQM1gg==", "integrity": "sha512-xOm/Mbs2Uqota4jJwAjl8kzsz8l1wIA216FslzSpS4TmzCskQNI8j7JIROlGkJbdMeZvBhFLirYnwilLYfQ6zw==",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/model": "0.14.0"
} }
}, },
"node_modules/@noble/curves": { "node_modules/@noble/curves": {
"version": "1.9.1", "version": "1.9.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz",
"integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==",
"dependencies": { "dependencies": {
"@noble/hashes": "1.8.0" "@noble/hashes": "1.8.0"
}, },
@ -1099,6 +1101,37 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
"dev": true,
"peerDependencies": {
"prettier": ">=2.0",
"typescript": ">=2.9",
"vue-tsc": "^2.1.0"
},
"peerDependenciesMeta": {
"vue-tsc": {
"optional": true
}
}
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",

View File

@ -18,7 +18,7 @@
"prettier-plugin-organize-imports": "^4.1.0" "prettier-plugin-organize-imports": "^4.1.0"
}, },
"dependencies": { "dependencies": {
"@matter/main": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/main": "v0.14.0",
"handlebars": "^4.7.8" "handlebars": "^4.7.8"
}, },
"files": [ "files": [

View File

@ -8,9 +8,9 @@
"name": "matter-server", "name": "matter-server",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@matter/main": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/main": "v0.14.0",
"@matter/node": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/node": "v0.14.0",
"@project-chip/matter.js": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@project-chip/matter.js": "v0.14.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"ws": "^8.18.0", "ws": "^8.18.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"
@ -424,85 +424,93 @@
} }
}, },
"node_modules/@matter/general": { "node_modules/@matter/general": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/general/-/general-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.14.0.tgz",
"integrity": "sha512-0rkdLhn/ETSW5w/MQqJQnYRPtOwFx2VKRHAiu5w1lHS7TIVtJ4emPLq2HMSiQ2HMAEDz9eYzfIO4OueEhCbW4A==", "integrity": "sha512-lTaJgeWRlwr+4JZr0RcuwSMqbZMa/uPpDWG5P2RC7N/QvmL3fPoRjfdWYjCKXZkGfeG2XXIezQGCkp0z2BWLLQ==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@noble/curves": "^1.9.1" "@noble/curves": "^1.9.1"
} }
}, },
"node_modules/@matter/main": { "node_modules/@matter/main": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/main/-/main-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.14.0.tgz",
"integrity": "sha512-tu/XAD3wK0kzI9PfXHUK5T0z4Y2vNUMZhbxNe7kSB4kHHjWCHitv1NMG0/uEIWRkSrm4/e82WCdJUrWxLgtm0g==", "integrity": "sha512-fBXQtBm5+ySWg7yA4FyiCl/olbirWeBSt8ExXCBoMX2blByiYgKdj7HiHWUK0ksgNGXwh8qX62jS3+VFEdWNBQ==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/model": "0.14.0",
"@matter/node": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/node": "0.14.0",
"@matter/protocol": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/protocol": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@matter/nodejs": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/nodejs": "0.14.0"
} }
}, },
"node_modules/@matter/model": { "node_modules/@matter/model": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/model/-/model-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.14.0.tgz",
"integrity": "sha512-NvXbecY1WUjXVuXSzZX78uP50rixPNKM8mKP6JRzUpO8sKys1JwZIiJl3kGKPdTuvwot2hwpFJURtnReDam/MA==", "integrity": "sha512-m4j+AY4lJCi9VE5bikUMwRIVuNWfjFIjhjD6VonTuYWVvqMGttWRQ3jTZV9qszkgur9YvOW2REmCJKvzB/KPqw==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/general": "0.14.0"
} }
}, },
"node_modules/@matter/node": { "node_modules/@matter/node": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/node/-/node-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.14.0.tgz",
"integrity": "sha512-dJt1HoFeomJKfBwcEkK2iAETLJfUVebYzCZ9Ie2qJ37uNc7DfhRomy/Dvj+deHS2Icfalm/COrkMiJQf2G3SvA==", "integrity": "sha512-QlK8Dg4YRD3db+CNZGnp0KVC1PsIbBwMzIAGh4TiO4ln68ltjRiOjNwYxb62F9YTSQhTuEQNxxiSfhK2nkYe2A==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/model": "0.14.0",
"@matter/protocol": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/protocol": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
} }
}, },
"node_modules/@matter/nodejs": { "node_modules/@matter/nodejs": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.14.0.tgz",
"integrity": "sha512-TTI7HfubBUQEZjcQAmcikoEerTfEt5iCy7ctSwzvt+M0PMToLyj3UrOgDEezezYw+hPzTBmitLWyb/VBnSpk0g==", "integrity": "sha512-/XkBV5yX8ZH2SOkR4DDLoBivSONpEKk88QNf7AcTCf2wDadvM+hETsQ0Kli6U8pInpKTG/vghec+0bQ50nvO/A==",
"license": "Apache-2.0",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/node": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/node": "0.14.0",
"@matter/protocol": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/protocol": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@matter/protocol": { "node_modules/@matter/protocol": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.14.0.tgz",
"integrity": "sha512-y3PNdjtuiA1mAHnfpA3U30y29zQsxW3BOB3anYmGnpKTLTQia6hpmtp3FGHMBMi8rMNW5+PgQT9Gx2f/G+WwFg==", "integrity": "sha512-lmnXEXiO9/dEKOL/0n4jTXnEhwDwZvkRIu9QU6miuETMjBoG0spUkV6+EPWbE1+j/vXr21m65NoIvUtDoS2SKw==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/model": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
} }
}, },
"node_modules/@matter/types": { "node_modules/@matter/types": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@matter/types/-/types-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.14.0.tgz",
"integrity": "sha512-72AgxSd1PQ5jxILMqt3JJ0Eg+duBekM/Bbjy7RIsXAB6eKPwXvvGypC4jjHn/FrAFbS7s0UVIvezT+xvxQM1gg==", "integrity": "sha512-xOm/Mbs2Uqota4jJwAjl8kzsz8l1wIA216FslzSpS4TmzCskQNI8j7JIROlGkJbdMeZvBhFLirYnwilLYfQ6zw==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/model": "0.14.0"
} }
}, },
"node_modules/@noble/curves": { "node_modules/@noble/curves": {
"version": "1.9.1", "version": "1.9.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz",
"integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==",
"license": "MIT",
"dependencies": { "dependencies": {
"@noble/hashes": "1.8.0" "@noble/hashes": "1.8.0"
}, },
@ -517,6 +525,7 @@
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": { "engines": {
"node": "^14.21.3 || >=16" "node": "^14.21.3 || >=16"
}, },
@ -563,15 +572,16 @@
} }
}, },
"node_modules/@project-chip/matter.js": { "node_modules/@project-chip/matter.js": {
"version": "0.14.0-alpha.0-20250531-7ed2d6da8", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.14.0-alpha.0-20250531-7ed2d6da8.tgz", "resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.14.0.tgz",
"integrity": "sha512-CewVH1Ug1eSZBetCICpDs0HJbhi8uAKLMgdiMmkFrf2FwcQb9whdCwQ1FmB0cHThZCNsHLicStqv1S5poS9QkQ==", "integrity": "sha512-w2aTSC3YbCbASBv/5YJvo9wIevRcfuuSP9mQwPnJVMlNiK8ocReVhS0L2xLqIrZVbA3SILATRnD6g7skT/G5wg==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@matter/general": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/general": "0.14.0",
"@matter/model": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/model": "0.14.0",
"@matter/node": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/node": "0.14.0",
"@matter/protocol": "0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/protocol": "0.14.0",
"@matter/types": "0.14.0-alpha.0-20250531-7ed2d6da8" "@matter/types": "0.14.0"
} }
}, },
"node_modules/@tsconfig/node10": { "node_modules/@tsconfig/node10": {

View File

@ -38,9 +38,9 @@
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
}, },
"dependencies": { "dependencies": {
"@matter/main": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/main": "v0.14.0",
"@matter/node": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@matter/node": "v0.14.0",
"@project-chip/matter.js": "v0.14.0-alpha.0-20250531-7ed2d6da8", "@project-chip/matter.js": "v0.14.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"ws": "^8.18.0", "ws": "^8.18.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"

View File

@ -44,21 +44,20 @@ export abstract class Controller {
try { try {
const result = this.executeCommand(namespace, functionName, args || []); const result = this.executeCommand(namespace, functionName, args || []);
if (result instanceof Promise) { if (result instanceof Promise) {
result const asyncResult = await result;
.then(asyncResult => {
this.ws.sendResponse(MessageType.ResultSuccess, id, asyncResult); this.ws.sendResponse(MessageType.ResultSuccess, id, asyncResult);
})
.catch(error => {
printError(logger, error, functionName);
this.ws.sendResponse(MessageType.ResultError, id, undefined, error.message);
});
} else { } else {
this.ws.sendResponse(MessageType.ResultSuccess, id, result); this.ws.sendResponse(MessageType.ResultSuccess, id, result);
} }
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
printError(logger, error, functionName); printError(logger, error, functionName);
this.ws.sendResponse(MessageType.ResultError, id, undefined, error.message); let errorId: string | undefined;
// instances of a MatterError has an id property
if ("id" in error) {
errorId = (error as any).id;
}
this.ws.sendResponse(MessageType.ResultError, id, undefined, error.message, errorId);
} else { } else {
logger.error(`Unexpected error executing function ${functionName}: ${error}`); logger.error(`Unexpected error executing function ${functionName}: ${error}`);
this.ws.sendResponse(MessageType.ResultError, id, undefined, String(error)); this.ws.sendResponse(MessageType.ResultError, id, undefined, String(error));

View File

@ -31,7 +31,6 @@ export interface Message {
} }
export enum MessageType { export enum MessageType {
Result = "result",
ResultError = "resultError", ResultError = "resultError",
ResultSuccess = "resultSuccess", ResultSuccess = "resultSuccess",
} }

View File

@ -6,8 +6,10 @@ import { hideBin } from "yargs/helpers";
import { BridgeController } from "./bridge/BridgeController"; import { BridgeController } from "./bridge/BridgeController";
import { ClientController } from "./client/ClientController"; import { ClientController } from "./client/ClientController";
import { Controller } from "./Controller"; import { Controller } from "./Controller";
import { Message, MessageType, Request } from "./MessageTypes"; import { Message, Request } from "./MessageTypes";
import { printError } from "./util/error"; import { printError } from "./util/error";
import { toJSON } from "./util/Json";
const argv: any = yargs(hideBin(process.argv)).argv; const argv: any = yargs(hideBin(process.argv)).argv;
const logger = Logger.get("matter"); const logger = Logger.get("matter");
@ -72,7 +74,7 @@ const shutdownHandler = async (signal: string) => {
export interface WebSocketSession extends WebSocket { export interface WebSocketSession extends WebSocket {
controller?: Controller; controller?: Controller;
sendResponse(type: string, id: string, result?: any, error?: string): void; sendResponse(type: string, id: string, result?: any, error?: string, errorId?: string): void;
sendEvent(type: string, data?: any): void; sendEvent(type: string, data?: any): void;
} }
@ -80,7 +82,7 @@ const socketPort = argv.port ? parseInt(argv.port) : 8888;
const wss: Server = new WebSocket.Server({ port: socketPort, host: argv.host }); const wss: Server = new WebSocket.Server({ port: socketPort, host: argv.host });
wss.on("connection", (ws: WebSocketSession, req: IncomingMessage) => { wss.on("connection", (ws: WebSocketSession, req: IncomingMessage) => {
ws.sendResponse = (type: string, id: string, result?: any, error?: string) => { ws.sendResponse = (type: string, id: string, result?: any, error?: string, errorId?: string) => {
const message: Message = { const message: Message = {
type: "response", type: "response",
message: { message: {
@ -88,10 +90,11 @@ wss.on("connection", (ws: WebSocketSession, req: IncomingMessage) => {
id, id,
result, result,
error, error,
errorId,
}, },
}; };
logger.debug(`Sending response: ${Logger.toJSON(message)}`); logger.debug(`Sending response: ${toJSON(message)}`);
ws.send(Logger.toJSON(message)); ws.send(toJSON(message));
}; };
ws.sendEvent = (type: string, data?: any) => { ws.sendEvent = (type: string, data?: any) => {
@ -102,8 +105,8 @@ wss.on("connection", (ws: WebSocketSession, req: IncomingMessage) => {
data, data,
}, },
}; };
logger.debug(`Sending event: ${Logger.toJSON(message)}`); logger.debug(`Sending event: ${toJSON(message)}`);
ws.send(Logger.toJSON(message)); ws.send(toJSON(message));
}; };
ws.on("open", () => { ws.on("open", () => {
@ -111,17 +114,9 @@ wss.on("connection", (ws: WebSocketSession, req: IncomingMessage) => {
}); });
ws.on("message", (message: string) => { ws.on("message", (message: string) => {
try {
const request: Request = JSON.parse(message); const request: Request = JSON.parse(message);
if (ws.controller) { if (ws.controller) {
void ws.controller.handleRequest(request).catch((error: Error) => { void ws.controller.handleRequest(request);
ws.sendResponse(MessageType.ResultError, "", undefined, error.message);
});
}
} catch (error) {
if (error instanceof Error) {
ws.sendResponse(MessageType.ResultError, "", undefined, error.message);
}
} }
}); });

View File

@ -1,6 +1,7 @@
import { Logger } from "@matter/general"; import { Logger } from "@matter/general";
import { WebSocketSession } from "../app"; import { WebSocketSession } from "../app";
import { Controller } from "../Controller"; import { Controller } from "../Controller";
import { toJSON } from "../util/Json";
import { ControllerNode } from "./ControllerNode"; import { ControllerNode } from "./ControllerNode";
import { Clusters } from "./namespaces/Clusters"; import { Clusters } from "./namespaces/Clusters";
import { Nodes } from "./namespaces/Nodes"; import { Nodes } from "./namespaces/Nodes";
@ -53,7 +54,7 @@ export class ClientController extends Controller {
} }
executeCommand(namespace: string, functionName: string, args: any[]): any | Promise<any> { executeCommand(namespace: string, functionName: string, args: any[]): any | Promise<any> {
logger.debug(`Executing function ${namespace}.${functionName}(${Logger.toJSON(args)})`); logger.debug(`Executing function ${namespace}.${functionName}(${toJSON(args)})`);
const controllerAny: any = this; const controllerAny: any = this;

View File

@ -2,7 +2,7 @@ import { Logger } from "@matter/general";
import { ClusterId, ValidationError } from "@matter/main/types"; import { ClusterId, ValidationError } from "@matter/main/types";
import { ClusterModel, MatterModel } from "@matter/model"; import { ClusterModel, MatterModel } from "@matter/model";
import { SupportedAttributeClient } from "@matter/protocol"; import { SupportedAttributeClient } from "@matter/protocol";
import { convertJsonDataWithModel } from "../../util/Json"; import { convertJsonDataWithModel, toJSON } from "../../util/Json";
import { capitalize } from "../../util/String"; import { capitalize } from "../../util/String";
import { ControllerNode } from "../ControllerNode"; import { ControllerNode } from "../ControllerNode";
@ -28,7 +28,7 @@ export class Clusters {
* @throws Error if the cluster or command is not found on the device. * @throws Error if the cluster or command is not found on the device.
*/ */
async command(nodeId: number, endpointId: number, clusterName: string, commandName: string, args: any) { async command(nodeId: number, endpointId: number, clusterName: string, commandName: string, args: any) {
logger.debug(`command ${nodeId} ${endpointId} ${clusterName} ${commandName} ${Logger.toJSON(args)}`); logger.debug(`command ${nodeId} ${endpointId} ${clusterName} ${commandName} ${toJSON(args)}`);
const device = this.controllerNode.getNode(nodeId).getDeviceById(endpointId); const device = this.controllerNode.getNode(nodeId).getDeviceById(endpointId);
if (device == undefined) { if (device == undefined) {
throw new Error(`Endpoint ${endpointId} not found`); throw new Error(`Endpoint ${endpointId} not found`);
@ -114,15 +114,15 @@ export class Clusters {
parsedValue = convertJsonDataWithModel(attribute, parsedValue); parsedValue = convertJsonDataWithModel(attribute, parsedValue);
await attributeClient.set(parsedValue); await attributeClient.set(parsedValue);
console.log( console.log(
`Attribute ${attributeName} ${nodeId}/${endpointId}/${clusterName}/${attributeName} set to ${Logger.toJSON(value)}`, `Attribute ${attributeName} ${nodeId}/${endpointId}/${clusterName}/${attributeName} set to ${toJSON(value)}`,
); );
} catch (error) { } catch (error) {
if (error instanceof ValidationError) { if (error instanceof ValidationError) {
throw new Error( throw new Error(
`Could not validate data for attribute ${attributeName} to ${Logger.toJSON(parsedValue)}: ${error}${error.fieldName !== undefined ? ` in field ${error.fieldName}` : ""}`, `Could not validate data for attribute ${attributeName} to ${toJSON(parsedValue)}: ${error}${error.fieldName !== undefined ? ` in field ${error.fieldName}` : ""}`,
); );
} else { } else {
throw new Error(`Could not set attribute ${attributeName} to ${Logger.toJSON(parsedValue)}: ${error}`); throw new Error(`Could not set attribute ${attributeName} to ${toJSON(parsedValue)}: ${error}`);
} }
} }
} }

View File

@ -11,6 +11,9 @@ export function printError(logger: Logger, error: Error, functionName: string) {
if ("name" in error) { if ("name" in error) {
logger.error(`Error name: ${(error as any).name}`); logger.error(`Error name: ${(error as any).name}`);
} }
if ("id" in error) {
logger.error(`Error id: ${(error as any).id}`);
}
// Fallback: log the entire error object in case there are other useful details // Fallback: log the entire error object in case there are other useful details
logger.error(`Full error object: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`); logger.error(`Full error object: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`);

View File

@ -17,6 +17,8 @@ import java.util.concurrent.ExecutionException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.matter.internal.MatterBindingConstants; import org.openhab.binding.matter.internal.MatterBindingConstants;
import org.openhab.binding.matter.internal.client.MatterErrorCode;
import org.openhab.binding.matter.internal.client.MatterRequestException;
import org.openhab.binding.matter.internal.handler.ControllerHandler; import org.openhab.binding.matter.internal.handler.ControllerHandler;
import org.openhab.binding.matter.internal.util.TranslationService; import org.openhab.binding.matter.internal.util.TranslationService;
import org.openhab.core.automation.annotation.ActionInput; import org.openhab.core.automation.annotation.ActionInput;
@ -72,9 +74,21 @@ public class MatterControllerActions implements ThingActions {
try { try {
handler.startScan(code).get(); handler.startScan(code).get();
return translationService.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_DEVICE_ADDED); return translationService.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_DEVICE_ADDED);
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException e) {
return handler.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_PAIRING_FAILED) return handler.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_PAIRING_FAILED,
+ e.getLocalizedMessage(); e.getLocalizedMessage());
} catch (ExecutionException e) {
if (e.getCause() instanceof MatterRequestException matterRequestException) {
MatterErrorCode errorCode = matterRequestException.getErrorCode();
if (errorCode != null) {
return handler.getTranslation(errorCode.getTranslationKey());
} else {
return handler.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_PAIRING_FAILED,
matterRequestException.getErrorMessage());
}
}
return handler.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_PAIRING_FAILED,
e.getLocalizedMessage());
} }
} }
return translationService.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_NO_HANDLER); return translationService.getTranslation(MatterBindingConstants.THING_ACTION_RESULT_NO_HANDLER);

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.matter.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Error codes and translation keys for Matter errors events.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public enum MatterErrorCode {
COMMISSIONING("commissioning", "matterjs.error.commissioning"),
MAXIMUM_COMMISSIONED_FABRICS_REACHED("maximum-commissioned-fabrics-reached",
"matterjs.error.maximum-commissioned-fabrics-reached"),
COMMISSIONING_TIMEOUT("commissioning-timeout", "matterjs.error.commissioning-timeout"),
DEVICE_ALREADY_COMMISSIONED_TO_THIS_FABRIC("device-already-commissioned-to-this-fabric",
"matterjs.error.device-already-commissioned-to-this-fabric"),
FABRIC_LABEL_CONFLICT("fabric-label-conflict", "matterjs.error.fabric-label-conflict"),
WIFI_OR_THREAD_NETWORK_CREDENTIALS_NOT_CONFIGURED("wifi-or-thread-network-credentials-not-configured",
"matterjs.error.wifi-or-thread-network-credentials-not-configured"),
WIFI_NETWORK_SETUP_FAILED("wifi-network-setup-failed", "matterjs.error.wifi-network-setup-failed"),
THREAD_NETWORK_SETUP_FAILED("thread-network-setup-failed", "matterjs.error.thread-network-setup-failed"),
NODE_ID_CONFLICT("node-id-conflict", "matterjs.error.node-id-conflict"),
COMMISSIONABLE_DEVICE_DISCOVERY_FAILED("commissionable-device-discovery-failed",
"matterjs.error.commissionable-device-discovery-failed"),
OPERATIVE_CONNECTION_FAILED("operative-connection-failed", "matterjs.error.operative-connection-failed");
private final String errorId;
private final String translationKey;
MatterErrorCode(String errorId, String translationKey) {
this.errorId = errorId;
this.translationKey = translationKey;
}
public String getErrorId() {
return errorId;
}
public String getTranslationKey() {
return translationKey;
}
public static @Nullable MatterErrorCode fromErrorId(String errorId) {
for (MatterErrorCode error : values()) {
if (error.errorId.equals(errorId)) {
return error;
}
}
return null;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.matter.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Exception thrown when a request to the Matter server fails.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class MatterRequestException extends Exception {
private static final long serialVersionUID = 1L;
private final String errorMessage;
private final @Nullable MatterErrorCode errorCode;
public MatterRequestException(String message, @Nullable MatterErrorCode errorCode) {
super(message);
this.errorMessage = message;
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public @Nullable MatterErrorCode getErrorCode() {
return errorCode;
}
}

View File

@ -58,6 +58,7 @@ import org.openhab.binding.matter.internal.client.dto.ws.NodeStateMessage;
import org.openhab.binding.matter.internal.client.dto.ws.Path; import org.openhab.binding.matter.internal.client.dto.ws.Path;
import org.openhab.binding.matter.internal.client.dto.ws.Request; import org.openhab.binding.matter.internal.client.dto.ws.Request;
import org.openhab.binding.matter.internal.client.dto.ws.Response; import org.openhab.binding.matter.internal.client.dto.ws.Response;
import org.openhab.binding.matter.internal.client.dto.ws.ResponseType;
import org.openhab.binding.matter.internal.client.dto.ws.TriggerEvent; import org.openhab.binding.matter.internal.client.dto.ws.TriggerEvent;
import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.common.ThreadPoolManager;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -261,8 +262,9 @@ public class MatterWebsocketClient implements WebSocketListener, MatterWebsocket
return; return;
} }
logger.debug("result type: {} ", response.type); logger.debug("result type: {} ", response.type);
if (!"resultSuccess".equals(response.type)) { if (response.type != ResponseType.RESULT_SUCCESS) {
future.completeExceptionally(new Exception(response.error)); future.completeExceptionally(
new MatterRequestException(response.error, MatterErrorCode.fromErrorId(response.errorId)));
} else { } else {
future.complete(response.result); future.complete(response.result);
} }

View File

@ -20,8 +20,9 @@ import com.google.gson.JsonElement;
* @author Dan Cunningham - Initial contribution * @author Dan Cunningham - Initial contribution
*/ */
public class Response { public class Response {
public String type; public ResponseType type;
public String id; public String id;
public JsonElement result; public JsonElement result;
public String error; public String error;
public String errorId;
} }

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.matter.internal.client.dto.ws;
import com.google.gson.annotations.SerializedName;
/**
* Websocket message response types.
*
* @author Dan Cunningham - Initial contribution
*/
public enum ResponseType {
@SerializedName("resultError")
RESULT_ERROR("resultError"),
@SerializedName("resultSuccess")
RESULT_SUCCESS("resultSuccess");
private final String value;
ResponseType(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
public String getValue() {
return value;
}
}

View File

@ -53,6 +53,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* Get all nodes that are commissioned / paired to this controller * Get all nodes that are commissioned / paired to this controller
* *
* @return a future that returns a list of node IDs * @return a future that returns a list of node IDs
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<List<BigInteger>> getCommissionedNodeIds() { public CompletableFuture<List<BigInteger>> getCommissionedNodeIds() {
CompletableFuture<JsonElement> future = sendMessage("nodes", "listNodes", new Object[0]); CompletableFuture<JsonElement> future = sendMessage("nodes", "listNodes", new Object[0]);
@ -69,6 +70,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param nodeId the node ID to initialize * @param nodeId the node ID to initialize
* @param connectionTimeoutMilliseconds the timeout in milliseconds to wait for the node to connect * @param connectionTimeoutMilliseconds the timeout in milliseconds to wait for the node to connect
* @return a future that completes when the node is initialized * @return a future that completes when the node is initialized
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> initializeNode(BigInteger nodeId, Integer connectionTimeoutMilliseconds) { public CompletableFuture<Void> initializeNode(BigInteger nodeId, Integer connectionTimeoutMilliseconds) {
// add 1 second delay to the message timeout to allow the function to complete // add 1 second delay to the message timeout to allow the function to complete
@ -85,6 +87,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* *
* @param nodeId the node ID to request data for * @param nodeId the node ID to request data for
* @return a future that completes when the data is requested * @return a future that completes when the data is requested
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> requestAllNodeData(BigInteger nodeId) { public CompletableFuture<Void> requestAllNodeData(BigInteger nodeId) {
CompletableFuture<JsonElement> future = sendMessage("nodes", "requestAllData", new Object[] { nodeId }); CompletableFuture<JsonElement> future = sendMessage("nodes", "requestAllData", new Object[] { nodeId });
@ -99,6 +102,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param nodeId the node ID to request data for * @param nodeId the node ID to request data for
* @param endpointId the endpoint ID to request data for * @param endpointId the endpoint ID to request data for
* @return a future that completes when the data is requested * @return a future that completes when the data is requested
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> requestEndpointData(BigInteger nodeId, Integer endpointId) { public CompletableFuture<Void> requestEndpointData(BigInteger nodeId, Integer endpointId) {
CompletableFuture<JsonElement> future = sendMessage("nodes", "requestEndpointData", CompletableFuture<JsonElement> future = sendMessage("nodes", "requestEndpointData",
@ -113,6 +117,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* *
* @param code the pairing code to pair with * @param code the pairing code to pair with
* @return a future that completes when the node is paired (or fails) * @return a future that completes when the node is paired (or fails)
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<BigInteger> pairNode(String code) { public CompletableFuture<BigInteger> pairNode(String code) {
String[] parts = code.trim().split(" "); String[] parts = code.trim().split(" ");
@ -134,6 +139,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* *
* @param nodeId the node ID to remove * @param nodeId the node ID to remove
* @return a future that completes when the node is removed * @return a future that completes when the node is removed
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> removeNode(BigInteger nodeId) { public CompletableFuture<Void> removeNode(BigInteger nodeId) {
CompletableFuture<JsonElement> future = sendMessage("nodes", "removeNode", new Object[] { nodeId }); CompletableFuture<JsonElement> future = sendMessage("nodes", "removeNode", new Object[] { nodeId });
@ -147,6 +153,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* *
* @param nodeId the node ID to reconnect * @param nodeId the node ID to reconnect
* @return a future that completes when the node is reconnected * @return a future that completes when the node is reconnected
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> reconnectNode(BigInteger nodeId) { public CompletableFuture<Void> reconnectNode(BigInteger nodeId) {
CompletableFuture<JsonElement> future = sendMessage("nodes", "reconnectNode", new Object[] { nodeId }); CompletableFuture<JsonElement> future = sendMessage("nodes", "reconnectNode", new Object[] { nodeId });
@ -161,6 +168,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param nodeId the node ID to get the pairing codes for * @param nodeId the node ID to get the pairing codes for
* @return a future that completes when the pairing codes are retrieved * @return a future that completes when the pairing codes are retrieved
* @throws JsonParseException when completing the future if the pairing codes cannot be deserialized * @throws JsonParseException when completing the future if the pairing codes cannot be deserialized
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<PairingCodes> enhancedCommissioningWindow(BigInteger nodeId) { public CompletableFuture<PairingCodes> enhancedCommissioningWindow(BigInteger nodeId) {
CompletableFuture<JsonElement> future = sendMessage("nodes", "enhancedCommissioningWindow", CompletableFuture<JsonElement> future = sendMessage("nodes", "enhancedCommissioningWindow",
@ -179,6 +187,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* *
* @param nodeId the node ID to disconnect * @param nodeId the node ID to disconnect
* @return a future that completes when the node is disconnected * @return a future that completes when the node is disconnected
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> disconnectNode(BigInteger nodeId) { public CompletableFuture<Void> disconnectNode(BigInteger nodeId) {
CompletableFuture<JsonElement> future = sendMessage("nodes", "disconnectNode", new Object[] { nodeId }); CompletableFuture<JsonElement> future = sendMessage("nodes", "disconnectNode", new Object[] { nodeId });
@ -193,6 +202,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param nodeId the node ID to get the fabrics for * @param nodeId the node ID to get the fabrics for
* @return a future that completes when the fabrics are retrieved or an exception is thrown * @return a future that completes when the fabrics are retrieved or an exception is thrown
* @throws JsonParseException when completing the future if the fabrics cannot be deserialized * @throws JsonParseException when completing the future if the fabrics cannot be deserialized
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<List<OperationalCredentialsCluster.FabricDescriptorStruct>> getFabrics(BigInteger nodeId) { public CompletableFuture<List<OperationalCredentialsCluster.FabricDescriptorStruct>> getFabrics(BigInteger nodeId) {
Object[] clusterArgs = { String.valueOf(nodeId) }; Object[] clusterArgs = { String.valueOf(nodeId) };
@ -214,6 +224,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param nodeId the node ID to remove the fabric from * @param nodeId the node ID to remove the fabric from
* @param index the index of the fabric to remove * @param index the index of the fabric to remove
* @return a future that completes when the fabric is removed * @return a future that completes when the fabric is removed
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> removeFabric(BigInteger nodeId, Integer index) { public CompletableFuture<Void> removeFabric(BigInteger nodeId, Integer index) {
CompletableFuture<JsonElement> future = sendMessage("nodes", "removeFabric", new Object[] { nodeId, index }); CompletableFuture<JsonElement> future = sendMessage("nodes", "removeFabric", new Object[] { nodeId, index });
@ -230,6 +241,8 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param clusterName the cluster name to send the command to * @param clusterName the cluster name to send the command to
* @param command the command to send * @param command the command to send
* @return a future that completes when the command is sent * @return a future that completes when the command is sent
* @throws MatterRequestException if the request fails
* @throws JsonParseException when completing the future if the command cannot be deserialized
*/ */
public CompletableFuture<JsonElement> clusterCommand(BigInteger nodeId, Integer endpointId, String clusterName, public CompletableFuture<JsonElement> clusterCommand(BigInteger nodeId, Integer endpointId, String clusterName,
ClusterCommand command) { ClusterCommand command) {
@ -247,6 +260,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param attributeName the attribute name to write * @param attributeName the attribute name to write
* @param value the value to write * @param value the value to write
* @return a future that completes when the attribute is written * @return a future that completes when the attribute is written
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<Void> clusterWriteAttribute(BigInteger nodeId, Integer endpointId, String clusterName, public CompletableFuture<Void> clusterWriteAttribute(BigInteger nodeId, Integer endpointId, String clusterName,
String attributeName, String value) { String attributeName, String value) {
@ -266,6 +280,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param clusterId the cluster ID to read the cluster from * @param clusterId the cluster ID to read the cluster from
* @return a future that completes when the cluster is read * @return a future that completes when the cluster is read
* @throws JsonParseException when completing the future if the cluster cannot be deserialized * @throws JsonParseException when completing the future if the cluster cannot be deserialized
* @throws MatterRequestException if the request fails
*/ */
public <T extends BaseCluster> CompletableFuture<T> readCluster(Class<T> type, BigInteger nodeId, public <T extends BaseCluster> CompletableFuture<T> readCluster(Class<T> type, BigInteger nodeId,
Integer endpointId, Integer clusterId) { Integer endpointId, Integer clusterId) {
@ -289,6 +304,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* @param clusterName the cluster name to read the attribute from * @param clusterName the cluster name to read the attribute from
* @param attributeName the attribute name to read * @param attributeName the attribute name to read
* @return a future that completes when the attribute is read * @return a future that completes when the attribute is read
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<String> clusterReadAttribute(BigInteger nodeId, Integer endpointId, String clusterName, public CompletableFuture<String> clusterReadAttribute(BigInteger nodeId, Integer endpointId, String clusterName,
String attributeName) { String attributeName) {
@ -304,6 +320,7 @@ public class MatterControllerClient extends MatterWebsocketClient {
* *
* @return a future that completes when the session information is retrieved * @return a future that completes when the session information is retrieved
* @throws JsonParseException when completing the future if the session information cannot be deserialized * @throws JsonParseException when completing the future if the session information cannot be deserialized
* @throws MatterRequestException if the request fails
*/ */
public CompletableFuture<ActiveSessionInformation[]> getSessionInformation() { public CompletableFuture<ActiveSessionInformation[]> getSessionInformation() {
CompletableFuture<JsonElement> future = sendMessage("nodes", "sessionInformation", new Object[0]); CompletableFuture<JsonElement> future = sendMessage("nodes", "sessionInformation", new Object[0]);

View File

@ -270,8 +270,8 @@ public class ControllerHandler extends BaseBridgeHandler implements MatterClient
linkedNodes.keySet().forEach(nodeId -> updateNode(nodeId)); linkedNodes.keySet().forEach(nodeId -> updateNode(nodeId));
} }
public String getTranslation(String key) { public String getTranslation(String key, Object... args) {
return translationService.getTranslation(key); return translationService.getTranslation(key, args);
} }
public MatterControllerClient getClient() { public MatterControllerClient getClient() {

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.matter.internal.util; package org.openhab.binding.matter.internal.util;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
@ -51,10 +53,10 @@ public class TranslationService {
* @param key the key to get the translation for (with or without the @text/ prefix) * @param key the key to get the translation for (with or without the @text/ prefix)
* @return the translation * @return the translation
*/ */
public String getTranslation(String key) { public String getTranslation(String key, Object... args) {
String lookupKey = key.replace("@text/", ""); String lookupKey = key.replace("@text/", "");
String result = translationProvider.getText(bundle, lookupKey, lookupKey, localeProvider.getLocale()); String result = translationProvider.getText(bundle, lookupKey, lookupKey, localeProvider.getLocale(), args);
return result == null ? lookupKey : result; return result == null ? lookupKey + " " + Arrays.toString(args) : result;
} }
public LocaleProvider getLocaleProvider() { public LocaleProvider getLocaleProvider() {

View File

@ -324,3 +324,17 @@ thing-action.result.pairing-failed = Failed to pair device: {0}
thing-status.detail.controller.waitingForData = Waiting for data thing-status.detail.controller.waitingForData = Waiting for data
thing-status.detail.endpoint.thingNotReachable = Bridge reports device as not reachable thing-status.detail.endpoint.thingNotReachable = Bridge reports device as not reachable
# matterjs error messages
matterjs.error.commissioning = General commissioning error
matterjs.error.maximum-commissioned-fabrics-reached = Maximum number of commissioned fabrics reached on device, please remove a fabric from another controller or reset the device
matterjs.error.commissioning-timeout = The commissioning process could not be finished within the maximum allowed time frame
matterjs.error.device-already-commissioned-to-this-fabric = The device is already commissioned to this fabric, either remove this fabric using another controller or reset the device
matterjs.error.fabric-label-conflict = The device is already commissioned to another fabric with the same label, please remove the fabric using another controller or reset the device
matterjs.error.wifi-or-thread-network-credentials-not-configured = Wi-Fi or Thread network credentials not configured
matterjs.error.wifi-network-setup-failed = Wi-Fi network setup failed
matterjs.error.thread-network-setup-failed = Thread network setup failed
matterjs.error.node-id-conflict = Node ID conflict, reset the device and try again
matterjs.error.commissionable-device-discovery-failed = The device could not be discovered using the provided code
matterjs.error.operative-connection-failed = The reconnection process for the device failed, please reset the device and try again