Skip to content

Commit

Permalink
Adding device type constraints per endpoint in Matter (project-chip#1104
Browse files Browse the repository at this point in the history
)

- Add Class, Scope and superset to the device type table
- Updating the loader to insert the above into the database properly
- Create a trigger for the device type constraint which can be used while creating a new endpoint or updating an existing one
- Catching the above constraints and throwing those as notifications into the front end
- Updating the schema
- Github: ZAP#1090
  • Loading branch information
brdandu authored Aug 23, 2023
1 parent ba32890 commit 6eba50d
Show file tree
Hide file tree
Showing 6 changed files with 1,421 additions and 1,181 deletions.
2,359 changes: 1,203 additions & 1,156 deletions docs/zap-schema.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 81 additions & 14 deletions src-electron/db/query-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const queryDeviceType = require('./query-device-type')
const queryCommand = require('./query-command.js')
const restApi = require('../../src-shared/rest-api.js')
const _ = require('lodash')
const notification = require('../db/query-session-notification.js')

/**
* Promises to update the cluster include/exclude state.
Expand Down Expand Up @@ -563,11 +564,35 @@ async function insertEndpointType(
}

// Insert into endpoint_type_device
let etd = await dbApi.dbMultiInsert(
db,
'INSERT INTO ENDPOINT_TYPE_DEVICE (ENDPOINT_TYPE_REF, DEVICE_TYPE_REF, DEVICE_IDENTIFIER, DEVICE_VERSION, DEVICE_TYPE_ORDER) VALUES (?, ?, ?, ?, ?)',
newEndpointTypeIdDeviceCombination
)
try {
let etd = await dbApi.dbMultiInsert(
db,
'INSERT INTO ENDPOINT_TYPE_DEVICE (ENDPOINT_TYPE_REF, DEVICE_TYPE_REF, DEVICE_IDENTIFIER, DEVICE_VERSION, DEVICE_TYPE_ORDER) VALUES (?, ?, ?, ?, ?)',
newEndpointTypeIdDeviceCombination
)
} catch (err) {
// Catching an error from a sql trigger
let isErrorStringPresent = err.includes('Error:')
notification.setNotification(
db,
'ERROR',
isErrorStringPresent ? err.split('Error:')[1] : err,
sessionId,
1,
1
)
await dbApi.dbMultiInsert(
db,
'DELETE FROM ENDPOINT_TYPE_DEVICE WHERE ENDPOINT_TYPE_REF = ? AND DEVICE_TYPE_REF = ? AND DEVICE_IDENTIFIER = ? AND DEVICE_VERSION =? AND DEVICE_TYPE_ORDER = ?',
newEndpointTypeIdDeviceCombination
)
await dbApi.dbRemove(
db,
'DELETE FROM ENDPOINT_TYPE WHERE ENDPOINT_TYPE_ID = ?',
newEndpointTypeId
)
throw new Error(err)
}

// Resolve endpointDefaults based on device type order.
for (const dtRef of deviceTypeRefs) {
Expand Down Expand Up @@ -688,6 +713,17 @@ async function updateEndpointType(db, sessionId, endpointTypeId, changesArray) {
let existingDeviceVersions = existingDeviceTypeInfo.map(
(dt) => dt.DEVICE_VERSION
)

let existingEndpointTypeDeviceInfoValues = []
for (let j = 0; j < existingDeviceTypeInfo.length; j++) {
existingEndpointTypeDeviceInfoValues.push([
endpointTypeId,
existingDeviceTypeInfo[j]['DEVICE_TYPE_REF'],
existingDeviceTypeInfo[j]['DEVICE_VERSION'],
existingDeviceTypeInfo[j]['DEVICE_IDENTIFIER'],
j,
])
}
let updatedDeviceTypeRefs = updatedValues[0]
let updatedDeviceVersions = updatedValues[1]
let isDeviceTypeRefsUpdated =
Expand Down Expand Up @@ -723,15 +759,46 @@ async function updateEndpointType(db, sessionId, endpointTypeId, changesArray) {
])
}

await dbApi.dbMultiInsert(
db,
`
INSERT INTO
ENDPOINT_TYPE_DEVICE (ENDPOINT_TYPE_REF, DEVICE_TYPE_REF, DEVICE_VERSION, DEVICE_IDENTIFIER, DEVICE_TYPE_ORDER)
VALUES
(?, ?, ?, ?, ?)`,
endpointTypeDeviceInfoValues
)
try {
await dbApi.dbMultiInsert(
db,
`
INSERT INTO
ENDPOINT_TYPE_DEVICE (ENDPOINT_TYPE_REF, DEVICE_TYPE_REF, DEVICE_VERSION, DEVICE_IDENTIFIER, DEVICE_TYPE_ORDER)
VALUES
(?, ?, ?, ?, ?)`,
endpointTypeDeviceInfoValues
)
} catch (err) {
// Catching an error from a sql trigger
let isErrorStringPresent = err.includes('Error:')
notification.setNotification(
db,
'ERROR',
isErrorStringPresent ? err.split('Error:')[1] : err,
sessionId,
1,
1
)
// Delete endpoint type devices with the latest updates
let test = await dbApi.dbMultiInsert(
db,
'DELETE FROM ENDPOINT_TYPE_DEVICE WHERE ENDPOINT_TYPE_REF = ? AND DEVICE_TYPE_REF = ? AND DEVICE_VERSION =? AND DEVICE_IDENTIFIER = ? AND DEVICE_TYPE_ORDER = ?',
endpointTypeDeviceInfoValues
)

// Re add the old device types on the endpoint before the update
await dbApi.dbMultiInsert(
db,
`
INSERT INTO
ENDPOINT_TYPE_DEVICE (ENDPOINT_TYPE_REF, DEVICE_TYPE_REF, DEVICE_VERSION, DEVICE_IDENTIFIER, DEVICE_TYPE_ORDER)
VALUES
(?, ?, ?, ?, ?)`,
existingEndpointTypeDeviceInfoValues
)
throw new Error(err)
}

// When updating the zcl device types, overwrite on top of existing configuration
// Note: Here the existing selections are not removed. For eg: the clusters which
Expand Down
13 changes: 11 additions & 2 deletions src-electron/db/query-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,13 @@ async function insertClusterExtensions(db, packageId, knownPackages, data) {
exist. Check clusterExtension meta data in xml file.
Cluster Code: ${data[i].code}`
env.logWarning(message)
queryNotification.setNotification(db, "WARNING", message, packageId, 2)
queryNotification.setNotification(
db,
'WARNING',
message,
packageId,
2
)
}
}
let pCommand = insertCommands(db, packageId, commands)
Expand Down Expand Up @@ -831,7 +837,7 @@ async function insertDeviceTypes(db, packageId, data) {
return dbApi
.dbMultiInsert(
db,
'INSERT INTO DEVICE_TYPE (PACKAGE_REF, DOMAIN, CODE, PROFILE_ID, NAME, DESCRIPTION) VALUES (?, ?, ?, ?, ?, ?)',
'INSERT INTO DEVICE_TYPE (PACKAGE_REF, DOMAIN, CODE, PROFILE_ID, NAME, DESCRIPTION, CLASS, SCOPE, SUPERSET) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
data.map((dt) => {
return [
packageId,
Expand All @@ -840,6 +846,9 @@ async function insertDeviceTypes(db, packageId, data) {
dt.profileId,
dt.name,
dt.description,
dt.class,
dt.scope,
dt.superset,
]
})
)
Expand Down
1 change: 1 addition & 0 deletions src-electron/db/query-session-notification.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 80 additions & 0 deletions src-electron/db/zap-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ CREATE TABLE IF NOT EXISTS "DEVICE_TYPE" (
"PROFILE_ID" integer,
"NAME" text,
"DESCRIPTION" text,
"CLASS" text,
"SCOPE" text,
"SUPERSET" text,
foreign key (PACKAGE_REF) references PACKAGE(PACKAGE_ID) on delete cascade
);
/*
Expand Down Expand Up @@ -736,6 +739,83 @@ CREATE TABLE IF NOT EXISTS "ENDPOINT_TYPE_DEVICE" (
UNIQUE("ENDPOINT_TYPE_REF", "DEVICE_TYPE_REF")
);

/**
SQL Trigger for device type triggers per endpoint.
From Matter Data Model Spec 9.2 Endpoint Composition
Each simple endpoint SHALL support only one Application device type with these exceptions:
- The endpoint MAY support additional device types which are subsets of the Application
device type (the superset).
- The endpoint MAY support additional device types (application, utility or node device types)
as defined by each additional device type.
*/
CREATE TRIGGER ENDPOINT_TYPE_SIMPLE_DEVICE_CHECK
BEFORE
INSERT ON ENDPOINT_TYPE_DEVICE
WHEN
(SELECT
CLASS
FROM
DEVICE_TYPE
WHERE
DEVICE_TYPE.DEVICE_TYPE_ID = NEW.DEVICE_TYPE_REF) = "Simple"
AND
((SELECT
CLASS
FROM
DEVICE_TYPE
WHERE
DEVICE_TYPE.DEVICE_TYPE_ID = NEW.DEVICE_TYPE_REF)
IN
(SELECT
CLASS
FROM
DEVICE_TYPE
INNER JOIN
ENDPOINT_TYPE_DEVICE
ON
DEVICE_TYPE.DEVICE_TYPE_ID = ENDPOINT_TYPE_DEVICE.DEVICE_TYPE_REF
WHERE
ENDPOINT_TYPE_DEVICE.ENDPOINT_TYPE_REF = NEW.ENDPOINT_TYPE_REF))
AND
((SELECT
SUPERSET
FROM
DEVICE_TYPE
WHERE
DEVICE_TYPE.DEVICE_TYPE_ID = NEW.DEVICE_TYPE_REF)
NOT IN
(SELECT
DESCRIPTION
FROM
DEVICE_TYPE
INNER JOIN
ENDPOINT_TYPE_DEVICE
ON
DEVICE_TYPE.DEVICE_TYPE_ID = ENDPOINT_TYPE_DEVICE.DEVICE_TYPE_REF
WHERE
ENDPOINT_TYPE_DEVICE.ENDPOINT_TYPE_REF = NEW.ENDPOINT_TYPE_REF))
AND
((SELECT
DESCRIPTION
FROM
DEVICE_TYPE
WHERE
DEVICE_TYPE.DEVICE_TYPE_ID = NEW.DEVICE_TYPE_REF)
NOT IN
(SELECT
SUPERSET
FROM
DEVICE_TYPE
INNER JOIN
ENDPOINT_TYPE_DEVICE
ON
DEVICE_TYPE.DEVICE_TYPE_ID = ENDPOINT_TYPE_DEVICE.DEVICE_TYPE_REF
WHERE
ENDPOINT_TYPE_DEVICE.ENDPOINT_TYPE_REF = NEW.ENDPOINT_TYPE_REF))
BEGIN
SELECT RAISE(ROLLBACK, 'Simple endpoint cannot have more than one application device type');
END;

