Skip to content

Commit c72231e

Browse files
authored
Feature Page Phase 2 (project-chip#1482)
- Enable users to toggle device type features. - When toggling a feature, automatically update the associated elements (attributes, commands, events) to correct conformance, and update the feature map attribute. - Show warnings and disable feature toggle if relevant elements have conformance that is unknown or too complex to handle. - Show detailed conformance warnings for elements if they're enabled state conflicts with conformance value.
1 parent 3879080 commit c72231e

34 files changed

+7977
-5100
lines changed

docs/api.md

+487-83
Large diffs are not rendered by default.

docs/zap-schema.svg

+2,396-2,421
Loading

src-electron/db/db-mapping.js

+47-5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ exports.map = {
100100
type: x.TYPE != 'array' ? x.TYPE : x.ARRAY_TYPE,
101101
side: x.SIDE,
102102
define: x.DEFINE,
103+
conformance: x.CONFORMANCE,
103104
min: x.MIN,
104105
max: x.MAX,
105106
minLength: x.MIN_LENGTH,
@@ -176,6 +177,7 @@ exports.map = {
176177
name: x.NAME,
177178
description: x.DESCRIPTION,
178179
side: x.SIDE,
180+
conformance: x.CONFORMANCE,
179181
isOptional: dbApi.fromDbBool(x.IS_OPTIONAL),
180182
isFabricSensitive: dbApi.fromDbBool(x.IS_FABRIC_SENSITIVE),
181183
priority: x.PRIORITY
@@ -196,6 +198,7 @@ exports.map = {
196198
description: x.DESCRIPTION,
197199
source: x.SOURCE,
198200
isOptional: dbApi.fromDbBool(x.IS_OPTIONAL),
201+
conformance: x.CONFORMANCE,
199202
mustUseTimedInvoke: dbApi.fromDbBool(x.MUST_USE_TIMED_INVOKE),
200203
isFabricScoped: dbApi.fromDbBool(x.IS_FABRIC_SCOPED),
201204
clusterCode: x.CLUSTER_CODE,
@@ -251,16 +254,20 @@ exports.map = {
251254
if (x == null) return undefined
252255
return {
253256
deviceType: x.DEVICE_TYPE_NAME,
257+
deviceTypeClusterId: x.DEVICE_TYPE_CLUSTER_ID,
258+
clusterRef: x.CLUSTER_REF,
254259
cluster: x.CLUSTER_NAME,
255260
includeServer: x.INCLUDE_SERVER,
256261
includeClient: x.INCLUDE_CLIENT,
257262
conformance: x.DEVICE_TYPE_CLUSTER_CONFORMANCE,
258-
id: x.FEATURE_ID,
263+
featureId: x.FEATURE_ID,
259264
name: x.FEATURE_NAME,
260265
code: x.CODE,
261266
bit: x.BIT,
262-
default_value: x.DEFAULT_VALUE,
263-
description: x.DESCRIPTION
267+
description: x.DESCRIPTION,
268+
endpointTypeClusterId: x.ENDPOINT_TYPE_CLUSTER_ID,
269+
featureMapAttributeId: x.FEATURE_MAP_ATTRIBUTE_ID,
270+
featureMapValue: x.FEATURE_MAP_VALUE
264271
}
265272
},
266273

@@ -585,7 +592,8 @@ exports.map = {
585592
featureName: x.FEATURE_NAME,
586593
featureBit: x.FEATURE_BIT,
587594
clusterId: x.CLUSTER_REF,
588-
composition: x.TYPE
595+
composition: x.TYPE,
596+
conformance: x.DEVICE_TYPE_CLUSTER_CONFORMANCE
589597
}
590598
},
591599
endpointTypeCluster: (x) => {
@@ -692,7 +700,10 @@ exports.map = {
692700
type: x.TYPE != 'array' ? x.TYPE : x.ARRAY_TYPE, // Attribute type
693701
apiMaturity: x.API_MATURITY,
694702
isChangeOmitted: dbApi.fromDbBool(x.IS_CHANGE_OMITTED),
695-
persistence: x.PERSISTENCE
703+
persistence: x.PERSISTENCE,
704+
reportMinInterval: x.REPORT_MIN_INTERVAL,
705+
reportMaxInterval: x.REPORT_MAX_INTERVAL,
706+
conformance: x.CONFORMANCE
696707
}
697708
},
698709

@@ -709,6 +720,23 @@ exports.map = {
709720
}
710721
},
711722

723+
endpointTypeCommandExtended: (x) => {
724+
if (x == null) return undefined
725+
return {
726+
id: x.COMMAND_ID,
727+
name: x.NAME, // Command Name
728+
clusterRef: x.CLUSTER_REF,
729+
commandRef: x.COMMAND_REF,
730+
incoming: dbApi.fromDbBool(x.INCOMING),
731+
outgoing: dbApi.fromDbBool(x.OUTGOING),
732+
isIncoming: dbApi.fromDbBool(x.IS_INCOMING),
733+
source: x.SOURCE,
734+
conformance: x.CONFORMANCE,
735+
endpointTypeRef: x.ENDPOINT_TYPE_REF,
736+
isEnabled: dbApi.fromDbBool(x.IS_ENABLED)
737+
}
738+
},
739+
712740
endpointTypeEvent: (x) => {
713741
if (x == null) return undefined
714742
return {
@@ -719,6 +747,20 @@ exports.map = {
719747
}
720748
},
721749

750+
endpointTypeEventExtended: (x) => {
751+
if (x == null) return undefined
752+
return {
753+
id: x.EVENT_ID,
754+
name: x.NAME, // Event Name
755+
clusterRef: x.CLUSTER_REF,
756+
eventRef: x.EVENT_REF,
757+
side: x.SIDE,
758+
conformance: x.CONFORMANCE,
759+
endpointTypeRef: x.ENDPOINT_TYPE_REF,
760+
included: dbApi.fromDbBool(x.INCLUDED)
761+
}
762+
},
763+
722764
packageExtension: (x) => {
723765
if (x == null) return undefined
724766
return {

src-electron/db/query-attribute.js

+39
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,43 @@ async function selectAttributeMappingsByPackageIds(db, packageIds) {
12481248
return rows.map(dbMapping.map.attributeMapping)
12491249
}
12501250

1251+
/**
1252+
* Get all attributes in an endpoint type cluster
1253+
* @param {*} db
1254+
* @param {*} endpointTyeClusterId
1255+
* @returns all attributes in an endpoint type cluster
1256+
*/
1257+
async function selectAttributesByEndpointTypeClusterId(
1258+
db,
1259+
endpointTyeClusterId
1260+
) {
1261+
let rows = await dbApi.dbAll(
1262+
db,
1263+
`
1264+
SELECT
1265+
ATTRIBUTE.ATTRIBUTE_ID,
1266+
ATTRIBUTE.NAME,
1267+
ATTRIBUTE.CLUSTER_REF,
1268+
ATTRIBUTE.SIDE,
1269+
ATTRIBUTE.CONFORMANCE,
1270+
ATTRIBUTE.REPORT_MIN_INTERVAL,
1271+
ATTRIBUTE.REPORT_MAX_INTERVAL,
1272+
ATTRIBUTE.REPORTABLE_CHANGE,
1273+
ENDPOINT_TYPE_ATTRIBUTE.INCLUDED
1274+
FROM
1275+
ATTRIBUTE
1276+
JOIN
1277+
ENDPOINT_TYPE_ATTRIBUTE
1278+
ON
1279+
ATTRIBUTE.ATTRIBUTE_ID = ENDPOINT_TYPE_ATTRIBUTE.ATTRIBUTE_REF
1280+
WHERE
1281+
ENDPOINT_TYPE_ATTRIBUTE.ENDPOINT_TYPE_CLUSTER_REF = ?
1282+
`,
1283+
[endpointTyeClusterId]
1284+
)
1285+
return rows.map(dbMapping.map.endpointTypeAttributeExtended)
1286+
}
1287+
12511288
exports.selectAllAttributeDetailsFromEnabledClusters = dbCache.cacheQuery(
12521289
selectAllAttributeDetailsFromEnabledClusters
12531290
)
@@ -1274,3 +1311,5 @@ exports.selectTokenAttributesForEndpoint = selectTokenAttributesForEndpoint
12741311
exports.selectAllUserTokenAttributes = selectAllUserTokenAttributes
12751312
exports.selectAttributeMappingsByPackageIds =
12761313
selectAttributeMappingsByPackageIds
1314+
exports.selectAttributesByEndpointTypeClusterId =
1315+
selectAttributesByEndpointTypeClusterId

src-electron/db/query-command.js

+48
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,7 @@ SELECT
10861086
COMMAND.DESCRIPTION,
10871087
COMMAND.SOURCE,
10881088
COMMAND.IS_OPTIONAL,
1089+
COMMAND.CONFORMANCE,
10891090
COMMAND.MUST_USE_TIMED_INVOKE,
10901091
COMMAND.IS_FABRIC_SCOPED,
10911092
COMMAND.RESPONSE_REF,
@@ -1326,6 +1327,7 @@ SELECT
13261327
COMMAND.DESCRIPTION,
13271328
COMMAND.SOURCE,
13281329
COMMAND.IS_OPTIONAL,
1330+
COMMAND.CONFORMANCE,
13291331
COMMAND.MUST_USE_TIMED_INVOKE,
13301332
COMMAND.IS_FABRIC_SCOPED,
13311333
COMMAND.RESPONSE_REF,
@@ -2046,6 +2048,50 @@ async function selectNonManufacturerSpecificCommandDetailsFromAllEndpointTypesAn
20462048
)
20472049
}
20482050

2051+
/**
2052+
* Get all commands in an endpoint type cluster
2053+
* Non-required commands are not loaded into ENDPOINT_TYPE_COMMAND table,
2054+
* so we need to load all commands by joining DEVICE_TYPE_COMMAND table
2055+
* @param {*} db
2056+
* @param {*} endpointTypeClusterId
2057+
* @param {*} deviceTypeClusterId
2058+
* @returns all commands in an endpoint type cluster
2059+
*/
2060+
async function selectCommandsByEndpointTypeClusterIdAndDeviceTypeClusterId(
2061+
db,
2062+
endpointTypeClusterId,
2063+
deviceTypeClusterId
2064+
) {
2065+
let rows = await dbApi.dbAll(
2066+
db,
2067+
`
2068+
SELECT
2069+
COMMAND.COMMAND_ID,
2070+
COMMAND.NAME,
2071+
COMMAND.CLUSTER_REF,
2072+
COMMAND.SOURCE,
2073+
COMMAND.CONFORMANCE,
2074+
COALESCE(ENDPOINT_TYPE_COMMAND.IS_ENABLED, 0) AS IS_ENABLED
2075+
FROM
2076+
COMMAND
2077+
JOIN
2078+
DEVICE_TYPE_CLUSTER
2079+
ON
2080+
COMMAND.CLUSTER_REF = DEVICE_TYPE_CLUSTER.CLUSTER_REF
2081+
LEFT JOIN
2082+
ENDPOINT_TYPE_COMMAND
2083+
ON
2084+
COMMAND.COMMAND_ID = ENDPOINT_TYPE_COMMAND.COMMAND_REF
2085+
AND
2086+
ENDPOINT_TYPE_COMMAND.ENDPOINT_TYPE_CLUSTER_REF = ?
2087+
WHERE
2088+
DEVICE_TYPE_CLUSTER.DEVICE_TYPE_CLUSTER_ID = ?
2089+
`,
2090+
[endpointTypeClusterId, deviceTypeClusterId]
2091+
)
2092+
return rows.map(dbMapping.map.endpointTypeCommandExtended)
2093+
}
2094+
20492095
exports.selectCliCommandCountFromEndpointTypeCluster =
20502096
selectCliCommandCountFromEndpointTypeCluster
20512097
exports.selectCliCommandsFromCluster = selectCliCommandsFromCluster
@@ -2098,3 +2144,5 @@ exports.selectAllOutgoingCommandsForCluster =
20982144
exports.selectEndpointTypeCommandsByEndpointTypeRefAndClusterRef =
20992145
selectEndpointTypeCommandsByEndpointTypeRefAndClusterRef
21002146
exports.duplicateEndpointTypeCommand = duplicateEndpointTypeCommand
2147+
exports.selectCommandsByEndpointTypeClusterIdAndDeviceTypeClusterId =
2148+
selectCommandsByEndpointTypeClusterIdAndDeviceTypeClusterId

src-electron/db/query-config.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,16 @@ async function insertOrUpdateAttributeState(
222222
staticAttribute.defaultValue == 0
223223
) {
224224
let featureMapDefaultValue = staticAttribute.defaultValue
225-
let mandatoryFeaturesOnEndpointTypeAndCluster =
225+
let featuresOnEndpointTypeAndCluster =
226226
await queryDeviceType.selectDeviceTypeFeaturesByEndpointTypeIdAndClusterId(
227227
db,
228228
endpointTypeId,
229229
clusterRef
230230
)
231-
let featureMapBitsToBeEnabled =
232-
mandatoryFeaturesOnEndpointTypeAndCluster.map((f) => f.featureBit)
231+
// only set featureMap bit to 1 for mandatory features
232+
let featureMapBitsToBeEnabled = featuresOnEndpointTypeAndCluster
233+
.filter((f) => f.conformance == 'M')
234+
.map((f) => f.featureBit)
233235
featureMapBitsToBeEnabled.forEach(
234236
(featureBit) =>
235237
(featureMapDefaultValue = featureMapDefaultValue | (1 << featureBit))

src-electron/db/query-device-type.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,8 @@ async function selectDeviceTypeFeaturesByEndpointTypeIdAndClusterId(
489489
FEATURE.NAME AS FEATURE_NAME,
490490
FEATURE.CODE AS FEATURE_CODE,
491491
FEATURE.BIT AS FEATURE_BIT,
492-
DEVICE_TYPE_CLUSTER.CLUSTER_REF
492+
DEVICE_TYPE_CLUSTER.CLUSTER_REF,
493+
DEVICE_TYPE_FEATURE.DEVICE_TYPE_CLUSTER_CONFORMANCE
493494
FROM
494495
ENDPOINT_TYPE_DEVICE AS ETD
495496
INNER JOIN

src-electron/db/query-event.js

+49
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ SELECT
113113
NAME,
114114
DESCRIPTION,
115115
SIDE,
116+
CONFORMANCE,
116117
IS_OPTIONAL,
117118
IS_FABRIC_SENSITIVE,
118119
PRIORITY
@@ -148,6 +149,7 @@ SELECT
148149
E.NAME,
149150
E.DESCRIPTION,
150151
E.SIDE,
152+
E.CONFORMANCE,
151153
E.IS_OPTIONAL,
152154
E.IS_FABRIC_SENSITIVE,
153155
E.PRIORITY
@@ -231,9 +233,56 @@ ORDER BY
231233
.then((rows) => rows.map(dbMapping.map.eventField))
232234
}
233235

236+
/**
237+
* Get all events in an endpoint type cluster
238+
* Disabled events are not loaded into ENDPOINT_TYPE_EVENT table,
239+
* so we need to load all events by joining DEVICE_TYPE_EVENT table
240+
*
241+
* @param {*} db
242+
* @param {*} endpointTypeClusterId
243+
* @param {*} deviceTypeClusterId
244+
* @returns all events in an endpoint type cluster
245+
*/
246+
async function selectEventsByEndpointTypeClusterIdAndDeviceTypeClusterId(
247+
db,
248+
endpointTypeClusterId,
249+
deviceTypeClusterId
250+
) {
251+
let rows = await dbApi.dbAll(
252+
db,
253+
`
254+
SELECT
255+
EVENT.EVENT_ID,
256+
EVENT.NAME,
257+
EVENT.CLUSTER_REF,
258+
EVENT.SIDE,
259+
EVENT.CONFORMANCE,
260+
COALESCE(ENDPOINT_TYPE_EVENT.INCLUDED, 0) AS INCLUDED
261+
FROM
262+
EVENT
263+
JOIN
264+
DEVICE_TYPE_CLUSTER
265+
ON
266+
EVENT.CLUSTER_REF = DEVICE_TYPE_CLUSTER.CLUSTER_REF
267+
LEFT JOIN
268+
ENDPOINT_TYPE_EVENT
269+
ON
270+
EVENT.EVENT_ID = ENDPOINT_TYPE_EVENT.EVENT_REF
271+
AND
272+
ENDPOINT_TYPE_EVENT.ENDPOINT_TYPE_CLUSTER_REF = ?
273+
WHERE
274+
DEVICE_TYPE_CLUSTER.DEVICE_TYPE_CLUSTER_ID = ?
275+
`,
276+
[endpointTypeClusterId, deviceTypeClusterId]
277+
)
278+
return rows.map(dbMapping.map.endpointTypeEventExtended)
279+
}
280+
234281
exports.selectEventsByClusterId = selectEventsByClusterId
235282
exports.selectAllEvents = selectAllEvents
236283
exports.selectAllEventFields = selectAllEventFields
284+
exports.selectEventsByEndpointTypeClusterIdAndDeviceTypeClusterId =
285+
selectEventsByEndpointTypeClusterIdAndDeviceTypeClusterId
237286
exports.selectEventFieldsByEventId = selectEventFieldsByEventId
238287
exports.selectEndpointTypeEventsByEndpointTypeRefAndClusterRef =
239288
selectEndpointTypeEventsByEndpointTypeRefAndClusterRef

0 commit comments

Comments
 (0)