diff --git a/apack.json b/apack.json index 7aaad61479..73c6987314 100644 --- a/apack.json +++ b/apack.json @@ -4,7 +4,7 @@ "description": "Graphical configuration tool for application and libraries based on Zigbee Cluster Library.", "path": [".", "node_modules/.bin/", "ZAP.app/Contents/MacOS"], "requiredFeatureLevel": "apack.core:9", - "featureLevel": 103, + "featureLevel": 104, "uc.triggerExtension": "zap", "executable": { "zap:win32.x86_64": { diff --git a/src-electron/db/query-config.js b/src-electron/db/query-config.js index 1b28c43b91..f160c34cdb 100644 --- a/src-electron/db/query-config.js +++ b/src-electron/db/query-config.js @@ -1452,7 +1452,8 @@ SELECT ETA.INCLUDED_REPORTABLE, ETA.MIN_INTERVAL, ETA.MAX_INTERVAL, - ETA.REPORTABLE_CHANGE + ETA.REPORTABLE_CHANGE, + ETA.ENDPOINT_TYPE_ATTRIBUTE_ID FROM CLUSTER AS C JOIN @@ -1477,6 +1478,7 @@ ORDER BY .then((rows) => rows.map((row) => { return { + endpointTypeAttributeId: row.ENDPOINT_TYPE_ATTRIBUTE_ID, name: row.NAME, attributeCode: row.ATTRIBUTE_CODE, clusterCode: row.CLUSTER_CODE, diff --git a/src-electron/db/zap-schema.sql b/src-electron/db/zap-schema.sql index 37ff2d9433..7b1257aa09 100644 --- a/src-electron/db/zap-schema.sql +++ b/src-electron/db/zap-schema.sql @@ -1296,6 +1296,151 @@ CREATE TABLE IF NOT EXISTS "ENDPOINT_TYPE_ATTRIBUTE" ( ) ); +/** +SQL Trigger for a multi-protocol Endpoint Type Attribute default value. +If the default value of a multi-protocol endpoint attribute is updated +then the corresponding attribute's default value in the other protocol +is also updated on the same endpoint identifier +Eg: If Matter's on/off attribute is updated then so will Zigbee's on/off +attribute be toggled as well. +*/ +CREATE TRIGGER + UPDATE_MULTIPROTOCOL_ATTRIBUTES_ACROSS_ENDPOINT_TYPES +AFTER + UPDATE ON ENDPOINT_TYPE_ATTRIBUTE +WHEN +( + ( + SELECT + COUNT() + FROM + ENDPOINT_TYPE_ATTRIBUTE + INNER JOIN + ATTRIBUTE + ON + ENDPOINT_TYPE_ATTRIBUTE.ATTRIBUTE_REF = ATTRIBUTE.ATTRIBUTE_ID + INNER JOIN + ATTRIBUTE_MAPPING + ON + ATTRIBUTE.ATTRIBUTE_ID = ATTRIBUTE_MAPPING.ATTRIBUTE_LEFT_REF + OR ATTRIBUTE.ATTRIBUTE_ID = ATTRIBUTE_MAPPING.ATTRIBUTE_RIGHT_REF + WHERE + ENDPOINT_TYPE_ATTRIBUTE.ENDPOINT_TYPE_ATTRIBUTE_ID = new.ENDPOINT_TYPE_ATTRIBUTE_ID + ) > 0 + AND + ( + SELECT EXISTS + (SELECT + ENDPOINT.ENDPOINT_IDENTIFIER, + ENDPOINT.SESSION_REF, + ( + SELECT + COUNT() + FROM + ENDPOINT + WHERE + ENDPOINT_IDENTIFIER = ENDPOINT.ENDPOINT_IDENTIFIER + AND + SESSION_REF = ENDPOINT.SESSION_REF + ) AS ENDPOINT_COUNT + FROM + ENDPOINT_TYPE_ATTRIBUTE + INNER JOIN + ENDPOINT_TYPE_CLUSTER + ON + ENDPOINT_TYPE_CLUSTER.ENDPOINT_TYPE_CLUSTER_ID = ENDPOINT_TYPE_ATTRIBUTE.ENDPOINT_TYPE_CLUSTER_REF + INNER JOIN + ENDPOINT_TYPE + ON + ENDPOINT_TYPE.ENDPOINT_TYPE_ID = ENDPOINT_TYPE_CLUSTER.ENDPOINT_TYPE_REF + INNER JOIN + ENDPOINT + ON + ENDPOINT.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID + WHERE + ENDPOINT_TYPE_ATTRIBUTE.ENDPOINT_TYPE_ATTRIBUTE_ID = new.ENDPOINT_TYPE_ATTRIBUTE_ID + AND + ENDPOINT_COUNT > 1) + ) +) +BEGIN + UPDATE + ENDPOINT_TYPE_ATTRIBUTE + SET + DEFAULT_VALUE = new.DEFAULT_VALUE + WHERE + ENDPOINT_TYPE_ATTRIBUTE_ID + IN + ( + SELECT + CASE + WHEN new.ENDPOINT_TYPE_ATTRIBUTE_ID = ETA1.ENDPOINT_TYPE_ATTRIBUTE_ID THEN ETA2.ENDPOINT_TYPE_ATTRIBUTE_ID + WHEN new.ENDPOINT_TYPE_ATTRIBUTE_ID = ETA2.ENDPOINT_TYPE_ATTRIBUTE_ID THEN ETA1.ENDPOINT_TYPE_ATTRIBUTE_ID + END AS ENDPOINT_TYPE_ATTRIBUTE_ID + FROM + ENDPOINT AS E1 + INNER JOIN + ENDPOINT_TYPE AS ET1 + ON + ET1.ENDPOINT_TYPE_ID = E1.ENDPOINT_TYPE_REF + INNER JOIN + ENDPOINT_TYPE_CLUSTER AS ETC1 + ON + ETC1.ENDPOINT_TYPE_REF = ET1.ENDPOINT_TYPE_ID + INNER JOIN + ENDPOINT_TYPE_ATTRIBUTE AS ETA1 + ON + ETA1.ENDPOINT_TYPE_CLUSTER_REF = ETC1.ENDPOINT_TYPE_CLUSTER_ID + INNER JOIN + ATTRIBUTE_MAPPING AS AM + ON + AM.ATTRIBUTE_LEFT_REF = ETA1.ATTRIBUTE_REF + INNER JOIN + ENDPOINT_TYPE_ATTRIBUTE AS ETA2 + ON + AM.ATTRIBUTE_RIGHT_REF = ETA2.ATTRIBUTE_REF + INNER JOIN + ENDPOINT_TYPE_CLUSTER AS ETC2 + ON + ETA2.ENDPOINT_TYPE_CLUSTER_REF = ETC2.ENDPOINT_TYPE_CLUSTER_ID + INNER JOIN + ENDPOINT_TYPE AS ET2 + ON + ETC2.ENDPOINT_TYPE_REF = ET2.ENDPOINT_TYPE_ID + INNER JOIN + ENDPOINT AS E2 + ON + ET2.ENDPOINT_TYPE_ID = E2.ENDPOINT_TYPE_REF + WHERE + E1.ENDPOINT_IDENTIFIER = E2.ENDPOINT_IDENTIFIER + AND + new.ENDPOINT_TYPE_ATTRIBUTE_ID IN (ETA1.ENDPOINT_TYPE_ATTRIBUTE_ID, ETA2.ENDPOINT_TYPE_ATTRIBUTE_ID) + AND + ( + SELECT + ENDPOINT.SESSION_REF + FROM + ENDPOINT_TYPE_ATTRIBUTE + INNER JOIN + ENDPOINT_TYPE_CLUSTER + ON + ENDPOINT_TYPE_CLUSTER.ENDPOINT_TYPE_CLUSTER_ID = ENDPOINT_TYPE_ATTRIBUTE.ENDPOINT_TYPE_CLUSTER_REF + INNER JOIN + ENDPOINT_TYPE + ON + ENDPOINT_TYPE.ENDPOINT_TYPE_ID = ENDPOINT_TYPE_CLUSTER.ENDPOINT_TYPE_REF + INNER JOIN + ENDPOINT + ON + ENDPOINT.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID + WHERE + ENDPOINT_TYPE_ATTRIBUTE.ENDPOINT_TYPE_ATTRIBUTE_ID = new.ENDPOINT_TYPE_ATTRIBUTE_ID + ) IN (E1.SESSION_REF, E2.SESSION_REF) + AND + E1.SESSION_REF = E2.SESSION_REF + ); +END; + /* SQL Trigger for Device Type attribute Compliance. This trigger is used to add a warning to the notification table when an diff --git a/src-electron/util/env.js b/src-electron/util/env.js index cf768c007d..cb51762ae1 100644 --- a/src-electron/util/env.js +++ b/src-electron/util/env.js @@ -475,7 +475,13 @@ export function isMatchingVersion(versionsArray, providedVersion) { */ export function versionsCheck() { let expectedNodeVersion = ['v14.x.x', 'v16.x.x', 'v18.x.x'] - let expectedElectronVersion = ['17.4.x', '18.x.x', '24.x.x', '27.x.x'] + let expectedElectronVersion = [ + '17.4.x', + '18.x.x', + '24.x.x', + '27.x.x', + '31.x.x' + ] let nodeVersion = process.version let electronVersion = process.versions.electron let ret = true diff --git a/test/multi-protocol.test.js b/test/multi-protocol.test.js index ceaf6e45c2..80e6ab0696 100644 --- a/test/multi-protocol.test.js +++ b/test/multi-protocol.test.js @@ -26,6 +26,7 @@ const querySessionNotice = require('../src-electron/db/query-session-notificatio const zclLoader = require('../src-electron/zcl/zcl-loader') const importJs = require('../src-electron/importexport/import') const testUtil = require('./test-util') +const queryConfig = require('../src-electron/db/query-config') const zigbeeTemplateCount = testUtil.testTemplate.zigbee2Count const matterTemplateCount = testUtil.testTemplate.matter2Count @@ -208,3 +209,95 @@ test( }, testUtil.timeout.long() ) + +// Test to check that the default values of corresponding attributes is updated on one endpoint +// Make sure the above does not affect other endpoints. +// Make sure other attribute's values are not affected as well. +// Test both sides matter to zigbee and zigbee to matter +test( + `Test Endpoint Type Attribute default value syncing between 2 protocols ${multiProtocolTestFile}`, + async () => { + let importRes = await importJs.importDataFromFile( + db, + multiProtocolTestFile, + { sessionId: null }, + ) + + // Get all session attributes + let allEndpointTypeAttributes = + await queryConfig.selectAllSessionAttributes(db, importRes.sessionId) + let zigbeeEndpointTypeAttribute = '' + let matterEndpointTypeAttribute = '' + + // Get all on/off endpoint type attribute values for zigbee and matter + for (const eta of allEndpointTypeAttributes) { + if (eta.name.toLowerCase() == 'on/off') { + zigbeeEndpointTypeAttribute = eta + } + if (eta.name.toLowerCase() == 'onoff') { + matterEndpointTypeAttribute = eta + } + } + + // Check both of them are the same + expect(parseInt(zigbeeEndpointTypeAttribute.defaultValue)).toEqual(0) + expect(parseInt(zigbeeEndpointTypeAttribute.defaultValue)).toEqual( + parseInt(matterEndpointTypeAttribute.defaultValue), + ) + + // Change zigbee ETA and check for the change in the corresponding Matter ETA. + await queryConfig.updateEndpointTypeAttribute( + db, + zigbeeEndpointTypeAttribute.endpointTypeAttributeId, + [['defaultValue', 1]], + ) + let allEndpointTypeAttributesAfterChange = + await queryConfig.selectAllSessionAttributes(db, importRes.sessionId) + for (const eta of allEndpointTypeAttributesAfterChange) { + if (eta.name.toLowerCase() == 'on/off') { + zigbeeEndpointTypeAttribute = eta + } + if (eta.name.toLowerCase() == 'onoff') { + matterEndpointTypeAttribute = eta + } + } + expect(parseInt(zigbeeEndpointTypeAttribute.defaultValue)).toEqual(1) + expect(parseInt(zigbeeEndpointTypeAttribute.defaultValue)).toEqual( + parseInt(matterEndpointTypeAttribute.defaultValue), + ) + + // Negative test: Check that none of the other Endpoint Type Attribute values are not changed. Only the ones intended i.e. on/off + for (let i = 0; i < allEndpointTypeAttributes.length; i++) { + if ( + allEndpointTypeAttributes[i].name.toLowerCase() != 'on/off' && + allEndpointTypeAttributes[i].name.toLowerCase() != 'onoff' + ) { + expect(allEndpointTypeAttributes[i].defaultValue).toEqual( + allEndpointTypeAttributesAfterChange[i].defaultValue, + ) + } + } + + // Also test change of matter ETA and check for the change in the corresponding Zigbee ETA. + await queryConfig.updateEndpointTypeAttribute( + db, + matterEndpointTypeAttribute.endpointTypeAttributeId, + [['defaultValue', 0]], + ) + allEndpointTypeAttributesAfterChange = + await queryConfig.selectAllSessionAttributes(db, importRes.sessionId) + for (const eta of allEndpointTypeAttributesAfterChange) { + if (eta.name.toLowerCase() == 'on/off') { + zigbeeEndpointTypeAttribute = eta + } + if (eta.name.toLowerCase() == 'onoff') { + matterEndpointTypeAttribute = eta + } + } + expect(parseInt(matterEndpointTypeAttribute.defaultValue)).toEqual(0) + expect(parseInt(matterEndpointTypeAttribute.defaultValue)).toEqual( + parseInt(zigbeeEndpointTypeAttribute.defaultValue), + ) + }, + testUtil.timeout.long(), +)