Skip to content

Commit af9340b

Browse files
authored
Adding upgrade rule mechanism in ZAP for SDKs (#1589)
- Users can now include an upgrade rules json file in the zcl.json file which has a list of all the upgrade rules that may be needed when a user updates an existing app from one version of the GSDK to another. - The upgrade rules json file lists a set of upgrade rules scripts with their priority as can be seen in upgrade-rules.json file. The scripts are run in order of priority with lower number signifying higher priority - The upgrade rules return an object including a message and status which helps determine if an upgrade rules was actually executed or not. These results can be output into a yaml file if required for post analysis on which upgrade rules ran on a certain .zap file once the GSDK was updated. - Adding unit tests to update cluster revision attribute of all clusters - Handling the multi protocol use case and adding unit tests that make sure that only the appropriate endpoints are updated when upgrade rules json files from both zigbee and matter exist - Adding unit tests for matter only .zap file - Adding documentation on how to add upgrade rules to your SDK - Adding the update keys available to the context in postLoad - Add unit tests to check the order in which the upgrade rules were run - Github: ZAP#1020
1 parent e4b22bb commit af9340b

17 files changed

+4349
-49
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ This software is licensed under [Apache 2.0 license](LICENSE.txt).
4040
- [ZAP Template Helpers](docs/helpers.md)
4141
- [ZAP External Template Helpers](docs/external-helpers.md)
4242
- [ZAP file Extensions](docs/zap-file-extensions.md)
43+
- [Upgrading ZAP files with GSDK upgrades](docs/zap-file-upgrade.md)
4344
- [FAQ/Developer dependencies](docs/faq.md)
4445
- [Release instructions](docs/release.md)
4546
- [Development Instructions](docs/development-instructions.md)

docs/zap-file-upgrade.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Upgrading ZAP Files with SDK upgrades
2+
3+
## Overview
4+
5+
Upgrading ZAP files is a critical step when transitioning to newer versions of the SDK. This process ensures compatibility with the latest features, bug fixes, and improvements provided by the SDK. By following the upgrade guidelines, you can maintain the integrity of your ZAP configuration files and avoid potential issues during development or deployment. This document outlines the steps and best practices for performing ZAP file upgrades effectively.
6+
7+
## How to upgrade the ZAP File?
8+
9+
Run the following command to update your .zap file
10+
11+
```bash
12+
"${Path to ZAP executable} upgrade --results ${path to .yaml file to see results of upgrade} -d ${directory containing ZAP files} --zcl ${path to zcl json file} --generationTemplate ${path to templates json file} --noLoadingFailure"
13+
```
14+
15+
## How to add upgrade rules for .zap files through your SDK
16+
17+
### - Create an `upgrade-rules.json` file with the following information if one already does not exist
18+
19+
```json
20+
{
21+
"version": 1,
22+
"description": "Upgrade Rules for Zigbee .zap files",
23+
"category": "matter",
24+
"upgradeRuleScripts": [
25+
{
26+
"path": "../../test/resource/test-matter-attribute-default-value-update.js",
27+
"priority": 101
28+
},
29+
{
30+
"path": "../../test/resource/test-attribute-default-value-update.js",
31+
"priority": 100
32+
}
33+
]
34+
}
35+
```
36+
37+
**category**: Determines that these upgrade rules need to run for matter.
38+
39+
**upgradeRuleScripts**: List of upgrade rules to run on .zap files. Includes the relative path from upgrade-rules.json to the upgrade scripts written in Javascript. Priority determines the order of execution for the upgrade rules. The scripts are run in order of priority with lower number signifying higher priority.
40+
41+
### - Add relative path to the upgrade-rules.json from your zcl.json file
42+
43+
```json
44+
"upgradeRules": "./upgrade-rules-matter.json"
45+
```
46+
47+
### - Creating your own javascript upgrade rule
48+
49+
Add a postLoad function as below with api and context as parameters. Api argument gives access to all APIs that can be used within the `postLoad` function. [Refer to the post-import API documentation](../src-electron/util/post-import-api.js) . Context gives the state of the Data-Model/ZCL with respect to the .zap file that will be passied along to the API calls.
50+
51+
```javascript
52+
// Example upgrade rule to update default value of Level Control cluster attribute in Matter.
53+
async function postLoad(api, context) {
54+
let resMsg = ''
55+
let epts = await api.endpoints(context)
56+
for (let i = 0; i < epts.length; i++) {
57+
let clusters = await api.clusters(context, epts[i])
58+
for (let j = 0; j < clusters.length; j++) {
59+
if (clusters[j].code == '0x0008') {
60+
let attributes = await api.attributes(context, epts[i], clusters[j])
61+
for (let k = 0; k < attributes.length; k++) {
62+
let attributeCode = parseInt(attributes[k].code)
63+
let attributeValue = parseInt(attributes[k].defaultValue)
64+
if (
65+
attributeCode == 0 &&
66+
(attributeValue == 0 || !attributeValue || attributeValue == 'null')
67+
) {
68+
let params = [
69+
{
70+
key: context.updateKey.attributeDefault,
71+
value: 10
72+
}
73+
]
74+
await api.updateAttribute(
75+
context,
76+
epts[i],
77+
clusters[j],
78+
attributes[k],
79+
params
80+
)
81+
resMsg += `Current Value attribute's default value updated to 10 for Level Control cluster on endpoint ${epts[i].endpointIdentifier} ${epts[i].category}\n`
82+
}
83+
}
84+
}
85+
}
86+
}
87+
return { message: resMsg, status: 'automatic' } // Status can be 'nothing', 'automatic', 'user_verification', 'impossible'.
88+
}
89+
90+
exports.postLoad = postLoad
91+
```

src-electron/db/query-endpoint.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,39 @@ const dbEnum = require('../../src-shared/db-enum.js')
3131
*
3232
* @param {*} db
3333
* @param {*} sessionId
34+
* @param {*} category
3435
* @returns Promise resolving into all endpoints.
3536
*/
36-
async function selectAllEndpoints(db, sessionId) {
37+
async function selectAllEndpoints(db, sessionId, category = null) {
38+
let categorySqlJoinString = category
39+
? `
40+
LEFT JOIN
41+
ENDPOINT_TYPE
42+
ON
43+
E1.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID
44+
LEFT JOIN
45+
ENDPOINT_TYPE_DEVICE
46+
ON
47+
ENDPOINT_TYPE_DEVICE.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID
48+
LEFT JOIN
49+
DEVICE_TYPE
50+
ON
51+
ENDPOINT_TYPE_DEVICE.DEVICE_TYPE_REF = DEVICE_TYPE.DEVICE_TYPE_ID
52+
LEFT JOIN
53+
PACKAGE
54+
ON
55+
PACKAGE.PACKAGE_ID = DEVICE_TYPE.PACKAGE_REF`
56+
: ``
57+
58+
let categorySqlSelectString = category
59+
? `,
60+
PACKAGE.CATEGORY`
61+
: ``
62+
63+
let categorySqlWhereString = category
64+
? `AND
65+
PACKAGE.CATEGORY = '${category}'`
66+
: ``
3767
let rows = await dbApi.dbAll(
3868
db,
3969
`
@@ -46,18 +76,23 @@ SELECT
4676
E1.NETWORK_IDENTIFIER,
4777
E2.ENDPOINT_ID AS PARENT_ENDPOINT_REF,
4878
E2.ENDPOINT_IDENTIFIER AS PARENT_ENDPOINT_IDENTIFIER
79+
${categorySqlSelectString}
4980
FROM
5081
ENDPOINT AS E1
5182
LEFT JOIN
5283
ENDPOINT AS E2
5384
ON
5485
E2.ENDPOINT_ID = E1.PARENT_ENDPOINT_REF
55-
WHERE E1.SESSION_REF = ?
56-
ORDER BY E1.ENDPOINT_IDENTIFIER
86+
${categorySqlJoinString}
87+
WHERE
88+
E1.SESSION_REF = ?
89+
${categorySqlWhereString}
90+
ORDER BY
91+
E1.ENDPOINT_IDENTIFIER
5792
`,
5893
[sessionId]
5994
)
60-
return rows.map(dbMapping.map.endpoint)
95+
return rows.map(dbMapping.map.endpointExtended)
6196
}
6297

6398
/**
@@ -197,6 +232,7 @@ ORDER BY C.CODE
197232
return rows.map((row) => {
198233
return {
199234
clusterId: row['CLUSTER_ID'],
235+
id: row['CLUSTER_ID'],
200236
endpointTypeId: row['ENDPOINT_TYPE_REF'],
201237
endpointTypeClusterId: row['ENDPOINT_TYPE_CLUSTER_ID'],
202238
hexCode: '0x' + bin.int16ToHex(row['CODE']),

src-electron/importexport/import.js

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
*/
2424

2525
const fsp = require('fs').promises
26+
const fs = require('fs')
27+
const path = require('path')
2628
const importIsc = require('./import-isc.js')
2729
const importJson = require('./import-json.js')
2830
const dbApi = require('../db/db-api.js')
@@ -66,19 +68,16 @@ async function readDataFromFile(filePath, defaultZclMetafile) {
6668
*
6769
* @param {*} db
6870
* @param {*} sessionId
69-
* @param {*} scriptFile
71+
* @param {*} scriptInfo
7072
* @returns Promise of function execution.
7173
*/
72-
async function executePostImportScript(db, sessionId, scriptFile) {
74+
async function executePostImportScript(db, sessionId, scriptInfo) {
7375
let context = {
7476
db: db,
75-
sessionId: sessionId
77+
sessionId: sessionId,
78+
script: scriptInfo
7679
}
77-
return script.executeScriptFunction(
78-
script.functions.postLoad,
79-
context,
80-
scriptFile
81-
)
80+
return script.executeScriptFunction(script.functions.postLoad, context)
8281
}
8382

8483
/**
@@ -232,11 +231,32 @@ async function importDataFromFile(
232231
options.upgradeTemplatePackages
233232
)
234233
if (options.postImportScript != null) {
235-
await executePostImportScript(
236-
db,
237-
loaderResult.sessionId,
238-
options.postImportScript
239-
)
234+
await executePostImportScript(db, loaderResult.sessionId, {
235+
path: path.resolve(options.postImportScript),
236+
category: null // When running individual scripts no need for category
237+
})
238+
}
239+
if (options.upgradeRuleScripts != null) {
240+
const upgradeScripts = options.upgradeRuleScripts
241+
let upgradeMessages = []
242+
for (let i = 0; i < upgradeScripts.length; i++) {
243+
let upgradeScript = upgradeScripts[i]
244+
if (fs.existsSync(upgradeScript.path)) {
245+
let upgradeMessage = await executePostImportScript(
246+
db,
247+
loaderResult.sessionId,
248+
upgradeScript
249+
)
250+
if (upgradeMessage) {
251+
upgradeMessages.push(upgradeMessage)
252+
}
253+
} else {
254+
throw new Error(
255+
`Post import script path does not exist: ${upgradeScript.path}`
256+
)
257+
}
258+
}
259+
loaderResult.upgradeMessages = upgradeMessages
240260
}
241261
return loaderResult
242262
} finally {

src-electron/main-process/startup.js

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,57 @@ async function upgradeZapFile(argv, options) {
312312
dbEnum.packageType.genTemplatesJson
313313
)
314314

315+
let upgradeRules = []
316+
// If more than one upgrade package is present then it is a multiprotocol
317+
// application so upgrade rules should be added to the corresponding endpoints.
318+
let isMultiProtocol = upgradeZclPackages.length > 1
319+
for (const pkg of upgradeZclPackages) {
320+
if (pkg.path) {
321+
try {
322+
const jsonData = JSON.parse(fs.readFileSync(pkg.path, 'utf-8'))
323+
if (jsonData.upgradeRules !== undefined) {
324+
const upgradeRulesJsonPath = path.resolve(
325+
path.dirname(pkg.path),
326+
jsonData.upgradeRules
327+
)
328+
try {
329+
const upgradeRulesData = JSON.parse(
330+
fs.readFileSync(upgradeRulesJsonPath, 'utf-8')
331+
)
332+
// Sorting upgrade rules by priority and then run them
333+
upgradeRulesData.upgradeRuleScripts
334+
.sort((a, b) => a.priority - b.priority)
335+
.forEach((ur) => {
336+
upgradeRules.push({
337+
path: path.resolve(path.dirname(pkg.path), ur.path),
338+
category: isMultiProtocol ? upgradeRulesData.category : null
339+
})
340+
})
341+
} catch (error) {
342+
console.error(
343+
`Error reading or parsing upgrade rules from path ${upgradeRulesJsonPath}:`,
344+
error
345+
)
346+
}
347+
}
348+
} catch (error) {
349+
console.error(
350+
`Error reading or parsing JSON from path ${pkg.path}:`,
351+
error
352+
)
353+
}
354+
}
355+
}
356+
315357
let importResult = await importJs.importDataFromFile(db, zapFile, {
316358
defaultZclMetafile: argv.zclProperties,
317359
postImportScript: argv.postImportScript,
318360
packageMatch: argv.packageMatch,
319361
upgradeZclPackages: upgradeZclPackages,
320-
upgradeTemplatePackages: upgradeTemplatePackages
362+
upgradeTemplatePackages: upgradeTemplatePackages,
363+
upgradeRuleScripts: upgradeRules // Used to apply all the upgrade rules to the .zap file
321364
})
365+
322366
let sessionId = importResult.sessionId
323367
await util.ensurePackagesAndPopulateSessionOptions(db, sessionId, {
324368
zcl: argv.zclProperties,
@@ -345,15 +389,23 @@ async function upgradeZapFile(argv, options) {
345389
fileFormat: argv.saveFileFormat
346390
})
347391
options.logger(` 👉 write out: ${outputPath}`)
392+
try {
393+
if (upgrade_results != null) {
394+
if (!fs.existsSync(path.dirname(upgrade_results))) {
395+
fs.mkdirSync(path.dirname(upgrade_results), { recursive: true })
396+
}
397+
await writeConversionResultsFile(
398+
upgrade_results,
399+
importResult.upgradeMessages
400+
)
401+
}
402+
options.logger(` 👉 write out: ${upgrade_results}`)
403+
} catch (error) {
404+
options.logger(` ⚠️ failed to write out: ${upgrade_results}`)
405+
}
406+
options.logger('😎 Upgrade done!')
348407
}
349-
try {
350-
if (upgrade_results != null)
351-
await writeConversionResultsFile(upgrade_results)
352-
options.logger(` 👉 write out: ${upgrade_results}`)
353-
} catch (error) {
354-
options.logger(` ⚠️ failed to write out: ${upgrade_results}`)
355-
}
356-
options.logger('😎 Upgrade done!')
408+
357409
if (options.quitFunction != null) {
358410
options.quitFunction()
359411
}
@@ -468,20 +520,25 @@ async function startConvert(argv, options) {
468520
* Write conversion results into file given.
469521
*
470522
* @param {*} file
523+
* @param {*} messages
471524
* @returns promise of a file write operation.
472525
*/
473-
async function writeConversionResultsFile(file) {
526+
async function writeConversionResultsFile(file, messages = null) {
474527
return fsp.writeFile(
475528
file,
476-
YAML.stringify({
477-
upgrade_results: [
478-
{
479-
message:
480-
'ZCL Advanced Platform (ZAP) configuration has been successfully upgraded.',
481-
status: 'automatic'
482-
}
483-
]
484-
})
529+
messages
530+
? YAML.stringify({
531+
upgrade_results: messages
532+
})
533+
: YAML.stringify({
534+
upgrade_results: [
535+
{
536+
message:
537+
'ZCL Advanced Platform (ZAP) configuration has been successfully upgraded.',
538+
status: 'automatic'
539+
}
540+
]
541+
})
485542
)
486543
}
487544

0 commit comments

Comments
 (0)