/*
ENDPOINT table contains the toplevel configured endpoints.
*/
Expand Down
54 changes: 45 additions & 9 deletions src-electron/zcl/zcl-loader-silabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1041,8 +1041,13 @@ async function processDataType(
env.logError(
'Could not find the discriminator for the data type: ' + dataType
)
queryPackageNotification.setNotification(dnb, "ERROR",
'Could not find the discriminator for the data type: ' + dataType, packageId, 1)
queryPackageNotification.setNotification(
dnb,
'ERROR',
'Could not find the discriminator for the data type: ' + dataType,
packageId,
1
)
}
}

Expand Down Expand Up @@ -1207,10 +1212,19 @@ function prepareEnumOrBitmap(db, packageId, a, dataType, typeMap) {
(a.$.type.toLowerCase().includes('int') ||
a.$.type.toLowerCase().includes(dbEnum.zclType.bitmap))
) {
let message = 'Check type contradiction in XML metadata for ' +
a.$.name + ' with type ' + a.$.type
let message =
'Check type contradiction in XML metadata for ' +
a.$.name +
' with type ' +
a.$.type
env.logWarning(message)
queryPackageNotification.setNotification(db, "WARNING", message, packageId, 2)
queryPackageNotification.setNotification(
db,
'WARNING',
message,
packageId,
2
)
a.$.type = 'enum' + a.$.type.toLowerCase().match(/\d+/g).join('')
}
return {
Expand Down Expand Up @@ -1238,7 +1252,13 @@ async function processEnum(db, filePath, packageId, knownPackages, data) {
db,
knownPackages,
data.map((x) =>
prepareEnumOrBitmap(db, packageId, x, typeMap.get(dbEnum.zclType.enum), typeMap)
prepareEnumOrBitmap(
db,
packageId,
x,
typeMap.get(dbEnum.zclType.enum),
typeMap
)
)
)
}
Expand Down Expand Up @@ -1325,7 +1345,13 @@ async function processBitmap(db, filePath, packageId, knownPackages, data) {
db,
knownPackages,
data.map((x) =>
prepareEnumOrBitmap(db, packageId, x, typeMap.get(dbEnum.zclType.bitmap), typeMap)
prepareEnumOrBitmap(
db,
packageId,
x,
typeMap.get(dbEnum.zclType.bitmap),
typeMap
)
)
)
}
Expand Down Expand Up @@ -1486,6 +1512,9 @@ function prepareDeviceType(deviceType) {
domain: deviceType.domain[0],
name: deviceType.name[0],
description: deviceType.typeName[0],
class: deviceType.class ? deviceType.class[0] : '',
scope: deviceType.scope ? deviceType.scope[0] : '',
superset: deviceType.superset ? deviceType.superset[0] : '',
}
if ('clusters' in deviceType) {
ret.clusters = []
Expand Down Expand Up @@ -2161,7 +2190,14 @@ async function loadIndividualSilabsFile(db, filePath, sessionId) {
return { succeeded: true, packageId: pkgId }
} catch (err) {
env.logError(`Error reading xml file: ${filePath}\n` + err.message)
querySessionNotification.setNotification(db, "ERROR", `Error reading xml file: ${filePath}\n` + err.message, sessionId, 1, 0)
querySessionNotification.setNotification(
db,
'ERROR',
`Error reading xml file: ${filePath}\n` + err.message,
sessionId,
1,
0
)
return { succeeded: false, err: err }
}
}
Expand Down Expand Up @@ -2303,7 +2339,7 @@ async function loadSilabsZcl(db, metafile, isJson = false) {
}
} catch (err) {
env.logError(err)
queryPackageNotification.setNotification(db, "ERROR", err, ctx.packageId, 1)
queryPackageNotification.setNotification(db, 'ERROR', err, ctx.packageId, 1)
throw err
} finally {
if (!isTransactionAlreadyExisting) await dbApi.dbCommit(db)
Expand Down

0 comments on commit 6eba50d

Please sign in to comment.