From 43da6e86a97c4b43be2dd341f7a281b5ed55b475 Mon Sep 17 00:00:00 2001 From: Ethan Zhou <73028112+ethanzhouyc@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:01:11 -0400 Subject: [PATCH] Feature Page phase 1 (#1409) * add conformance column * parse xml data * feature page v1 * add feature state * add cluster side * resolve comments * add unit test * improve parseConformance function * change button location & improve UI * restructure ui-options * fix Cypress test --- .../clusters/cluster-multiple-search.cy.js | 6 +- src-electron/db/db-mapping.js | 17 ++ src-electron/db/query-feature.js | 76 +++++++++ src-electron/db/query-loader.js | 6 +- src-electron/db/zap-schema.sql | 1 + src-electron/rest/user-data.js | 23 +++ src-electron/zcl/zcl-loader-silabs.js | 119 ++++++++++++- src-shared/db-enum.js | 23 +++ src-shared/rest-api.js | 1 + src/components/ZclClusterManager.vue | 26 ++- src/components/ZclClusterView.vue | 2 +- .../ZclDeviceTypeFeatureManager.vue | 158 ++++++++++++++++++ src/components/ZclEndpointCard.vue | 4 +- src/router/routes.js | 14 ++ src/store/zap/actions.js | 18 ++ src/store/zap/mutations.js | 5 + src/store/zap/state.js | 1 + src/util/common-mixin.js | 5 + src/util/ui-options.js | 109 +++++++----- test/feature.test.js | 96 +++++++++++ .../matter/data-model/chip/matter-devices.xml | 12 +- 21 files changed, 653 insertions(+), 69 deletions(-) create mode 100644 src-electron/db/query-feature.js create mode 100644 src/components/ZclDeviceTypeFeatureManager.vue create mode 100644 test/feature.test.js diff --git a/cypress/e2e/clusters/cluster-multiple-search.cy.js b/cypress/e2e/clusters/cluster-multiple-search.cy.js index cf172e3e25..541cf91a2c 100644 --- a/cypress/e2e/clusters/cluster-multiple-search.cy.js +++ b/cypress/e2e/clusters/cluster-multiple-search.cy.js @@ -15,11 +15,11 @@ describe('Add multiple clusters and search', () => { cy.fixture('data').then((data) => { cy.addEndpoint(data.endpoint4, data.cluster1) }) - cy.get('.flex > strong').should('contain', 'Endpoint - 1') + cy.get('.flex > strong').should('contain', '#1') cy.fixture('data').then((data) => { cy.addEndpoint(data.endpoint3, data.cluster1) }) - cy.get('.flex > strong').should('contain', 'Endpoint - 2') + cy.get('.flex > strong').should('contain', '#2') cy.fixture('data').then((data) => { cy.get('tbody') .children() @@ -27,7 +27,7 @@ describe('Add multiple clusters and search', () => { .and('contain', data.cluster6) cy.addEndpoint(data.endpoint5, data.cluster1) }) - cy.get('.flex > strong').should('contain', 'Endpoint - 3') + cy.get('.flex > strong').should('contain', '#3') cy.fixture('data').then((data) => { cy.get('#General > .q-expansion-item__container > .q-item').click({ force: true diff --git a/src-electron/db/db-mapping.js b/src-electron/db/db-mapping.js index 8bde933057..77ed290daf 100644 --- a/src-electron/db/db-mapping.js +++ b/src-electron/db/db-mapping.js @@ -245,6 +245,23 @@ exports.map = { } }, + deviceTypeFeature: (x) => { + if (x == null) return undefined + return { + deviceType: x.DEVICE_TYPE_NAME, + cluster: x.CLUSTER_NAME, + includeServer: x.INCLUDE_SERVER, + includeClient: x.INCLUDE_CLIENT, + conformance: x.DEVICE_TYPE_CLUSTER_CONFORMANCE, + id: x.FEATURE_ID, + name: x.FEATURE_NAME, + code: x.CODE, + bit: x.BIT, + default_value: x.DEFAULT_VALUE, + description: x.DESCRIPTION + } + }, + domain: (x) => { if (x == null) return undefined return { diff --git a/src-electron/db/query-feature.js b/src-electron/db/query-feature.js new file mode 100644 index 0000000000..98f9e64110 --- /dev/null +++ b/src-electron/db/query-feature.js @@ -0,0 +1,76 @@ +/** + * + * Copyright (c) 2021 Silicon Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This module provides queries for features. + * + * @module DB API: feature related queries + */ +const dbApi = require('./db-api.js') +const dbMapping = require('./db-mapping.js') + +/** + * Get all device type features associated with a list of device type refs + * @param {*} db + * @param {*} deviceTypeRefs + * @returns All feature information and device type conformance + * with associated device type and cluster details + */ +async function getFeaturesByDeviceTypeRefs(db, deviceTypeRefs) { + let deviceTypeRefsSql = deviceTypeRefs.map(() => '?').join(', ') + let features = await dbApi.dbAll( + db, + ` + SELECT + d.DESCRIPTION AS DEVICE_TYPE_NAME, + dc.CLUSTER_NAME, + dc.INCLUDE_SERVER, + dc.INCLUDE_CLIENT, + df.DEVICE_TYPE_CLUSTER_CONFORMANCE, + f.FEATURE_ID, + f.NAME AS FEATURE_NAME, + f.CODE, + f.BIT, + f.DEFAULT_VALUE, + f.DESCRIPTION + FROM + DEVICE_TYPE d + JOIN + DEVICE_TYPE_CLUSTER dc + ON + d.DEVICE_TYPE_ID = dc.DEVICE_TYPE_REF + JOIN + DEVICE_TYPE_FEATURE df + ON + dc.DEVICE_TYPE_CLUSTER_ID = df.DEVICE_TYPE_CLUSTER_REF + JOIN + FEATURE f + ON + df.FEATURE_REF = f.FEATURE_ID + WHERE + d.DEVICE_TYPE_ID IN (${deviceTypeRefsSql}) + ORDER BY + d.DEVICE_TYPE_ID, + dc.CLUSTER_REF, + f.FEATURE_ID + `, + deviceTypeRefs + ) + return features.map(dbMapping.map.deviceTypeFeature) +} + +exports.getFeaturesByDeviceTypeRefs = getFeaturesByDeviceTypeRefs diff --git a/src-electron/db/query-loader.js b/src-electron/db/query-loader.js index bc5d5fe17a..ba0a066789 100644 --- a/src-electron/db/query-loader.js +++ b/src-electron/db/query-loader.js @@ -1124,14 +1124,14 @@ async function insertDeviceTypeFeatures(db, dtClusterRefDataPairs) { let dtClusterRef = dtClusterRefDataPair.dtClusterRef let clusterData = dtClusterRefDataPair.clusterData if ('features' in clusterData && clusterData.features.length > 0) { - clusterData.features.forEach((featureCode) => { - features.push([dtClusterRef, featureCode]) + clusterData.features.forEach((feature) => { + features.push([dtClusterRef, feature.code, feature.conformance]) }) } }) return dbApi.dbMultiInsert( db, - 'INSERT INTO DEVICE_TYPE_FEATURE (DEVICE_TYPE_CLUSTER_REF, FEATURE_CODE) VALUES (?, ?)', + 'INSERT INTO DEVICE_TYPE_FEATURE (DEVICE_TYPE_CLUSTER_REF, FEATURE_CODE, DEVICE_TYPE_CLUSTER_CONFORMANCE) VALUES (?, ?, ?)', features ) } diff --git a/src-electron/db/zap-schema.sql b/src-electron/db/zap-schema.sql index 7b1257aa09..a5c598537c 100644 --- a/src-electron/db/zap-schema.sql +++ b/src-electron/db/zap-schema.sql @@ -441,6 +441,7 @@ CREATE TABLE IF NOT EXISTS "DEVICE_TYPE_FEATURE" ( "DEVICE_TYPE_CLUSTER_REF" integer, "FEATURE_REF" integer, "FEATURE_CODE" text, + "DEVICE_TYPE_CLUSTER_CONFORMANCE" text, foreign key (DEVICE_TYPE_CLUSTER_REF) references DEVICE_TYPE_CLUSTER(DEVICE_TYPE_CLUSTER_ID) ON DELETE CASCADE ON UPDATE CASCADE, foreign key (FEATURE_REF) references FEATURE(FEATURE_ID) ON DELETE CASCADE ON UPDATE CASCADE UNIQUE(DEVICE_TYPE_CLUSTER_REF, FEATURE_REF) diff --git a/src-electron/rest/user-data.js b/src-electron/rest/user-data.js index 967cbb7167..a1366ad67d 100644 --- a/src-electron/rest/user-data.js +++ b/src-electron/rest/user-data.js @@ -24,6 +24,7 @@ const env = require('../util/env') const queryZcl = require('../db/query-zcl.js') const queryAttribute = require('../db/query-attribute.js') const queryCommand = require('../db/query-command.js') +const queryFeature = require('../db/query-feature') const queryConfig = require('../db/query-config.js') const upgrade = require('../sdk/matter.js') const querySessionNotification = require('../db/query-session-notification.js') @@ -72,6 +73,24 @@ function httpGetEndpointIds(db) { response.status(StatusCodes.OK).json(endpointIds) } } + +/** + * HTTP GET: device type features + * + * @param {*} db + * @returns callback for the express uri registration + */ +function httpGetDeviceTypeFeatures(db) { + return async (request, response) => { + let deviceTypeRefs = request.query.deviceTypeRefs + let deviceTypeFeatures = await queryFeature.getFeaturesByDeviceTypeRefs( + db, + deviceTypeRefs + ) + response.status(StatusCodes.OK).json(deviceTypeFeatures) + } +} + /** * HTTP GET: session get notifications * @@ -1030,6 +1049,10 @@ exports.get = [ uri: restApi.uri.getAllSessionKeyValues, callback: httpGetSessionKeyValues }, + { + uri: restApi.uri.deviceTypeFeatures, + callback: httpGetDeviceTypeFeatures + }, { uri: restApi.uri.sessionNotification, callback: httpGetSessionNotifications diff --git a/src-electron/zcl/zcl-loader-silabs.js b/src-electron/zcl/zcl-loader-silabs.js index 45905b4b42..113be3c13e 100644 --- a/src-electron/zcl/zcl-loader-silabs.js +++ b/src-electron/zcl/zcl-loader-silabs.js @@ -686,7 +686,7 @@ function prepareCluster(cluster, context, isExtension = false) { bit: feature.$.bit, defaultValue: feature.$.default, description: feature.$.summary, - conformance: feature.$.conformance + conformance: parseFeatureConformance(feature) } ret.features.push(f) @@ -1638,10 +1638,11 @@ function prepareDeviceType(deviceType) { } if ('features' in include) { include.features[0].feature.forEach((f) => { - // Only adding madatory features for now - if (f.mandatoryConform && f.mandatoryConform[0] === '') { - features.push(f.$.name) - } + let conformance = parseFeatureConformance(f) + features.push({ + code: f.$.code, + conformance: conformance + }) }) } ret.clusters.push({ @@ -2202,6 +2203,114 @@ async function parseFeatureFlags(db, packageId, featureFlags) { ) } +/** + * Parses feature conformance or an operand in feature conformance recursively from xml data. + * + * An example of parsing the conformance of 'User' device type feature: + * + * Input operand from xml data: + * { + * "$": {"code": "USR", "name": "User"}, + * "mandatoryConform": [ + * { "andTerm": [ + * { + * "condition": [{"$": {"name": "Matter"}}], + * "orTerm": [ + * { "feature": [ + * { "$": {"name": "PIN"}}, + * { "$": {"name": "RID"}}, + * { "$": {"name": "FPG"}}, + * { "$": {"name": "FACE"}} + * ] + * } + * ] + * } + * ] + * } + * ] + * } + * + * Output device type feature conformance string: + * "Matter & (PIN | RID | FPG | FACE)" + * + * @param {*} operand - The operand to be parsed. + * @returns The feature conformance string. + */ +function parseFeatureConformance(operand) { + if (operand.mandatoryConform) { + let insideTerm = operand.mandatoryConform[0] + // Recurse further if insideTerm is not empty + if (insideTerm && Object.keys(insideTerm).toString() != '$') { + return parseFeatureConformance(operand.mandatoryConform[0]) + } else { + return 'M' + } + } else if (operand.optionalConform) { + let insideTerm = operand.optionalConform[0] + // check '$' key is not the only key in the object to handle special cases + // e.g. '' + if (insideTerm && Object.keys(insideTerm).toString() != '$') { + return `[${parseFeatureConformance(operand.optionalConform[0])}]` + } else { + return 'O' + } + } else if (operand.provisionalConform) { + return 'P' + } else if (operand.disallowConform) { + return 'X' + } else if (operand.deprecateConform) { + return 'D' + } else if (operand.feature) { + return operand.feature[0].$.name + } else if (operand.condition) { + return operand.condition[0].$.name + } else if (operand.otherwiseConform) { + return Object.entries(operand.otherwiseConform[0]) + .map(([key, value]) => parseFeatureConformance({ [key]: value })) + .join(', ') + } else if (operand.notTerm) { + let notTerms = parseFeatureConformance(operand.notTerm[0]) + // need to surround notTerms with '()' if it contains multiple terms + // e.g. !(A | B) or !(A & B) + return notTerms.includes('&') || notTerms.includes('|') + ? `!(${notTerms})` + : `!${notTerms}` + } else if (operand.andTerm) { + return parseAndOrConformanceTerms(operand.andTerm, '&') + } else if (operand.orTerm) { + return parseAndOrConformanceTerms(operand.orTerm, '|') + } else { + return '' + } +} + +/** + * Helper function to parse andTerm or orTerm from xml data + * @param {*} operand + * @param {*} joinChar + * @returns feature conformance string + */ +function parseAndOrConformanceTerms(operand, joinChar) { + // when joining multiple orTerms inside andTerms, we need to + // surround them with '()', vice versa for andTerms inside orTerms + // e.g. A & (B | C) or A | (B & C) + let oppositeChar = joinChar === '&' ? '|' : '&' + let oppositeTerm = joinChar === '&' ? 'orTerm' : 'andTerm' + + return Object.entries(operand[0]) + .map(([key, value]) => { + if (key == 'feature' || key == 'condition') { + return value.map((operand) => operand.$.name).join(` ${joinChar} `) + } else if (key == oppositeTerm) { + let terms = parseFeatureConformance({ [key]: value }) + return terms.includes(oppositeChar) ? `(${terms})` : terms + } else { + return '' + } + }) + .join(` ${joinChar} `) +} + /** * Inside the `zcl.json` can be a `featureFlags` key, which is * a general purpose object. It contains keys, that map to objects. diff --git a/src-shared/db-enum.js b/src-shared/db-enum.js index e301ee1740..654cc1369f 100644 --- a/src-shared/db-enum.js +++ b/src-shared/db-enum.js @@ -208,3 +208,26 @@ exports.packageMatch = { strict: 'strict', // This mechanism will ONLY use the records of packages in the .zap file. ignore: 'ignore' // This mechanism will completely ignore the use of packages in the .zap file. } + +exports.deviceTypeFeature = { + name: { + deviceType: 'deviceType', + cluster: 'cluster', + clusterSide: 'clusterSide', + featureName: 'featureName', + code: 'code', + conformance: 'conformance', + bit: 'bit', + description: 'description' + }, + label: { + deviceType: 'Device Type', + cluster: 'Cluster', + clusterSide: 'Cluster Side', + featureName: 'Feature Name', + code: 'Code', + conformance: 'Conformance', + bit: 'Bit', + description: 'Description' + } +} diff --git a/src-shared/rest-api.js b/src-shared/rest-api.js index 10dce9f0a5..b20f581698 100644 --- a/src-shared/rest-api.js +++ b/src-shared/rest-api.js @@ -67,6 +67,7 @@ const uri = { deviceTypeAttributes: '/zcl/deviceTypeAttributes/', deviceTypeCommands: '/zcl/deviceTypeCommands/', deviceTypeEvents: '/zcl/deviceTypeEvents/', + deviceTypeFeatures: '/zcl/deviceTypeFeatures/', sessionAttempt: '/zcl/sessionAttempt', initializeSession: '/zcl/initializeSession', sessionCreate: '/zcl/sessionCreate', diff --git a/src/components/ZclClusterManager.vue b/src/components/ZclClusterManager.vue index 6123905df1..250576d127 100644 --- a/src/components/ZclClusterManager.vue +++ b/src/components/ZclClusterManager.vue @@ -32,13 +32,24 @@ limitations under the License. @update:model-value="setSelectedEndpointType($event)" /> -
+
Endpoint {{ this.endpointId[this.selectedEndpointId] }} Clusters
+
+ +
-
+
{{ selectedCluster.label }}
diff --git a/src/components/ZclDeviceTypeFeatureManager.vue b/src/components/ZclDeviceTypeFeatureManager.vue new file mode 100644 index 0000000000..08f1f54664 --- /dev/null +++ b/src/components/ZclDeviceTypeFeatureManager.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/src/components/ZclEndpointCard.vue b/src/components/ZclEndpointCard.vue index ccce49b914..cf256f9de0 100644 --- a/src/components/ZclEndpointCard.vue +++ b/src/components/ZclEndpointCard.vue @@ -37,9 +37,7 @@ limitations under the License. " alt="" /> - - Endpoint - {{ getFormattedEndpointId(endpointReference) }} + #{{ getFormattedEndpointId(endpointReference) }}
import('layouts/MainLayout.vue'), + children: [ + { + path: '', + components: { + default: () => import('components/ZclDeviceTypeFeatureManager.vue'), + sidebar: () => import('components/ZclEndpointManager.vue') + } + } + ] } ] diff --git a/src/store/zap/actions.js b/src/store/zap/actions.js index 8fd38c922c..244f13ae84 100644 --- a/src/store/zap/actions.js +++ b/src/store/zap/actions.js @@ -660,6 +660,24 @@ export function setMiniState(context, data) { context.commit('setMiniState', data) } +/** + * This action updates the device type features after a new endpoint is selected. + * + * @param {*} context + * @param {*} deviceTypeRefs + */ +export async function updateSelectedDeviceTypeFeatures( + context, + deviceTypeRefs +) { + let config = { params: { deviceTypeRefs: deviceTypeRefs } } + axiosRequests + .$serverGet(restApi.uri.deviceTypeFeatures, config) + .then((resp) => { + context.commit('updateDeviceTypeFeatures', resp.data) + }) +} + /** * This action loads the initial data from the database. * diff --git a/src/store/zap/mutations.js b/src/store/zap/mutations.js index 99e01c2938..093ff8d0b6 100644 --- a/src/store/zap/mutations.js +++ b/src/store/zap/mutations.js @@ -721,6 +721,11 @@ export function updateNotificationCount(state, value) { state.notificationCount = value } +// This function will update the device type features after a new endpoint is selected +export function updateDeviceTypeFeatures(state, value) { + state.deviceTypeFeatures = value +} + export function setDirtyState(state, isDirty) { if (state.isDirty != isDirty) { state.isDirty = isDirty diff --git a/src/store/zap/state.js b/src/store/zap/state.js index 3e11884bb4..d189e2231a 100644 --- a/src/store/zap/state.js +++ b/src/store/zap/state.js @@ -43,6 +43,7 @@ export default function () { commands: [], events: [], zclDeviceTypes: {}, + deviceTypeFeatures: [], endpoints: [], genericOptions: {}, selectedGenericOptions: {}, diff --git a/src/util/common-mixin.js b/src/util/common-mixin.js index fa74dbb97b..8204f4e39d 100644 --- a/src/util/common-mixin.js +++ b/src/util/common-mixin.js @@ -150,6 +150,11 @@ export default { }) this.$store.dispatch('zap/updateSelectedEndpoint', endpointReference) this.$store.dispatch('zap/updateClusters') + let deviceTypeRefs = this.endpointDeviceTypeRef[this.selectedEndpointId] + this.$store.dispatch( + 'zap/updateSelectedDeviceTypeFeatures', + deviceTypeRefs + ) }, sdkExtClusterCode(extEntry) { return extEntry ? extEntry.entityCode : '' diff --git a/src/util/ui-options.js b/src/util/ui-options.js index 68aff074ec..cb7836f812 100644 --- a/src/util/ui-options.js +++ b/src/util/ui-options.js @@ -18,14 +18,6 @@ export default { data() { return { - enableProfileId: false, - enableNetworkId: false, - enableMultipleDevice: false, - enablePrimaryDevice: false, - enableParentEndpoint: false, - enableServerOnly: false, - enableSingleton: false, - enableBounded: false, globalLists: [ 'EventList', 'AttributeList', @@ -34,44 +26,71 @@ export default { ] } }, - mounted() { - const zapState = this.$store.state.zap - const zclProperties = zapState.selectedZapConfig?.zclProperties - let multiDeviceCategories = [] - let enableMatterFeatures = false - let enableZigbeeFeatures = false - if (Array.isArray(zclProperties) && zclProperties.length > 0) { - multiDeviceCategories = zclProperties.map((zclProp) => zclProp.category) - enableMatterFeatures = multiDeviceCategories.includes('matter') - enableZigbeeFeatures = multiDeviceCategories.includes('zigbee') - } else { - let selectedZapConfig = zapState.selectedZapConfig - multiDeviceCategories = selectedZapConfig?.zclProperties?.category - // the use case when there is only one zcl and template package and user - // does not have any packages to select from. - if ( - !multiDeviceCategories && - selectedZapConfig && - selectedZapConfig.zclProperties && - selectedZapConfig.zclProperties.length == 0 && - zapState.packages && - zapState.packages.length > 0 - ) { - multiDeviceCategories = zapState.packages[0]?.sessionPackage?.category + computed: { + zapState() { + return this.$store.state.zap + }, + zclProperties() { + return this.zapState.selectedZapConfig?.zclProperties + }, + zclPropertiesNonEmpty() { + return this.zclProperties && this.zclProperties.length > 0 + }, + multiDeviceCategories() { + if (this.zclPropertiesNonEmpty) { + return this.zclProperties.map((zclProp) => zclProp.category) + } else { + const selectedZapConfig = this.zapState.selectedZapConfig + let categories = selectedZapConfig?.zclProperties?.category + if ( + !categories && + selectedZapConfig && + selectedZapConfig.zclProperties && + selectedZapConfig.zclProperties.length == 0 && + this.zapState.packages && + this.zapState.packages.length > 0 + ) { + categories = this.zapState.packages[0]?.sessionPackage?.category + } + return categories } - enableMatterFeatures = multiDeviceCategories == 'matter' - // Showing zigbee UI by default when category is not defined - enableZigbeeFeatures = - multiDeviceCategories == 'zigbee' || !multiDeviceCategories + }, + enableMatterFeatures() { + return this.zclPropertiesNonEmpty + ? this.multiDeviceCategories.includes('matter') + : this.multiDeviceCategories == 'matter' + }, + enableZigbeeFeatures() { + return this.zclPropertiesNonEmpty + ? this.multiDeviceCategories.includes('zigbee') + : this.multiDeviceCategories === 'zigbee' || !this.multiDeviceCategories + }, + enableProfileId() { + return this.enableZigbeeFeatures + }, + enableNetworkId() { + return this.enableZigbeeFeatures + }, + enableSingleton() { + return this.enableZigbeeFeatures + }, + enableBounded() { + return this.enableZigbeeFeatures + }, + enableMultipleDevice() { + return this.enableMatterFeatures + }, + enablePrimaryDevice() { + return this.enableMatterFeatures + }, + enableParentEndpoint() { + return this.enableMatterFeatures + }, + enableFeature() { + return this.enableMatterFeatures + }, + enableServerOnly() { + return this.enableMatterFeatures && !this.enableZigbeeFeatures } - - this.enableProfileId = enableZigbeeFeatures - this.enableNetworkId = enableZigbeeFeatures - this.enableSingleton = enableZigbeeFeatures - this.enableBounded = enableZigbeeFeatures - this.enableMultipleDevice = enableMatterFeatures - this.enablePrimaryDevice = enableMatterFeatures - this.enableParentEndpoint = enableMatterFeatures - this.enableServerOnly = enableMatterFeatures & !enableZigbeeFeatures } } diff --git a/test/feature.test.js b/test/feature.test.js new file mode 100644 index 0000000000..6d88fe6993 --- /dev/null +++ b/test/feature.test.js @@ -0,0 +1,96 @@ +/** + * + * Copyright (c) 2020 Silicon Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * @jest-environment node + */ +const testUtil = require('./test-util') +const env = require('../src-electron/util/env') +const zclLoader = require('../src-electron/zcl/zcl-loader') +const dbApi = require('../src-electron/db/db-api') +const queryDeviceType = require('../src-electron/db/query-device-type') +const queryFeature = require('../src-electron/db/query-feature') + +let db +let ctx +let pkgId + +beforeAll(async () => { + env.setDevelopmentEnv() + let file = env.sqliteTestFile('query') + db = await dbApi.initDatabaseAndLoadSchema( + file, + env.schemaFile(), + env.zapVersion() + ) + // load Matter packages for testing features + ctx = await zclLoader.loadZcl(db, env.builtinMatterZclMetafile()) + pkgId = ctx.packageId +}, testUtil.timeout.medium()) + +afterAll(() => dbApi.closeDatabase(db), testUtil.timeout.short()) + +test( + 'Test get device type features', + async () => { + /** test with MA-dimmablelight device type, the only device type + associated with features in current xml file + it should have 3 device type features */ + let deviceTypeName = 'MA-dimmablelight' + let MA_Dimmablelight = await queryDeviceType.selectDeviceTypeByCodeAndName( + db, + pkgId, + 257, + deviceTypeName + ) + let deviceTypeFeatures = await queryFeature.getFeaturesByDeviceTypeRefs( + db, + [MA_Dimmablelight.id] + ) + + let expectedDeviceTypeFeatures = [ + { + cluster: 'On/Off', + name: 'Lighting', + conformance: 'M' + }, + { + cluster: 'Level Control', + name: 'OnOff', + conformance: 'M' + }, + { + cluster: 'Level Control', + name: 'Lighting', + conformance: 'M' + } + ] + + expect(deviceTypeFeatures.length).toBe(3) + + deviceTypeFeatures.forEach((feature) => { + expect(feature.deviceType == deviceTypeName) + let expected = expectedDeviceTypeFeatures.find( + (f) => + f.cluster == feature.cluster && + f.name == feature.name && + f.conformance == feature.conformance + ) + expect(expected).toBeTruthy() + }) + }, + testUtil.timeout.short() +) diff --git a/zcl-builtin/matter/data-model/chip/matter-devices.xml b/zcl-builtin/matter/data-model/chip/matter-devices.xml index bb8fb7b5ae..0518f48d84 100644 --- a/zcl-builtin/matter/data-model/chip/matter-devices.xml +++ b/zcl-builtin/matter/data-model/chip/matter-devices.xml @@ -311,7 +311,7 @@ limitations under the License. - + @@ -329,10 +329,10 @@ limitations under the License. - + - + @@ -2061,7 +2061,7 @@ limitations under the License. - + @@ -2083,14 +2083,14 @@ limitations under the License. - + - +