diff --git a/front/src/config/i18n/de.json b/front/src/config/i18n/de.json
index a7f017e1f2..70efc012ba 100644
--- a/front/src/config/i18n/de.json
+++ b/front/src/config/i18n/de.json
@@ -2133,7 +2133,16 @@
"disabledWarningSettings": "Matter ist nicht aktiviert, bitte klicke auf den Button unten, um es zu aktivieren.",
"enableButton": "Matter aktivieren",
"disableButton": "Matter deaktivieren",
- "downloadNodesJson": "Knoten-JSON herunterladen"
+ "downloadNodesJson": "Knoten-JSON herunterladen",
+ "reset": {
+ "title": "Integration zurücksetzen",
+ "description": "Verwende diese Option, wenn du mit Matter von vorne beginnen möchtest. Dadurch werden alle Controller-Daten, Backups und der Matter-Ordner gelöscht.",
+ "button": "Zurücksetzen",
+ "confirmMessage": "Achtung, das Zurücksetzen der Integration löscht alle Matter-Controller-Daten, Backups und den Matter-Ordner. Alle derzeit gekoppelten Matter-Geräte müssen erneut gekoppelt werden.",
+ "confirmButton": "Ja, alles zurücksetzen",
+ "cancelButton": "Abbrechen",
+ "error": "Beim Zurücksetzen der Integration ist ein Fehler aufgetreten."
+ }
},
"device": {
"title": "Matter-Geräte",
diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json
index 9de1d41897..3469c9974e 100644
--- a/front/src/config/i18n/en.json
+++ b/front/src/config/i18n/en.json
@@ -2133,7 +2133,16 @@
"disabledWarningSettings": "Matter is not activated, please click on the button below to activate it.",
"enableButton": "Enable Matter",
"disableButton": "Disable Matter",
- "downloadNodesJson": "Download Nodes JSON"
+ "downloadNodesJson": "Download Nodes JSON",
+ "reset": {
+ "title": "Reset Integration",
+ "description": "Use this option if you want to start from scratch with Matter. This will delete all controller data, backup, and Matter folder.",
+ "button": "Reset",
+ "confirmMessage": "Warning, resetting the integration will delete all Matter controller data, backup, and Matter folder. All Matter devices currently paired will need to be re-paired.",
+ "confirmButton": "Yes, reset everything",
+ "cancelButton": "Cancel",
+ "error": "An error occurred while resetting the integration."
+ }
},
"device": {
"title": "Matter Devices",
diff --git a/front/src/config/i18n/fr.json b/front/src/config/i18n/fr.json
index aae0b1fcaf..1cf37f933d 100644
--- a/front/src/config/i18n/fr.json
+++ b/front/src/config/i18n/fr.json
@@ -2164,7 +2164,16 @@
"disabledWarningSettings": "Matter n'est pas activé, veuillez cliquer sur le bouton ci-dessous pour l'activer.",
"enableButton": "Activer Matter",
"disableButton": "Désactiver Matter",
- "downloadNodesJson": "Télécharger Noeuds Matter en JSON"
+ "downloadNodesJson": "Télécharger Noeuds Matter en JSON",
+ "reset": {
+ "title": "Réinitialiser l'intégration",
+ "description": "Utilisez cette option si vous souhaitez repartir de zéro avec Matter. Cela supprimera toutes les données du contrôleur, la sauvegarde et le dossier Matter.",
+ "button": "Réinitialiser",
+ "confirmMessage": "Attention, la réinitialisation de l'intégration supprimera toutes les données du contrôleur Matter, la sauvegarde et le dossier Matter. Tous les appareils Matter actuellement appairés devront être ré-appairés.",
+ "confirmButton": "Oui, tout réinitialiser",
+ "cancelButton": "Annuler",
+ "error": "Une erreur est survenue lors de la réinitialisation de l'intégration."
+ }
}
},
"mcp": {
diff --git a/front/src/routes/integration/all/matter/MatterSettingsPage.jsx b/front/src/routes/integration/all/matter/MatterSettingsPage.jsx
index 2dfe725d05..ac20742886 100644
--- a/front/src/routes/integration/all/matter/MatterSettingsPage.jsx
+++ b/front/src/routes/integration/all/matter/MatterSettingsPage.jsx
@@ -136,7 +136,10 @@ class MatterSettingsPage extends Component {
loadingNodes: true,
decommissioningNodes: {},
collapsedDevices: {},
- visibleKeys: {}
+ visibleKeys: {},
+ showConfirmReset: false,
+ resetting: false,
+ resetError: null
};
}
@@ -319,6 +322,29 @@ class MatterSettingsPage extends Component {
window.URL.revokeObjectURL(url);
};
+ showConfirmReset = () => {
+ this.setState({ showConfirmReset: true });
+ };
+
+ cancelReset = () => {
+ this.setState({ showConfirmReset: false });
+ };
+
+ confirmReset = async () => {
+ this.setState({ showConfirmReset: false, resetting: true, resetError: null });
+ try {
+ await this.props.httpClient.post('/api/v1/service/matter/reset');
+ this.setState({
+ resetting: false,
+ matterEnabled: false,
+ nodes: []
+ });
+ } catch (e) {
+ console.error(e);
+ this.setState({ resetting: false, resetError: true });
+ }
+ };
+
render() {
const {
matterEnabled,
@@ -330,7 +356,10 @@ class MatterSettingsPage extends Component {
decommissioningNodes,
collapsedDevices,
visibleKeys,
- hasIpv6
+ hasIpv6,
+ showConfirmReset,
+ resetting,
+ resetError
} = this.state;
return (
@@ -464,6 +493,47 @@ class MatterSettingsPage extends Component {
+
+
+
+
+
+
+
+
+
+ {resetError && (
+
+
+
+ )}
+
+ {!showConfirmReset && (
+
+ )}
+
+ {showConfirmReset && (
+
+ )}
+
>
)}
diff --git a/server/lib/device/device.newStateEvent.js b/server/lib/device/device.newStateEvent.js
index 341403385a..0738ef1548 100644
--- a/server/lib/device/device.newStateEvent.js
+++ b/server/lib/device/device.newStateEvent.js
@@ -1,4 +1,3 @@
-const { NotFoundError } = require('../../utils/coreErrors');
const { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../utils/constants');
const logger = require('../../utils/logger');
@@ -15,11 +14,15 @@ const logger = require('../../utils/logger');
async function newStateEvent(event) {
const deviceFeature = this.stateManager.get('deviceFeatureByExternalId', event.device_feature_external_id);
if (deviceFeature === null) {
- throw new NotFoundError(`DeviceFeature ${event.device_feature_external_id} not found`);
+ logger.info(
+ `DeviceFeature "${event.device_feature_external_id}" not found (or not added to Gladys), skipping state update.`,
+ );
+ return;
}
const device = this.stateManager.get('deviceById', deviceFeature.device_id);
if (device === null) {
- throw new NotFoundError(`Device ${deviceFeature.device_id} not found`);
+ logger.info(`Device "${deviceFeature.device_id}" not found, skipping state update.`);
+ return;
}
try {
// If there is a "text" property in the even, we save as string
diff --git a/server/package-lock.json b/server/package-lock.json
index 55ef046d84..82491feb4a 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -53,7 +53,7 @@
"ws": "^6.2.2"
},
"devDependencies": {
- "@matter/main": "^0.13.0",
+ "@matter/main": "^0.16.11",
"apidoc": "^1.0.3",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
@@ -1062,93 +1062,93 @@
}
},
"node_modules/@matter/general": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.13.0.tgz",
- "integrity": "sha512-PZ+FVJotKWgtoBvorqN+PLCuTBBbTCJTCss2P5C6n9r/ZAIcCgW7LDTFmRXNNNMJEri8JUVR0REvHaa92zVj2Q==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.16.11.tgz",
+ "integrity": "sha512-iOnCR7azYgRzr4p/WQRRmbediZFYsqfaqSIp7yyGhnfn2gx386F/Wh96HGXYWdprTWet/7IsFV6uiA9jcDNHvA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@noble/curves": "^1.8.2"
+ "@noble/curves": "^2.0.1"
}
},
"node_modules/@matter/main": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.13.0.tgz",
- "integrity": "sha512-Qu60G05f821bEtp2yU+rJ7Xpe66GHDn9NdSPGrAn1EJH/iWplmVeLS1nSXzyGE28iPVPYNLcMX0edY/FejAhmQ==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.16.11.tgz",
+ "integrity": "sha512-FDFhTix4AcH7t7TP7PnU2smFayza2RrI3uppSRzEmJ+Vxy8btelJMpDjBcxBbNb2Iao7jX7W8DLPMMuH6AzMQg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/node": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/node": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
},
"optionalDependencies": {
- "@matter/nodejs": "0.13.0"
+ "@matter/nodejs": "0.16.11"
}
},
"node_modules/@matter/model": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.13.0.tgz",
- "integrity": "sha512-rcXu7OdMctlOGVOkClH6/+11ct6FMDSK2/Yu05+J7BJfohJY5mQbo0jnz8l0FPbwRrk5ADbqfAJ5jh/1OKHR2Q==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.16.11.tgz",
+ "integrity": "sha512-7a64fUnf3EFwfqt5C/p4aR9RrQBWgNYnP/FKPAI4wC6cp3OyOm9Yt7fqE//Q6ikPpU867kMLxhMHwmgvntMgtA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0"
+ "@matter/general": "0.16.11"
}
},
"node_modules/@matter/node": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.13.0.tgz",
- "integrity": "sha512-HQkzpRnhAUfj9u2ijkT4qgyFzEfaNqyAxh+dgMpLKnbbGQoHTskJ/wEgkRRI7B0Q57mr6VJ5KgXYdbXUYA7xFA==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.16.11.tgz",
+ "integrity": "sha512-Y+ji+A8iWRCaG2HI7yXlvgOizwePIt3lSZV+wVo+Pyf86vwtDLxxY225nfRcV/Iwawmm/l2WM8aoPaNh37C0iw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"node_modules/@matter/nodejs": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.13.0.tgz",
- "integrity": "sha512-ThWrLZJo7UH4Ebanf3gxkhe2p8vq4X3PxyRG+ICDRGPfzSAJZ3znh2hD/zdvcdyzQlSoXeLpFbLpTkJu7aRMHg==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.16.11.tgz",
+ "integrity": "sha512-Q/kOWerSnHHUI5A/FnD6Tz61asO0C4Rz/hemF3L7DAWu4nEEq6O2msH+RTaj3NrHK9ef28PGpTMS35PbMpFOyg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/node": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/node": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
},
"engines": {
- "node": ">=18.0.0"
+ "node": ">=20.19.0 <22.0.0 || >=22.13.0"
}
},
"node_modules/@matter/protocol": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.13.0.tgz",
- "integrity": "sha512-wFxU6+LG5ygzruOV5JF30hmLkk88hz8PqAMkQ6ow81ZvoDFBevsW0OyqxXMCNwYd/zmGXmvr3mpC0mi9/troHg==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.16.11.tgz",
+ "integrity": "sha512-+Q6s+Cmvcm5BJEFBtS6m0vUVrHdxTBteDcJ+VfpNna1ST910371lVqtDwYFclqECQMlDYxoNjkfcYNv4pZfNPg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"node_modules/@matter/types": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.13.0.tgz",
- "integrity": "sha512-8mH7hRC4MBSy4KUs8zb6uDTr0MfRYGtGBFq9t2uedlsiHA5lMUP3L1fitszoeCbpJh4EeqKHWSvF6RxWzKNueA==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.16.11.tgz",
+ "integrity": "sha512-RcZk5N4AeoqKaLzlC6M6+J8YGJgxnLYpF/3WL7CeIM1DpA9ebkPQB9B8of+RJnqy8kgr6eqL/3ATmPGDA46fwg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11"
}
},
"node_modules/@microsoft/recognizers-text": {
@@ -1776,29 +1776,29 @@
}
},
"node_modules/@noble/curves": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz",
- "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz",
+ "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@noble/hashes": "1.8.0"
+ "@noble/hashes": "2.0.1"
},
"engines": {
- "node": "^14.21.3 || >=16"
+ "node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
- "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
+ "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": "^14.21.3 || >=16"
+ "node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
@@ -13146,81 +13146,81 @@
}
},
"@matter/general": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.13.0.tgz",
- "integrity": "sha512-PZ+FVJotKWgtoBvorqN+PLCuTBBbTCJTCss2P5C6n9r/ZAIcCgW7LDTFmRXNNNMJEri8JUVR0REvHaa92zVj2Q==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.16.11.tgz",
+ "integrity": "sha512-iOnCR7azYgRzr4p/WQRRmbediZFYsqfaqSIp7yyGhnfn2gx386F/Wh96HGXYWdprTWet/7IsFV6uiA9jcDNHvA==",
"dev": true,
"requires": {
- "@noble/curves": "^1.8.2"
+ "@noble/curves": "^2.0.1"
}
},
"@matter/main": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.13.0.tgz",
- "integrity": "sha512-Qu60G05f821bEtp2yU+rJ7Xpe66GHDn9NdSPGrAn1EJH/iWplmVeLS1nSXzyGE28iPVPYNLcMX0edY/FejAhmQ==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.16.11.tgz",
+ "integrity": "sha512-FDFhTix4AcH7t7TP7PnU2smFayza2RrI3uppSRzEmJ+Vxy8btelJMpDjBcxBbNb2Iao7jX7W8DLPMMuH6AzMQg==",
"dev": true,
"requires": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/node": "0.13.0",
- "@matter/nodejs": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/node": "0.16.11",
+ "@matter/nodejs": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"@matter/model": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.13.0.tgz",
- "integrity": "sha512-rcXu7OdMctlOGVOkClH6/+11ct6FMDSK2/Yu05+J7BJfohJY5mQbo0jnz8l0FPbwRrk5ADbqfAJ5jh/1OKHR2Q==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.16.11.tgz",
+ "integrity": "sha512-7a64fUnf3EFwfqt5C/p4aR9RrQBWgNYnP/FKPAI4wC6cp3OyOm9Yt7fqE//Q6ikPpU867kMLxhMHwmgvntMgtA==",
"dev": true,
"requires": {
- "@matter/general": "0.13.0"
+ "@matter/general": "0.16.11"
}
},
"@matter/node": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.13.0.tgz",
- "integrity": "sha512-HQkzpRnhAUfj9u2ijkT4qgyFzEfaNqyAxh+dgMpLKnbbGQoHTskJ/wEgkRRI7B0Q57mr6VJ5KgXYdbXUYA7xFA==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.16.11.tgz",
+ "integrity": "sha512-Y+ji+A8iWRCaG2HI7yXlvgOizwePIt3lSZV+wVo+Pyf86vwtDLxxY225nfRcV/Iwawmm/l2WM8aoPaNh37C0iw==",
"dev": true,
"requires": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"@matter/nodejs": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.13.0.tgz",
- "integrity": "sha512-ThWrLZJo7UH4Ebanf3gxkhe2p8vq4X3PxyRG+ICDRGPfzSAJZ3znh2hD/zdvcdyzQlSoXeLpFbLpTkJu7aRMHg==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.16.11.tgz",
+ "integrity": "sha512-Q/kOWerSnHHUI5A/FnD6Tz61asO0C4Rz/hemF3L7DAWu4nEEq6O2msH+RTaj3NrHK9ef28PGpTMS35PbMpFOyg==",
"dev": true,
"optional": true,
"requires": {
- "@matter/general": "0.13.0",
- "@matter/node": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/node": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"@matter/protocol": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.13.0.tgz",
- "integrity": "sha512-wFxU6+LG5ygzruOV5JF30hmLkk88hz8PqAMkQ6ow81ZvoDFBevsW0OyqxXMCNwYd/zmGXmvr3mpC0mi9/troHg==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.16.11.tgz",
+ "integrity": "sha512-+Q6s+Cmvcm5BJEFBtS6m0vUVrHdxTBteDcJ+VfpNna1ST910371lVqtDwYFclqECQMlDYxoNjkfcYNv4pZfNPg==",
"dev": true,
"requires": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"@matter/types": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.13.0.tgz",
- "integrity": "sha512-8mH7hRC4MBSy4KUs8zb6uDTr0MfRYGtGBFq9t2uedlsiHA5lMUP3L1fitszoeCbpJh4EeqKHWSvF6RxWzKNueA==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.16.11.tgz",
+ "integrity": "sha512-RcZk5N4AeoqKaLzlC6M6+J8YGJgxnLYpF/3WL7CeIM1DpA9ebkPQB9B8of+RJnqy8kgr6eqL/3ATmPGDA46fwg==",
"dev": true,
"requires": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11"
}
},
"@microsoft/recognizers-text": {
@@ -13824,18 +13824,18 @@
}
},
"@noble/curves": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz",
- "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz",
+ "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==",
"dev": true,
"requires": {
- "@noble/hashes": "1.8.0"
+ "@noble/hashes": "2.0.1"
}
},
"@noble/hashes": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
- "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
+ "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
"dev": true
},
"@nodelib/fs.scandir": {
diff --git a/server/package.json b/server/package.json
index 594741446b..65ff7411a6 100644
--- a/server/package.json
+++ b/server/package.json
@@ -46,7 +46,7 @@
]
},
"devDependencies": {
- "@matter/main": "^0.13.0",
+ "@matter/main": "^0.16.11",
"apidoc": "^1.0.3",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
diff --git a/server/services/matter/api/matter.controller.js b/server/services/matter/api/matter.controller.js
index eb172f0a83..2413ce853f 100644
--- a/server/services/matter/api/matter.controller.js
+++ b/server/services/matter/api/matter.controller.js
@@ -59,6 +59,16 @@ module.exports = function MatterController(matterHandler) {
res.json({ success: true });
}
+ /**
+ * @api {post} /api/v1/service/matter/reset Reset Matter integration
+ * @apiName reset
+ * @apiGroup Matter
+ */
+ async function reset(req, res) {
+ await matterHandler.reset();
+ res.json({ success: true });
+ }
+
return {
'post /api/v1/service/matter/pair-device': {
authenticated: true,
@@ -80,5 +90,10 @@ module.exports = function MatterController(matterHandler) {
authenticated: true,
controller: asyncMiddleware(decommissionNode),
},
+ 'post /api/v1/service/matter/reset': {
+ authenticated: true,
+ admin: true,
+ controller: asyncMiddleware(reset),
+ },
};
};
diff --git a/server/services/matter/lib/index.js b/server/services/matter/lib/index.js
index 676b18ee50..08428f7d62 100644
--- a/server/services/matter/lib/index.js
+++ b/server/services/matter/lib/index.js
@@ -14,6 +14,7 @@ const { checkIpv6 } = require('./matter.checkIpv6');
const { refreshDevices } = require('./matter.refreshDevices');
const { backupController } = require('./matter.backupController');
const { restoreBackup } = require('./matter.restoreBackup');
+const { reset } = require('./matter.reset');
/**
* @description Matter handler.
@@ -52,5 +53,6 @@ MatterHandler.prototype.checkIpv6 = checkIpv6;
MatterHandler.prototype.refreshDevices = refreshDevices;
MatterHandler.prototype.backupController = backupController;
MatterHandler.prototype.restoreBackup = restoreBackup;
+MatterHandler.prototype.reset = reset;
module.exports = MatterHandler;
diff --git a/server/services/matter/lib/matter.getNodes.js b/server/services/matter/lib/matter.getNodes.js
index 5ec7a37a83..bdbe3c9591 100644
--- a/server/services/matter/lib/matter.getNodes.js
+++ b/server/services/matter/lib/matter.getNodes.js
@@ -5,7 +5,8 @@ const convertDevice = (device) => {
const clusterClients = [];
// Each cluster client is a feature of the device
- device.clusterClients.forEach((clusterClient, clusterIndex) => {
+ const allClusterClients = device.getAllClusterClients();
+ allClusterClients.forEach((clusterClient) => {
clusterClients.push({
id: clusterClient.id.toString(),
name: clusterClient.name,
@@ -16,7 +17,8 @@ const convertDevice = (device) => {
});
// Convert child endpoints (child devices)
- const childEndpoints = device.childEndpoints.map((childDeviceEndpoint) => {
+ const childEndpointsList = device.getChildEndpoints();
+ const childEndpoints = childEndpointsList.map((childDeviceEndpoint) => {
return convertDevice(childDeviceEndpoint);
});
@@ -37,8 +39,8 @@ const convertDevice = (device) => {
async function getNodes() {
const nodeDetails = this.commissioningController.getCommissionedNodesDetails();
const filteredNodeDetails = nodeDetails.filter((nodeDetail) => {
- if (!nodeDetail.deviceData) {
- logger.warn(`Matter: Node ${nodeDetail.nodeId} has no device data`);
+ if (!nodeDetail.deviceData || !nodeDetail.deviceData.basicInformation) {
+ logger.warn(`Matter: Node ${nodeDetail.nodeId} has no device data or basic information`);
return false;
}
return true;
diff --git a/server/services/matter/lib/matter.handleNode.js b/server/services/matter/lib/matter.handleNode.js
index 85cc866658..806fc3834d 100644
--- a/server/services/matter/lib/matter.handleNode.js
+++ b/server/services/matter/lib/matter.handleNode.js
@@ -26,7 +26,7 @@ const handleDevice = async (
};
// If we have this cluster, it means we are in a bridge device
- const bridgedDeviceBasicInformationClusterClient = device.clusterClients.get(
+ const bridgedDeviceBasicInformationClusterClient = device.getClusterClientById(
BridgedDeviceBasicInformation.Complete.id,
);
@@ -82,8 +82,9 @@ const handleDevice = async (
devices.push(gladysDevice);
}
- if (device.childEndpoints) {
- await Promise.each(device.childEndpoints, async (childDevice, index) => {
+ const childEndpoints = device.getChildEndpoints();
+ if (childEndpoints && childEndpoints.length > 0) {
+ await Promise.each(childEndpoints, async (childDevice, index) => {
await handleDevice(
nodeId,
childInformations,
diff --git a/server/services/matter/lib/matter.init.js b/server/services/matter/lib/matter.init.js
index 0b566e76a0..6f150c2b27 100644
--- a/server/services/matter/lib/matter.init.js
+++ b/server/services/matter/lib/matter.init.js
@@ -35,7 +35,7 @@ async function init() {
// Set the log level to "notice"
// Log levels are defined here:
// https://github.com/project-chip/matter.js/blob/b0ffc2ff3c8acd7fef19918337d4fd95dfa466e6/packages/general/src/log/LogLevel.ts
- Logger.level = LogLevel('notice');
+ Logger.level = LogLevel('debug');
const storageService = environment.get(StorageService);
storageService.location = storagePath;
@@ -45,14 +45,19 @@ async function init() {
environment,
id: 'matter-controller-data',
},
- autoConnect: true,
+ // Set autoConnect to undefined to fix matter-js bug (https://github.com/matter-js/matter.js/pull/3436)
+ // Once the fix is live in matter-js, we can switch back to true
+ autoConnect: undefined,
adminFabricLabel: 'Gladys Assistant',
- storage: storageService,
});
await this.commissioningController.start();
logger.info('Matter controller started');
- await this.refreshDevices();
+ // Refresh devices in the background to avoid blocking Gladys startup
+ // Store the promise so it can be awaited in tests
+ this.refreshDevicesPromise = this.refreshDevices().catch((err) => {
+ logger.error('Matter: Error refreshing devices in background:', err);
+ });
// Schedule reccurent job if not already scheduled
if (!this.backupScheduledJob) {
this.backupScheduledJob = this.gladys.scheduler.scheduleJob('0 0 4 * * *', () => this.backupController());
diff --git a/server/services/matter/lib/matter.listenToStateChange.js b/server/services/matter/lib/matter.listenToStateChange.js
index 99b34f395c..3681c2e4cf 100644
--- a/server/services/matter/lib/matter.listenToStateChange.js
+++ b/server/services/matter/lib/matter.listenToStateChange.js
@@ -32,8 +32,8 @@ const { EVENTS, STATE, BUTTON_STATUS } = require('../../../utils/constants');
* @example matter.listenToStateChange(nodeId, device);
*/
async function listenToStateChange(nodeId, devicePath, device) {
- // Get the OnOff cluster from clusterClients map
- const onOff = device.clusterClients.get(OnOff.Complete.id);
+ // Get the OnOff cluster
+ const onOff = device.getClusterClientById(OnOff.Complete.id);
// We only add the listener if it's not already added
if (onOff && !this.stateChangeListeners.has(onOff)) {
@@ -49,7 +49,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const booleanState = device.clusterClients.get(BooleanState.Complete.id);
+ const booleanState = device.getClusterClientById(BooleanState.Complete.id);
if (booleanState && !this.stateChangeListeners.has(booleanState)) {
logger.debug(`Matter: Adding state change listener for BooleanState cluster ${booleanState.name}`);
this.stateChangeListeners.add(booleanState);
@@ -62,7 +62,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const switchCluster = device.clusterClients.get(Switch.Complete.id);
+ const switchCluster = device.getClusterClientById(Switch.Complete.id);
if (switchCluster && !this.stateChangeListeners.has(switchCluster)) {
logger.debug(`Matter: Adding state change listener for Switch cluster ${switchCluster.name}`);
this.stateChangeListeners.add(switchCluster);
@@ -104,7 +104,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
}
}
- const occupancy = device.clusterClients.get(OccupancySensing.Complete.id);
+ const occupancy = device.getClusterClientById(OccupancySensing.Complete.id);
if (occupancy && !this.stateChangeListeners.has(occupancy)) {
logger.debug(`Matter: Adding state change listener for OccupancySensing cluster ${occupancy.name}`);
this.stateChangeListeners.add(occupancy);
@@ -118,7 +118,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const illuminance = device.clusterClients.get(IlluminanceMeasurement.Complete.id);
+ const illuminance = device.getClusterClientById(IlluminanceMeasurement.Complete.id);
if (illuminance && !this.stateChangeListeners.has(illuminance)) {
logger.debug(`Matter: Adding state change listener for IlluminanceMeasurement cluster ${illuminance.name}`);
this.stateChangeListeners.add(illuminance);
@@ -133,7 +133,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const temperatureSensor = device.clusterClients.get(TemperatureMeasurement.Complete.id);
+ const temperatureSensor = device.getClusterClientById(TemperatureMeasurement.Complete.id);
if (temperatureSensor && !this.stateChangeListeners.has(temperatureSensor)) {
logger.debug(`Matter: Adding state change listener for TemperatureMeasurement cluster ${temperatureSensor.name}`);
this.stateChangeListeners.add(temperatureSensor);
@@ -147,7 +147,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const windowCover = device.clusterClients.get(WindowCovering.Complete.id);
+ const windowCover = device.getClusterClientById(WindowCovering.Complete.id);
if (windowCover && !this.stateChangeListeners.has(windowCover)) {
logger.debug(`Matter: Adding state change listener for WindowCovering cluster ${windowCover.name}`);
@@ -162,7 +162,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const levelControl = device.clusterClients.get(LevelControl.Complete.id);
+ const levelControl = device.getClusterClientById(LevelControl.Complete.id);
if (levelControl && !this.stateChangeListeners.has(levelControl)) {
logger.debug(`Matter: Adding state change listener for LevelControl cluster ${levelControl.name}`);
this.stateChangeListeners.add(levelControl);
@@ -176,7 +176,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const colorControl = device.clusterClients.get(ColorControl.Complete.id);
+ const colorControl = device.getClusterClientById(ColorControl.Complete.id);
if (colorControl && !this.stateChangeListeners.has(colorControl)) {
logger.debug(`Matter: Adding state change listener for ColorControl cluster ${colorControl.name}`);
this.stateChangeListeners.add(colorControl);
@@ -220,7 +220,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
}
}
- const relativeHumidityMeasurement = device.clusterClients.get(RelativeHumidityMeasurement.Complete.id);
+ const relativeHumidityMeasurement = device.getClusterClientById(RelativeHumidityMeasurement.Complete.id);
if (relativeHumidityMeasurement && !this.stateChangeListeners.has(relativeHumidityMeasurement)) {
logger.debug(
`Matter: Adding state change listener for RelativeHumidityMeasurement cluster ${relativeHumidityMeasurement.name}`,
@@ -236,7 +236,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const pm25ConcentrationMeasurement = device.clusterClients.get(Pm25ConcentrationMeasurement.Complete.id);
+ const pm25ConcentrationMeasurement = device.getClusterClientById(Pm25ConcentrationMeasurement.Complete.id);
if (pm25ConcentrationMeasurement && !this.stateChangeListeners.has(pm25ConcentrationMeasurement)) {
logger.debug(
`Matter: Adding state change listener for Pm25ConcentrationMeasurement cluster ${pm25ConcentrationMeasurement.name}`,
@@ -252,7 +252,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const pm10ConcentrationMeasurement = device.clusterClients.get(Pm10ConcentrationMeasurement.Complete.id);
+ const pm10ConcentrationMeasurement = device.getClusterClientById(Pm10ConcentrationMeasurement.Complete.id);
if (pm10ConcentrationMeasurement && !this.stateChangeListeners.has(pm10ConcentrationMeasurement)) {
logger.debug(
`Matter: Adding state change listener for Pm10ConcentrationMeasurement cluster ${pm10ConcentrationMeasurement.name}`,
@@ -268,7 +268,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const totalVolatileOrganicCompoundsConcentrationMeasurement = device.clusterClients.get(
+ const totalVolatileOrganicCompoundsConcentrationMeasurement = device.getClusterClientById(
TotalVolatileOrganicCompoundsConcentrationMeasurement.Complete.id,
);
if (
@@ -289,7 +289,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const formaldehydeConcentrationMeasurement = device.clusterClients.get(
+ const formaldehydeConcentrationMeasurement = device.getClusterClientById(
FormaldehydeConcentrationMeasurement.Complete.id,
);
if (formaldehydeConcentrationMeasurement && !this.stateChangeListeners.has(formaldehydeConcentrationMeasurement)) {
@@ -307,7 +307,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
});
}
- const thermostat = device.clusterClients.get(Thermostat.Complete.id);
+ const thermostat = device.getClusterClientById(Thermostat.Complete.id);
if (thermostat && !this.stateChangeListeners.has(thermostat)) {
logger.debug(`Matter: Adding state change listener for Thermostat cluster ${thermostat.name}`);
this.stateChangeListeners.add(thermostat);
@@ -332,7 +332,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
}
}
- const electricalPowerMeasurement = device.clusterClients.get(ElectricalPowerMeasurement.Complete.id);
+ const electricalPowerMeasurement = device.getClusterClientById(ElectricalPowerMeasurement.Complete.id);
if (electricalPowerMeasurement && !this.stateChangeListeners.has(electricalPowerMeasurement)) {
logger.debug(
`Matter: Adding state change listener for ElectricalPowerMeasurement cluster ${electricalPowerMeasurement.name}`,
@@ -374,7 +374,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
}
}
- const electricalEnergyMeasurement = device.clusterClients.get(ElectricalEnergyMeasurement.Complete.id);
+ const electricalEnergyMeasurement = device.getClusterClientById(ElectricalEnergyMeasurement.Complete.id);
if (electricalEnergyMeasurement && !this.stateChangeListeners.has(electricalEnergyMeasurement)) {
logger.debug(
`Matter: Adding state change listener for ElectricalEnergyMeasurement cluster ${electricalEnergyMeasurement.name}`,
@@ -395,7 +395,7 @@ async function listenToStateChange(nodeId, devicePath, device) {
}
}
- const hepaFilterMonitoring = device.clusterClients.get(HepaFilterMonitoring.Complete.id);
+ const hepaFilterMonitoring = device.getClusterClientById(HepaFilterMonitoring.Complete.id);
if (hepaFilterMonitoring && !this.stateChangeListeners.has(hepaFilterMonitoring)) {
logger.debug(`Matter: Adding state change listener for HepaFilterMonitoring cluster ${hepaFilterMonitoring.name}`);
this.stateChangeListeners.add(hepaFilterMonitoring);
diff --git a/server/services/matter/lib/matter.refreshDevices.js b/server/services/matter/lib/matter.refreshDevices.js
index 005e37b584..e875e410bc 100644
--- a/server/services/matter/lib/matter.refreshDevices.js
+++ b/server/services/matter/lib/matter.refreshDevices.js
@@ -1,5 +1,7 @@
const Promise = require('bluebird');
+const logger = require('../../../utils/logger');
+
/**
* @description Refresh the devices.
* @example matter.refreshDevices();
@@ -10,7 +12,12 @@ async function refreshDevices() {
const nodeDetails = this.commissioningController.getCommissionedNodesDetails();
await Promise.each(nodeDetails, async (nodeDetail) => {
- await this.handleNode(nodeDetail);
+ try {
+ await this.handleNode(nodeDetail);
+ } catch (err) {
+ // Log error but continue with other devices - one unreachable device shouldn't break the others
+ logger.warn(`Matter: Error handling node ${nodeDetail.nodeId}: ${err.message}`);
+ }
});
}
diff --git a/server/services/matter/lib/matter.reset.js b/server/services/matter/lib/matter.reset.js
new file mode 100644
index 0000000000..92b492f8c1
--- /dev/null
+++ b/server/services/matter/lib/matter.reset.js
@@ -0,0 +1,43 @@
+const path = require('path');
+const fse = require('fs-extra');
+
+const logger = require('../../../utils/logger');
+const { VARIABLES } = require('../utils/constants');
+
+/**
+ * @description Reset the Matter integration to factory defaults.
+ * This will delete all controller data, backup, and matter folder.
+ * @example
+ * await matter.reset();
+ */
+async function reset() {
+ logger.info('Matter: resetting integration...');
+
+ // 1. Stop the Matter controller
+ await this.stop();
+
+ // 2. Delete the Matter backup variable from database
+ await this.gladys.variable.destroy(VARIABLES.MATTER_BACKUP, this.serviceId);
+ logger.info('Matter: backup variable deleted');
+
+ // 3. Set MATTER_ENABLED to false
+ await this.gladys.variable.setValue(VARIABLES.MATTER_ENABLED, 'false', this.serviceId);
+ logger.info('Matter: MATTER_ENABLED set to false');
+
+ // 4. Delete the Matter folder on disk
+ const storagePath = process.env.MATTER_FOLDER_PATH || path.join(path.dirname(this.gladys.config.storage), 'matter');
+ await fse.remove(storagePath);
+ logger.info('Matter: folder deleted successfully');
+
+ // 5. Reset in-memory state
+ this.commissioningController = null;
+ this.devices = [];
+ this.nodesMap = new Map();
+ this.stateChangeListeners = new Set();
+
+ logger.info('Matter: integration reset complete');
+}
+
+module.exports = {
+ reset,
+};
diff --git a/server/services/matter/lib/matter.setValue.js b/server/services/matter/lib/matter.setValue.js
index 287bb95d03..0e8567dd9f 100644
--- a/server/services/matter/lib/matter.setValue.js
+++ b/server/services/matter/lib/matter.setValue.js
@@ -27,8 +27,9 @@ function findDeviceRecursively(parentDevice, path) {
const deviceNumber = Number(currentNumber);
// Look in child endpoints
- if (parentDevice.childEndpoints) {
- const childDevice = parentDevice.childEndpoints.find((child) => child.number === deviceNumber);
+ const childEndpoints = parentDevice.getChildEndpoints();
+ if (childEndpoints && childEndpoints.length > 0) {
+ const childDevice = childEndpoints.find((child) => child.number === deviceNumber);
if (childDevice) {
return findDeviceRecursively(childDevice, remainingPath);
}
@@ -85,7 +86,7 @@ async function setValue(gladysDevice, gladysFeature, value) {
// Handle binary device
if (gladysFeature.type === DEVICE_FEATURE_TYPES.SWITCH.BINARY) {
- const onOff = targetDevice.clusterClients.get(OnOff.Complete.id);
+ const onOff = targetDevice.getClusterClientById(OnOff.Complete.id);
if (!onOff) {
throw new Error('Device does not support OnOff cluster');
@@ -101,7 +102,7 @@ async function setValue(gladysDevice, gladysFeature, value) {
// Handle shutters
if (gladysFeature.category === DEVICE_FEATURE_CATEGORIES.SHUTTER) {
- const windowCovering = targetDevice.clusterClients.get(WindowCovering.Complete.id);
+ const windowCovering = targetDevice.getClusterClientById(WindowCovering.Complete.id);
// Handle device feature shutter position
if (gladysFeature.type === DEVICE_FEATURE_TYPES.SHUTTER.POSITION) {
await windowCovering.goToLiftPercentage({
@@ -125,8 +126,8 @@ async function setValue(gladysDevice, gladysFeature, value) {
gladysFeature.category === DEVICE_FEATURE_CATEGORIES.LIGHT &&
gladysFeature.type === DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS
) {
- const levelControl = targetDevice.clusterClients.get(LevelControl.Complete.id);
- const onOff = targetDevice.clusterClients.get(OnOff.Complete.id);
+ const levelControl = targetDevice.getClusterClientById(LevelControl.Complete.id);
+ const onOff = targetDevice.getClusterClientById(OnOff.Complete.id);
await levelControl.moveToLevel({
level: value,
transitionTime: null,
@@ -147,8 +148,8 @@ async function setValue(gladysDevice, gladysFeature, value) {
gladysFeature.category === DEVICE_FEATURE_CATEGORIES.LIGHT &&
gladysFeature.type === DEVICE_FEATURE_TYPES.LIGHT.COLOR
) {
- const colorControl = targetDevice.clusterClients.get(ColorControl.Complete.id);
- const onOff = targetDevice.clusterClients.get(OnOff.Complete.id);
+ const colorControl = targetDevice.getClusterClientById(ColorControl.Complete.id);
+ const onOff = targetDevice.getClusterClientById(OnOff.Complete.id);
const [hue, saturation] = intToHsb(value);
// Convert from standard HSB ranges to Matter ranges
@@ -173,7 +174,7 @@ async function setValue(gladysDevice, gladysFeature, value) {
gladysFeature.category === DEVICE_FEATURE_CATEGORIES.THERMOSTAT &&
gladysFeature.type === DEVICE_FEATURE_TYPES.THERMOSTAT.TARGET_TEMPERATURE
) {
- const thermostat = targetDevice.clusterClients.get(Thermostat.Complete.id);
+ const thermostat = targetDevice.getClusterClientById(Thermostat.Complete.id);
await thermostat.setOccupiedHeatingSetpointAttribute(value * 100);
}
@@ -182,7 +183,7 @@ async function setValue(gladysDevice, gladysFeature, value) {
gladysFeature.category === DEVICE_FEATURE_CATEGORIES.AIR_CONDITIONING &&
gladysFeature.type === DEVICE_FEATURE_TYPES.AIR_CONDITIONING.TARGET_TEMPERATURE
) {
- const thermostat = targetDevice.clusterClients.get(Thermostat.Complete.id);
+ const thermostat = targetDevice.getClusterClientById(Thermostat.Complete.id);
await thermostat.setOccupiedCoolingSetpointAttribute(value * 100);
}
}
diff --git a/server/services/matter/package-lock.json b/server/services/matter/package-lock.json
index 328c3bd4e0..6402b1532b 100644
--- a/server/services/matter/package-lock.json
+++ b/server/services/matter/package-lock.json
@@ -16,133 +16,133 @@
"win32"
],
"dependencies": {
- "@matter/main": "^0.13.0",
- "@project-chip/matter.js": "^0.13.0",
+ "@matter/main": "^0.16.11",
+ "@project-chip/matter.js": "^0.16.11",
"bluebird": "^3.7.2",
"fs-extra": "^11.3.0"
}
},
"node_modules/@matter/general": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.13.0.tgz",
- "integrity": "sha512-PZ+FVJotKWgtoBvorqN+PLCuTBBbTCJTCss2P5C6n9r/ZAIcCgW7LDTFmRXNNNMJEri8JUVR0REvHaa92zVj2Q==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.16.11.tgz",
+ "integrity": "sha512-iOnCR7azYgRzr4p/WQRRmbediZFYsqfaqSIp7yyGhnfn2gx386F/Wh96HGXYWdprTWet/7IsFV6uiA9jcDNHvA==",
"license": "Apache-2.0",
"dependencies": {
- "@noble/curves": "^1.8.2"
+ "@noble/curves": "^2.0.1"
}
},
"node_modules/@matter/main": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.13.0.tgz",
- "integrity": "sha512-Qu60G05f821bEtp2yU+rJ7Xpe66GHDn9NdSPGrAn1EJH/iWplmVeLS1nSXzyGE28iPVPYNLcMX0edY/FejAhmQ==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/main/-/main-0.16.11.tgz",
+ "integrity": "sha512-FDFhTix4AcH7t7TP7PnU2smFayza2RrI3uppSRzEmJ+Vxy8btelJMpDjBcxBbNb2Iao7jX7W8DLPMMuH6AzMQg==",
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/node": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/node": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
},
"optionalDependencies": {
- "@matter/nodejs": "0.13.0"
+ "@matter/nodejs": "0.16.11"
}
},
"node_modules/@matter/model": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.13.0.tgz",
- "integrity": "sha512-rcXu7OdMctlOGVOkClH6/+11ct6FMDSK2/Yu05+J7BJfohJY5mQbo0jnz8l0FPbwRrk5ADbqfAJ5jh/1OKHR2Q==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.16.11.tgz",
+ "integrity": "sha512-7a64fUnf3EFwfqt5C/p4aR9RrQBWgNYnP/FKPAI4wC6cp3OyOm9Yt7fqE//Q6ikPpU867kMLxhMHwmgvntMgtA==",
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0"
+ "@matter/general": "0.16.11"
}
},
"node_modules/@matter/node": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.13.0.tgz",
- "integrity": "sha512-HQkzpRnhAUfj9u2ijkT4qgyFzEfaNqyAxh+dgMpLKnbbGQoHTskJ/wEgkRRI7B0Q57mr6VJ5KgXYdbXUYA7xFA==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.16.11.tgz",
+ "integrity": "sha512-Y+ji+A8iWRCaG2HI7yXlvgOizwePIt3lSZV+wVo+Pyf86vwtDLxxY225nfRcV/Iwawmm/l2WM8aoPaNh37C0iw==",
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"node_modules/@matter/nodejs": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.13.0.tgz",
- "integrity": "sha512-ThWrLZJo7UH4Ebanf3gxkhe2p8vq4X3PxyRG+ICDRGPfzSAJZ3znh2hD/zdvcdyzQlSoXeLpFbLpTkJu7aRMHg==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.16.11.tgz",
+ "integrity": "sha512-Q/kOWerSnHHUI5A/FnD6Tz61asO0C4Rz/hemF3L7DAWu4nEEq6O2msH+RTaj3NrHK9ef28PGpTMS35PbMpFOyg==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/node": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/node": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
},
"engines": {
- "node": ">=18.0.0"
+ "node": ">=20.19.0 <22.0.0 || >=22.13.0"
}
},
"node_modules/@matter/protocol": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.13.0.tgz",
- "integrity": "sha512-wFxU6+LG5ygzruOV5JF30hmLkk88hz8PqAMkQ6ow81ZvoDFBevsW0OyqxXMCNwYd/zmGXmvr3mpC0mi9/troHg==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.16.11.tgz",
+ "integrity": "sha512-+Q6s+Cmvcm5BJEFBtS6m0vUVrHdxTBteDcJ+VfpNna1ST910371lVqtDwYFclqECQMlDYxoNjkfcYNv4pZfNPg==",
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"node_modules/@matter/types": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.13.0.tgz",
- "integrity": "sha512-8mH7hRC4MBSy4KUs8zb6uDTr0MfRYGtGBFq9t2uedlsiHA5lMUP3L1fitszoeCbpJh4EeqKHWSvF6RxWzKNueA==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.16.11.tgz",
+ "integrity": "sha512-RcZk5N4AeoqKaLzlC6M6+J8YGJgxnLYpF/3WL7CeIM1DpA9ebkPQB9B8of+RJnqy8kgr6eqL/3ATmPGDA46fwg==",
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11"
}
},
"node_modules/@noble/curves": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz",
- "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz",
+ "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==",
"license": "MIT",
"dependencies": {
- "@noble/hashes": "1.8.0"
+ "@noble/hashes": "2.0.1"
},
"engines": {
- "node": "^14.21.3 || >=16"
+ "node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
- "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
+ "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
"license": "MIT",
"engines": {
- "node": "^14.21.3 || >=16"
+ "node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@project-chip/matter.js": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.13.0.tgz",
- "integrity": "sha512-QWmfuDorGBW8dpgYk+3L9V4SdW80PQ4pW90s+ORxtWnpBcJoiP6a5skS7NJuW3kNRSMLtXQmpG+yI8oDXcULSg==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.16.11.tgz",
+ "integrity": "sha512-FAgJuh1uSWY7o+Hn0rVKS7i6+5crv3lGekwp002JoU5joty8jdNqMKUiSfwNtPGdYAkm5aMa7KTOaewHkx5taQ==",
"license": "Apache-2.0",
"dependencies": {
- "@matter/general": "0.13.0",
- "@matter/model": "0.13.0",
- "@matter/node": "0.13.0",
- "@matter/protocol": "0.13.0",
- "@matter/types": "0.13.0"
+ "@matter/general": "0.16.11",
+ "@matter/model": "0.16.11",
+ "@matter/node": "0.16.11",
+ "@matter/protocol": "0.16.11",
+ "@matter/types": "0.16.11"
}
},
"node_modules/bluebird": {
diff --git a/server/services/matter/package.json b/server/services/matter/package.json
index c4965473c9..97647f4661 100644
--- a/server/services/matter/package.json
+++ b/server/services/matter/package.json
@@ -12,8 +12,8 @@
"arm64"
],
"dependencies": {
- "@matter/main": "^0.13.0",
- "@project-chip/matter.js": "^0.13.0",
+ "@matter/main": "^0.16.11",
+ "@project-chip/matter.js": "^0.16.11",
"bluebird": "^3.7.2",
"fs-extra": "^11.3.0"
}
diff --git a/server/services/matter/utils/convertToGladysDevice.js b/server/services/matter/utils/convertToGladysDevice.js
index 7975f66333..2059e1c813 100644
--- a/server/services/matter/utils/convertToGladysDevice.js
+++ b/server/services/matter/utils/convertToGladysDevice.js
@@ -101,8 +101,10 @@ async function convertToGladysDevice(serviceId, nodeId, device, nodeDetailDevice
// Add endpoint number to the name so the user can identify the device
gladysDevice.name += ` ${device.number}`;
- if (device.clusterClients) {
- await Promise.each(Array.from(device.clusterClients.entries()), async ([clusterIndex, clusterClient]) => {
+ const allClusterClients = device.getAllClusterClients();
+ if (allClusterClients && allClusterClients.length > 0) {
+ await Promise.each(allClusterClients, async (clusterClient) => {
+ const clusterIndex = clusterClient.id;
const commonNewFeature = {
name: `${clusterClient.name} - ${clusterClient.endpointId}`,
selector: slugify(`matter-${device.name}-${clusterClient.name}`, true),
diff --git a/server/test/lib/device/device.newStateEvent.test.js b/server/test/lib/device/device.newStateEvent.test.js
index 4fe49897fe..bd9e43a429 100644
--- a/server/test/lib/device/device.newStateEvent.test.js
+++ b/server/test/lib/device/device.newStateEvent.test.js
@@ -8,7 +8,6 @@ const { expect } = require('chai');
const Device = require('../../../lib/device');
const StateManager = require('../../../lib/state');
const Job = require('../../../lib/job');
-const { NotFoundError } = require('../../../utils/coreErrors');
const event = new EventEmitter();
const job = new Job(event);
@@ -184,16 +183,12 @@ describe('Device.newStateEvent', () => {
it('should not save state missing device feature', async () => {
const stateManager = new StateManager(event);
const device = new Device(event, {}, stateManager, {}, {}, {}, job);
- try {
- await device.newStateEvent({
- device_feature_external_id: 'hue:binary:1',
- state: 12,
- created_at: '2019-02-12 07:49:07.556 +00:00',
- });
- assert.fail();
- } catch (e) {
- expect(e).to.be.instanceOf(NotFoundError);
- }
+ // Should not throw, just log and return early
+ await device.newStateEvent({
+ device_feature_external_id: 'hue:binary:1',
+ state: 12,
+ created_at: '2019-02-12 07:49:07.556 +00:00',
+ });
const newDeviceFeature = stateManager.get('deviceFeatureByExternalId', 'hue:binary:1');
// eslint-disable-next-line no-unused-expressions
expect(newDeviceFeature).to.be.null;
@@ -209,16 +204,12 @@ describe('Device.newStateEvent', () => {
};
stateManager.setState('deviceFeatureByExternalId', 'hue:binary:1', currentDeviceFeature);
const device = new Device(event, {}, stateManager, {}, {}, {}, job);
- try {
- await device.newStateEvent({
- device_feature_external_id: 'hue:binary:1',
- state: 12,
- created_at: '2019-02-12 07:49:07.556 +00:00',
- });
- assert.fail();
- } catch (e) {
- expect(e).to.be.instanceOf(NotFoundError);
- }
+ // Should not throw, just log and return early
+ await device.newStateEvent({
+ device_feature_external_id: 'hue:binary:1',
+ state: 12,
+ created_at: '2019-02-12 07:49:07.556 +00:00',
+ });
const newDeviceFeature = stateManager.get('deviceFeatureByExternalId', 'hue:binary:1');
expect(newDeviceFeature).not.to.have.property('last_value');
expect(newDeviceFeature).not.to.have.property('last_value_changed');
diff --git a/server/test/services/matter/api/matter.controller.test.js b/server/test/services/matter/api/matter.controller.test.js
index 0b9c40a403..26fbcc0366 100644
--- a/server/test/services/matter/api/matter.controller.test.js
+++ b/server/test/services/matter/api/matter.controller.test.js
@@ -28,6 +28,7 @@ describe('MatterController', () => {
}),
decommission: fake.resolves(null),
listenToStateChange: fake.resolves(null),
+ reset: fake.resolves(null),
};
controller = MatterController(matterHandler);
sinon.reset();
@@ -122,4 +123,14 @@ describe('MatterController', () => {
assert.calledOnce(matterHandler.decommission);
assert.calledWith(res.json, { success: true });
});
+ it('should reset matter integration', async () => {
+ const req = {};
+ const res = {
+ json: fake.returns(null),
+ };
+
+ await controller['post /api/v1/service/matter/reset'].controller(req, res);
+ assert.calledOnce(matterHandler.reset);
+ assert.calledWith(res.json, { success: true });
+ });
});
diff --git a/server/test/services/matter/lib/convertToGladysDevice.test.js b/server/test/services/matter/lib/convertToGladysDevice.test.js
index a241489ecf..d7983fdb62 100644
--- a/server/test/services/matter/lib/convertToGladysDevice.test.js
+++ b/server/test/services/matter/lib/convertToGladysDevice.test.js
@@ -13,17 +13,17 @@ describe('Matter.convertToGladysDevice', () => {
};
it('should create a read-only binary feature for BooleanState cluster', async () => {
- const clusterClients = new Map();
- clusterClients.set(BooleanState.Complete.id, {
+ const clusterClient = {
id: BooleanState.Complete.id,
name: 'BooleanState',
endpointId: 1,
- });
+ };
const device = {
name: 'Test Device',
number: 1,
- clusterClients,
+ getAllClusterClients: () => [clusterClient],
+ getChildEndpoints: () => [],
};
const gladysDevice = await convertToGladysDevice(serviceId, nodeId, device, basicInformation, '1');
@@ -43,17 +43,17 @@ describe('Matter.convertToGladysDevice', () => {
});
it('should create a button click feature for Switch cluster', async () => {
- const clusterClients = new Map();
- clusterClients.set(Switch.Complete.id, {
+ const clusterClient = {
id: Switch.Complete.id,
name: 'Switch',
endpointId: 2,
- });
+ };
const device = {
name: 'Test Device',
number: 2,
- clusterClients,
+ getAllClusterClients: () => [clusterClient],
+ getChildEndpoints: () => [],
};
const gladysDevice = await convertToGladysDevice(serviceId, nodeId, device, basicInformation, '1:child_endpoint:2');
diff --git a/server/test/services/matter/lib/listenToStateChange.test.js b/server/test/services/matter/lib/listenToStateChange.test.js
index 2e95802e47..f7c093d5f3 100644
--- a/server/test/services/matter/lib/listenToStateChange.test.js
+++ b/server/test/services/matter/lib/listenToStateChange.test.js
@@ -48,15 +48,15 @@ describe('Matter.listenToStateChange', () => {
});
it('should listen to state change (ON)', async () => {
- const clusterClients = new Map();
- clusterClients.set(OnOff.Complete.id, {
+ const clusterClient = {
+ id: OnOff.Complete.id,
addOnOffAttributeListener: (callback) => {
callback(true);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -65,15 +65,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (OFF)', async () => {
- const clusterClients = new Map();
- clusterClients.set(OnOff.Complete.id, {
+ const clusterClient = {
+ id: OnOff.Complete.id,
addOnOffAttributeListener: (callback) => {
callback(false);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -82,15 +82,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change in nested child endpoint (OFF)', async () => {
- const clusterClients = new Map();
- clusterClients.set(OnOff.Complete.id, {
+ const clusterClient = {
+ id: OnOff.Complete.id,
addOnOffAttributeListener: (callback) => {
callback(false);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1:child_endpoint:2', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -99,15 +99,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (BooleanState = true)', async () => {
- const clusterClients = new Map();
- clusterClients.set(BooleanState.Complete.id, {
+ const clusterClient = {
+ id: BooleanState.Complete.id,
addStateValueAttributeListener: (callback) => {
callback(true);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -116,8 +116,8 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to switch events', async () => {
- const clusterClients = new Map();
- clusterClients.set(Switch.Complete.id, {
+ const clusterClient = {
+ id: Switch.Complete.id,
addInitialPressEventListener: (callback) => {
callback();
},
@@ -130,10 +130,10 @@ describe('Matter.listenToStateChange', () => {
addLongReleaseEventListener: (callback) => {
callback();
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -154,15 +154,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (Occupancy = true)', async () => {
- const clusterClients = new Map();
- clusterClients.set(OccupancySensing.Complete.id, {
+ const clusterClient = {
+ id: OccupancySensing.Complete.id,
addOccupancyAttributeListener: (callback) => {
callback({ occupied: true });
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -171,15 +171,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (Occupancy = false)', async () => {
- const clusterClients = new Map();
- clusterClients.set(OccupancySensing.Complete.id, {
+ const clusterClient = {
+ id: OccupancySensing.Complete.id,
addOccupancyAttributeListener: (callback) => {
callback({ occupied: false });
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -188,16 +188,16 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (IlluminanceMeasurement)', async () => {
- const clusterClients = new Map();
// Matter: Illuminance attribute changed to 21327 (Converted to 136 lux)
- clusterClients.set(IlluminanceMeasurement.Complete.id, {
+ const clusterClient = {
+ id: IlluminanceMeasurement.Complete.id,
addMeasuredValueAttributeListener: (callback) => {
callback(21327);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -206,15 +206,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (TemperatureMeasurement)', async () => {
- const clusterClients = new Map();
- clusterClients.set(TemperatureMeasurement.Complete.id, {
+ const clusterClient = {
+ id: TemperatureMeasurement.Complete.id,
addMeasuredValueAttributeListener: (callback) => {
callback(2150);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -223,15 +223,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (RelativeHumidityMeasurement)', async () => {
- const clusterClients = new Map();
- clusterClients.set(RelativeHumidityMeasurement.Complete.id, {
+ const clusterClient = {
+ id: RelativeHumidityMeasurement.Complete.id,
addMeasuredValueAttributeListener: (callback) => {
callback(5000);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -240,15 +240,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (Pm25ConcentrationMeasurement)', async () => {
- const clusterClients = new Map();
- clusterClients.set(Pm25ConcentrationMeasurement.Complete.id, {
+ const clusterClient = {
+ id: Pm25ConcentrationMeasurement.Complete.id,
addMeasuredValueAttributeListener: (callback) => {
callback(100);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -257,15 +257,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (Pm10ConcentrationMeasurement)', async () => {
- const clusterClients = new Map();
- clusterClients.set(Pm10ConcentrationMeasurement.Complete.id, {
+ const clusterClient = {
+ id: Pm10ConcentrationMeasurement.Complete.id,
addMeasuredValueAttributeListener: (callback) => {
callback(100);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -274,15 +274,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (TotalVolatileOrganicCompoundsConcentrationMeasurement)', async () => {
- const clusterClients = new Map();
- clusterClients.set(TotalVolatileOrganicCompoundsConcentrationMeasurement.Complete.id, {
+ const clusterClient = {
+ id: TotalVolatileOrganicCompoundsConcentrationMeasurement.Complete.id,
addLevelValueAttributeListener: (callback) => {
callback(3);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -291,15 +291,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (FormaldehydeConcentrationMeasurement)', async () => {
- const clusterClients = new Map();
- clusterClients.set(FormaldehydeConcentrationMeasurement.Complete.id, {
+ const clusterClient = {
+ id: FormaldehydeConcentrationMeasurement.Complete.id,
addMeasuredValueAttributeListener: (callback) => {
callback(100);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -308,18 +308,18 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (Thermostat heating)', async () => {
- const clusterClients = new Map();
- clusterClients.set(Thermostat.Complete.id, {
+ const clusterClient = {
+ id: Thermostat.Complete.id,
supportedFeatures: {
heating: true,
},
addOccupiedHeatingSetpointAttributeListener: (callback) => {
callback(2000);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -328,18 +328,18 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (Thermostat cooling)', async () => {
- const clusterClients = new Map();
- clusterClients.set(Thermostat.Complete.id, {
+ const clusterClient = {
+ id: Thermostat.Complete.id,
supportedFeatures: {
cooling: true,
},
addOccupiedCoolingSetpointAttributeListener: (callback) => {
callback(2000);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -348,15 +348,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (WindowCovering)', async () => {
- const clusterClients = new Map();
- clusterClients.set(WindowCovering.Complete.id, {
+ const clusterClient = {
+ id: WindowCovering.Complete.id,
addCurrentPositionLiftPercent100thsAttributeListener: (callback) => {
callback(8400);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -365,15 +365,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (LevelControl)', async () => {
- const clusterClients = new Map();
- clusterClients.set(LevelControl.Complete.id, {
+ const clusterClient = {
+ id: LevelControl.Complete.id,
addCurrentLevelAttributeListener: (callback) => {
callback(99);
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -382,7 +382,7 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (ColorControl)', async () => {
- const clusterClients = new Map();
+ let clusterClient;
const promise = new Promise((resolve) => {
let callCount = 0;
const checkThatEveryThingWasCalled = () => {
@@ -391,7 +391,8 @@ describe('Matter.listenToStateChange', () => {
resolve();
}
};
- clusterClients.set(ColorControl.Complete.id, {
+ clusterClient = {
+ id: ColorControl.Complete.id,
supportedFeatures: {
hueSaturation: true,
},
@@ -411,11 +412,11 @@ describe('Matter.listenToStateChange', () => {
checkThatEveryThingWasCalled();
return 40;
},
- });
+ };
});
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
// We need to make sure that we called all 4 functions before checking the events
@@ -431,17 +432,17 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (ElectricalPowerMeasurement - ActivePower)', async () => {
- const clusterClients = new Map();
- clusterClients.set(ElectricalPowerMeasurement.Complete.id, {
+ const clusterClient = {
+ id: ElectricalPowerMeasurement.Complete.id,
addActivePowerAttributeListener: (callback) => {
callback(1500000); // 1500000 mW = 1500 W
},
addVoltageAttributeListener: () => {},
addActiveCurrentAttributeListener: () => {},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -450,8 +451,8 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (ElectricalPowerMeasurement - Voltage)', async () => {
- const clusterClients = new Map();
- clusterClients.set(ElectricalPowerMeasurement.Complete.id, {
+ const clusterClient = {
+ id: ElectricalPowerMeasurement.Complete.id,
supportedFeatures: {
voltage: true,
},
@@ -460,10 +461,10 @@ describe('Matter.listenToStateChange', () => {
callback(230000); // 230000 mV = 230 V
},
addActiveCurrentAttributeListener: () => {},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -472,8 +473,8 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (ElectricalPowerMeasurement - ActiveCurrent)', async () => {
- const clusterClients = new Map();
- clusterClients.set(ElectricalPowerMeasurement.Complete.id, {
+ const clusterClient = {
+ id: ElectricalPowerMeasurement.Complete.id,
supportedFeatures: {
current: true,
},
@@ -482,10 +483,10 @@ describe('Matter.listenToStateChange', () => {
addActiveCurrentAttributeListener: (callback) => {
callback(6500); // 6500 mA = 6.5 A
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -494,18 +495,18 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (ElectricalEnergyMeasurement - CumulativeEnergy)', async () => {
- const clusterClients = new Map();
- clusterClients.set(ElectricalEnergyMeasurement.Complete.id, {
+ const clusterClient = {
+ id: ElectricalEnergyMeasurement.Complete.id,
supportedFeatures: {
cumulativeEnergy: true,
},
addCumulativeEnergyImportedAttributeListener: (callback) => {
callback({ energy: 1500000000 }); // 1500000000 mWh = 1500 kWh
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
@@ -514,15 +515,15 @@ describe('Matter.listenToStateChange', () => {
});
});
it('should listen to state change (HepaFilterMonitoring)', async () => {
- const clusterClients = new Map();
- clusterClients.set(HepaFilterMonitoring.Complete.id, {
+ const clusterClient = {
+ id: HepaFilterMonitoring.Complete.id,
addConditionAttributeListener: (callback) => {
callback(75); // 75% filter life remaining
},
- });
+ };
const device = {
number: 1,
- clusterClients,
+ getClusterClientById: (id) => (id === clusterClient.id ? clusterClient : null),
};
await matterHandler.listenToStateChange(1234n, '1', device);
assert.calledWith(gladys.event.emit, EVENTS.DEVICE.NEW_STATE, {
diff --git a/server/test/services/matter/lib/matter.getNodes.test.js b/server/test/services/matter/lib/matter.getNodes.test.js
index 2515b02a2d..2c8f186fe8 100644
--- a/server/test/services/matter/lib/matter.getNodes.test.js
+++ b/server/test/services/matter/lib/matter.getNodes.test.js
@@ -21,15 +21,14 @@ describe('Matter.getNodes', () => {
});
it('should return all nodes', async () => {
- const clusterClients = new Map();
- clusterClients.set(6, {
+ const clusterClient = {
id: 2,
name: 'OnOff',
attributes: {
onOff: {},
},
commands: {},
- });
+ };
matterHandler.commissioningController = {
getNode: fake.returns({
isConnected: true,
@@ -37,13 +36,13 @@ describe('Matter.getNodes', () => {
{
number: 1,
name: 'Test Device',
- clusterClients: new Map(),
- childEndpoints: [
+ getAllClusterClients: () => [],
+ getChildEndpoints: () => [
{
name: 'Test Device child',
number: 2,
- clusterClients,
- childEndpoints: [],
+ getAllClusterClients: () => [clusterClient],
+ getChildEndpoints: () => [],
},
],
},
diff --git a/server/test/services/matter/lib/matter.init.test.js b/server/test/services/matter/lib/matter.init.test.js
index a9ae89572d..c33973f986 100644
--- a/server/test/services/matter/lib/matter.init.test.js
+++ b/server/test/services/matter/lib/matter.init.test.js
@@ -294,13 +294,16 @@ describe('Matter.init', () => {
id: 'device-1',
name: 'Test Device',
number: 1,
- clusterClients,
- childEndpoints: [
+ getAllClusterClients: () => Array.from(clusterClients.values()),
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [
{
id: 'child-endpoint-1',
name: 'Child Endpoint',
number: 2,
- clusterClients,
+ getAllClusterClients: () => Array.from(clusterClients.values()),
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
],
},
@@ -346,6 +349,8 @@ describe('Matter.init', () => {
it('should initialize matter service successfully', async () => {
await matterHandler.init();
+ // Wait for background refreshDevices() to complete
+ await matterHandler.refreshDevicesPromise;
expect(matterHandler.devices).to.have.lengthOf(2);
// Device selector should be a slug of the name with 4 random characters at the end
expect(matterHandler.devices[0].selector).to.satisfy(
@@ -892,6 +897,14 @@ describe('Matter.init', () => {
expect(matterHandler.nodesMap.size).to.equal(0);
});
+ it('should log error when refreshDevices fails in background', async () => {
+ const error = new Error('Test error');
+ matterHandler.refreshDevices = fake.rejects(error);
+ await matterHandler.init();
+ // Wait for background promise to complete - error should be caught and logged, not thrown
+ await matterHandler.refreshDevicesPromise;
+ });
+
it('should return PPM for 0', () => {
expect(convertMeasurementUnitToDeviceFeatureUnits(0)).to.equal('ppm');
});
diff --git a/server/test/services/matter/lib/matter.pairDevice.test.js b/server/test/services/matter/lib/matter.pairDevice.test.js
index 82ef8a9ce4..ab63197d7b 100644
--- a/server/test/services/matter/lib/matter.pairDevice.test.js
+++ b/server/test/services/matter/lib/matter.pairDevice.test.js
@@ -24,10 +24,10 @@ describe('Matter.pairDevice', () => {
it('should pair a device', async () => {
const pairingCode = '1450-134-1614';
- const clusterClients = new Map();
- clusterClients.set(6, {
+ const clusterClient = {
+ id: 6,
addOnOffAttributeListener: fake.returns(null),
- });
+ };
matterHandler.commissioningController = {
commissionNode: fake.resolves(12345n),
getCommissionedNodesDetails: fake.returns([
@@ -47,13 +47,16 @@ describe('Matter.pairDevice', () => {
id: 'device-1',
name: 'Test Device',
number: 1,
- clusterClients: new Map(),
- childEndpoints: [
+ getAllClusterClients: () => [],
+ getClusterClientById: () => null,
+ getChildEndpoints: () => [
{
id: 'child-endpoint-1',
name: 'Child Endpoint',
number: 2,
- clusterClients,
+ getAllClusterClients: () => [clusterClient],
+ getClusterClientById: () => null,
+ getChildEndpoints: () => [],
},
],
},
@@ -66,12 +69,12 @@ describe('Matter.pairDevice', () => {
});
it('should pair a bridge device', async () => {
const pairingCode = '1450-134-1614';
- const clusterClients = new Map();
- clusterClients.set(6, {
+ const clusterClient = {
+ id: 6,
addOnOffAttributeListener: fake.returns(null),
- });
- const bridgeClusterClients = new Map();
- bridgeClusterClients.set(BridgedDeviceBasicInformation.Complete.id, {
+ };
+ const bridgeClusterClient = {
+ id: BridgedDeviceBasicInformation.Complete.id,
attributes: {
vendorName: {
get: fake.resolves('Test Vendor'),
@@ -92,7 +95,7 @@ describe('Matter.pairDevice', () => {
get: fake.resolves('serialNumber'),
},
},
- });
+ };
matterHandler.commissioningController = {
commissionNode: fake.resolves(12345n),
getCommissionedNodesDetails: fake.returns([
@@ -114,13 +117,16 @@ describe('Matter.pairDevice', () => {
id: 'device-1',
name: 'Test Device',
number: 1,
- clusterClients: bridgeClusterClients,
- childEndpoints: [
+ getAllClusterClients: () => [bridgeClusterClient],
+ getClusterClientById: (id) => (id === bridgeClusterClient.id ? bridgeClusterClient : null),
+ getChildEndpoints: () => [
{
id: 'child-endpoint-1',
name: 'Child Endpoint',
number: 2,
- clusterClients,
+ getAllClusterClients: () => [clusterClient],
+ getClusterClientById: () => null,
+ getChildEndpoints: () => [],
},
],
},
@@ -128,13 +134,16 @@ describe('Matter.pairDevice', () => {
id: 'device-2',
name: 'Test Device 2',
number: 2,
- clusterClients: bridgeClusterClients,
- childEndpoints: [
+ getAllClusterClients: () => [bridgeClusterClient],
+ getClusterClientById: (id) => (id === bridgeClusterClient.id ? bridgeClusterClient : null),
+ getChildEndpoints: () => [
{
id: 'child-endpoint-2',
name: 'Child Endpoint 2',
number: 2,
- clusterClients,
+ getAllClusterClients: () => [clusterClient],
+ getClusterClientById: () => null,
+ getChildEndpoints: () => [],
},
],
},
diff --git a/server/test/services/matter/lib/matter.refreshDevices.test.js b/server/test/services/matter/lib/matter.refreshDevices.test.js
index 1e867e5fb6..46a98c0ed9 100644
--- a/server/test/services/matter/lib/matter.refreshDevices.test.js
+++ b/server/test/services/matter/lib/matter.refreshDevices.test.js
@@ -53,13 +53,16 @@ describe('Matter.refreshDevices', () => {
id: 'device-1',
name: 'Test Device',
number: 1,
- clusterClients,
- childEndpoints: [
+ getAllClusterClients: () => Array.from(clusterClients.values()),
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [
{
id: 'child-endpoint-1',
name: 'Child Endpoint',
number: 2,
- clusterClients,
+ getAllClusterClients: () => Array.from(clusterClients.values()),
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
],
},
@@ -76,4 +79,54 @@ describe('Matter.refreshDevices', () => {
expect(matterHandler.devices).to.have.lengthOf(2);
expect(matterHandler.nodesMap.size).to.equal(1);
});
+
+ it('should continue with other devices when one node fails', async () => {
+ let callCount = 0;
+ // First node throws an error, second node succeeds
+ matterHandler.commissioningController = {
+ getCommissionedNodesDetails: fake.returns([
+ {
+ nodeId: 11111n,
+ deviceData: {
+ basicInformation: {
+ vendorName: 'Unreachable Vendor',
+ productName: 'Unreachable Product',
+ },
+ },
+ },
+ {
+ nodeId: 22222n,
+ deviceData: {
+ basicInformation: {
+ vendorName: 'Reachable Vendor',
+ productName: 'Reachable Product',
+ },
+ },
+ },
+ ]),
+ getNode: sinon.stub().callsFake(async () => {
+ callCount += 1;
+ if (callCount === 1) {
+ throw new Error('Node is not reachable right now');
+ }
+ return {
+ getDevices: fake.returns([
+ {
+ id: 'device-2',
+ name: 'Reachable Device',
+ number: 1,
+ getAllClusterClients: () => Array.from(clusterClients.values()),
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
+ },
+ ]),
+ };
+ }),
+ };
+
+ await matterHandler.refreshDevices();
+ // Should have 1 device from the second node (first node failed)
+ expect(matterHandler.devices).to.have.lengthOf(1);
+ expect(matterHandler.devices[0].name).to.include('Reachable');
+ });
});
diff --git a/server/test/services/matter/lib/matter.reset.test.js b/server/test/services/matter/lib/matter.reset.test.js
new file mode 100644
index 0000000000..67805a9961
--- /dev/null
+++ b/server/test/services/matter/lib/matter.reset.test.js
@@ -0,0 +1,77 @@
+const sinon = require('sinon');
+const path = require('path');
+const fse = require('fs-extra');
+const { expect } = require('chai');
+
+const { fake, assert } = sinon;
+
+const MatterHandler = require('../../../../services/matter/lib');
+
+describe('Matter.reset', () => {
+ let matterHandler;
+ let gladys;
+ let previousMatterPath;
+ let testMatterPath;
+
+ beforeEach(async () => {
+ previousMatterPath = process.env.MATTER_FOLDER_PATH;
+ testMatterPath = '/tmp/gladys-matter-reset-test';
+ process.env.MATTER_FOLDER_PATH = testMatterPath;
+
+ // Create test directory with some files
+ await fse.ensureDir(testMatterPath);
+ await fse.writeFile(path.join(testMatterPath, 'test-file'), 'test content');
+
+ gladys = {
+ job: {
+ wrapper: fake.returns(null),
+ },
+ variable: {
+ destroy: fake.resolves(null),
+ setValue: fake.resolves(null),
+ },
+ config: {
+ storage: '/var/lib/gladys/gladys.db',
+ },
+ };
+ const MatterMain = {};
+ const ProjectChipMatter = {};
+
+ matterHandler = new MatterHandler(gladys, MatterMain, ProjectChipMatter, 'service-1');
+ matterHandler.stop = fake.resolves(null);
+ });
+
+ afterEach(async () => {
+ await fse.remove(testMatterPath);
+ process.env.MATTER_FOLDER_PATH = previousMatterPath;
+ });
+
+ it('should reset matter integration', async () => {
+ matterHandler.commissioningController = {
+ close: fake.resolves(null),
+ };
+ matterHandler.devices = [{ name: 'device1' }];
+ matterHandler.nodesMap = new Map([['node1', {}]]);
+ matterHandler.stateChangeListeners = new Set(['listener1']);
+
+ // Verify directory exists before reset
+ const existsBefore = await fse.pathExists(testMatterPath);
+ expect(existsBefore).to.equal(true);
+
+ await matterHandler.reset();
+
+ assert.calledOnce(matterHandler.stop);
+ assert.calledOnceWithExactly(gladys.variable.destroy, 'MATTER_BACKUP', 'service-1');
+ assert.calledOnceWithExactly(gladys.variable.setValue, 'MATTER_ENABLED', 'false', 'service-1');
+
+ // Verify directory was deleted
+ const existsAfter = await fse.pathExists(testMatterPath);
+ expect(existsAfter).to.equal(false);
+
+ // Verify in-memory state is reset
+ expect(matterHandler.commissioningController).to.equal(null);
+ expect(matterHandler.devices).to.deep.equal([]);
+ expect(matterHandler.nodesMap.size).to.equal(0);
+ expect(matterHandler.stateChangeListeners.size).to.equal(0);
+ });
+});
diff --git a/server/test/services/matter/lib/matter.setValue.test.js b/server/test/services/matter/lib/matter.setValue.test.js
index 8d18617404..c1325dc9d8 100644
--- a/server/test/services/matter/lib/matter.setValue.test.js
+++ b/server/test/services/matter/lib/matter.setValue.test.js
@@ -51,7 +51,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -90,7 +91,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -130,13 +132,16 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- childEndpoints: [
+ getClusterClientById: () => null,
+ getChildEndpoints: () => [
{
number: 2,
- childEndpoints: [
+ getClusterClientById: () => null,
+ getChildEndpoints: () => [
{
number: 2,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
],
},
@@ -179,7 +184,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -218,7 +224,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -265,7 +272,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -319,7 +327,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -364,7 +373,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -402,7 +412,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -473,7 +484,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -505,7 +517,8 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
]),
});
@@ -537,10 +550,12 @@ describe('Matter.setValue', () => {
getDevices: fake.returns([
{
number: 1,
- childEndpoints: [
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [
{
number: 1,
- clusterClients,
+ getClusterClientById: (id) => clusterClients.get(id),
+ getChildEndpoints: () => [],
},
],
},