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 @@
+
+
+
+
+
+ Endpoint {{ this.endpointId[this.selectedEndpointId] }} Device Type
+ Features
+
+
+
+
+
+
+
+ {{ props.row.deviceType }}
+
+
+ {{ props.row.cluster }}
+
+
+ {{ getClusterSide(props.row) }}
+
+
+ {{ props.row.name }}
+
+
+ {{ props.row.code }}
+
+
+ {{ props.row.conformance }}
+
+
+ {{ props.row.bit }}
+
+
+ {{ props.row.description }}
+
+
+
+
+
+
+
+ {{ nodataMessage }}
+
+
+
+
+
+
+
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.
-
+
-
+