homey-lib 2.45.4 → 2.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/app/schema.d.ts +39 -0
- package/assets/app/schema.json +136 -0
- package/helpers/index.js +10 -0
- package/index.js +1 -0
- package/lib/App/index.js +84 -0
- package/lib/Util/file.js +84 -0
- package/lib/Util/index.js +62 -0
- package/lib/Util/zigbee.js +225 -0
- package/package.json +1 -1
- package/types/assets/app/schema.d.ts +39 -0
- package/types/helpers/index.d.ts +1 -0
- package/types/helpers/index.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/lib/App/index.d.ts.map +1 -1
- package/types/lib/Util/file.d.ts +27 -0
- package/types/lib/Util/file.d.ts.map +1 -0
- package/types/lib/Util/index.d.ts +53 -0
- package/types/lib/Util/index.d.ts.map +1 -1
- package/types/lib/Util/zigbee.d.ts +88 -0
- package/types/lib/Util/zigbee.d.ts.map +1 -0
- package/webpack/index.js +1 -1
- package/webpack.config.js +1 -0
package/assets/app/schema.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ export type DriverSettings = (
|
|
|
80
80
|
[k: string]: unknown;
|
|
81
81
|
}
|
|
82
82
|
)[];
|
|
83
|
+
export type Integrity = string;
|
|
83
84
|
export type AppSettings = (
|
|
84
85
|
| {
|
|
85
86
|
type: "text" | "password" | "textarea" | "label";
|
|
@@ -247,6 +248,7 @@ export interface App {
|
|
|
247
248
|
[k: string]: unknown;
|
|
248
249
|
}[];
|
|
249
250
|
settings?: DriverSettings;
|
|
251
|
+
firmwareUpdates?: ZigbeeFirmwareUpdates;
|
|
250
252
|
gtin?: string | string[];
|
|
251
253
|
matter?: MatterDevice;
|
|
252
254
|
zwave?: ZwaveDevice;
|
|
@@ -373,6 +375,43 @@ export interface ZwaveSetting {
|
|
|
373
375
|
signed?: boolean;
|
|
374
376
|
[k: string]: unknown;
|
|
375
377
|
}
|
|
378
|
+
export interface ZigbeeFirmwareUpdates {
|
|
379
|
+
queryNextImageTimeout?: number;
|
|
380
|
+
minImageBlockPeriod?: number;
|
|
381
|
+
maxImageBlockSize?: number;
|
|
382
|
+
imageBlockRequestTimeout?: number;
|
|
383
|
+
upgradeEndRequestTimeout?: number;
|
|
384
|
+
upgradeEndDelay?: number;
|
|
385
|
+
postUpgradeAnnounceTimeout?: number;
|
|
386
|
+
/**
|
|
387
|
+
* @minItems 1
|
|
388
|
+
*/
|
|
389
|
+
updates: [ZigbeeFirmwareUpdate, ...ZigbeeFirmwareUpdate[]];
|
|
390
|
+
}
|
|
391
|
+
export interface ZigbeeFirmwareUpdate {
|
|
392
|
+
changelog: I18NObject;
|
|
393
|
+
device: ZigbeeFirmwareUpdateDevice;
|
|
394
|
+
/**
|
|
395
|
+
* @minItems 1
|
|
396
|
+
*/
|
|
397
|
+
files: [ZigbeeFirmwareUpdateFile, ...ZigbeeFirmwareUpdateFile[]];
|
|
398
|
+
}
|
|
399
|
+
export interface ZigbeeFirmwareUpdateDevice {
|
|
400
|
+
manufacturerName: string | string[];
|
|
401
|
+
productId: string | string[];
|
|
402
|
+
}
|
|
403
|
+
export interface ZigbeeFirmwareUpdateFile {
|
|
404
|
+
fileVersion: number;
|
|
405
|
+
imageType: number;
|
|
406
|
+
manufacturerCode: number;
|
|
407
|
+
minFileVersion?: number;
|
|
408
|
+
maxFileVersion?: number;
|
|
409
|
+
minHardwareVersion?: number;
|
|
410
|
+
maxHardwareVersion?: number;
|
|
411
|
+
size: number;
|
|
412
|
+
name: string;
|
|
413
|
+
integrity: Integrity;
|
|
414
|
+
}
|
|
376
415
|
export interface MatterDevice {
|
|
377
416
|
vendorId: number | number[];
|
|
378
417
|
productId: number | number[];
|
package/assets/app/schema.json
CHANGED
|
@@ -32,6 +32,135 @@
|
|
|
32
32
|
},
|
|
33
33
|
"additionalProperties": false
|
|
34
34
|
},
|
|
35
|
+
"integrity": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"pattern": "^(blake2b512|blake2s256|sha256|sha384|sha512|sha512-256|sha3-256|sha3-384|sha3-512):[0-9a-fA-F]+$"
|
|
38
|
+
},
|
|
39
|
+
"zigbee-firmware-update-device": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"required": ["manufacturerName", "productId"],
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"properties": {
|
|
44
|
+
"manufacturerName": {
|
|
45
|
+
"oneOf": [
|
|
46
|
+
{
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "string"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"productId": {
|
|
58
|
+
"oneOf": [
|
|
59
|
+
{
|
|
60
|
+
"type": "string"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"zigbee-firmware-update-file": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"required": ["fileVersion", "size", "imageType", "manufacturerCode", "name", "integrity"],
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"properties": {
|
|
77
|
+
"fileVersion": {
|
|
78
|
+
"type": "number"
|
|
79
|
+
},
|
|
80
|
+
"imageType": {
|
|
81
|
+
"type": "number"
|
|
82
|
+
},
|
|
83
|
+
"manufacturerCode": {
|
|
84
|
+
"type": "number"
|
|
85
|
+
},
|
|
86
|
+
"minFileVersion": {
|
|
87
|
+
"type": "number"
|
|
88
|
+
},
|
|
89
|
+
"maxFileVersion": {
|
|
90
|
+
"type": "number"
|
|
91
|
+
},
|
|
92
|
+
"minHardwareVersion": {
|
|
93
|
+
"type": "number"
|
|
94
|
+
},
|
|
95
|
+
"maxHardwareVersion": {
|
|
96
|
+
"type": "number"
|
|
97
|
+
},
|
|
98
|
+
"size": {
|
|
99
|
+
"type": "number"
|
|
100
|
+
},
|
|
101
|
+
"name": {
|
|
102
|
+
"type": "string"
|
|
103
|
+
},
|
|
104
|
+
"integrity": {
|
|
105
|
+
"$ref": "#/definitions/integrity"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"zigbee-firmware-update": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"required": ["changelog", "device", "files"],
|
|
112
|
+
"additionalProperties": false,
|
|
113
|
+
"properties": {
|
|
114
|
+
"changelog": {
|
|
115
|
+
"$ref": "#/definitions/i18nObject"
|
|
116
|
+
},
|
|
117
|
+
"device": {
|
|
118
|
+
"$ref": "#/definitions/zigbee-firmware-update-device"
|
|
119
|
+
},
|
|
120
|
+
"files": {
|
|
121
|
+
"type": "array",
|
|
122
|
+
"minItems": 1,
|
|
123
|
+
"items": {
|
|
124
|
+
"$ref": "#/definitions/zigbee-firmware-update-file"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"zigbee-firmware-updates": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"additionalProperties": false,
|
|
132
|
+
"required": ["updates"],
|
|
133
|
+
"properties": {
|
|
134
|
+
"queryNextImageTimeout": {
|
|
135
|
+
"type": "number"
|
|
136
|
+
},
|
|
137
|
+
"minImageBlockPeriod": {
|
|
138
|
+
"type": "number"
|
|
139
|
+
},
|
|
140
|
+
"maxImageBlockSize": {
|
|
141
|
+
"type": "number"
|
|
142
|
+
},
|
|
143
|
+
"imageBlockRequestTimeout": {
|
|
144
|
+
"type": "number"
|
|
145
|
+
},
|
|
146
|
+
"upgradeEndRequestTimeout": {
|
|
147
|
+
"type": "number"
|
|
148
|
+
},
|
|
149
|
+
"upgradeEndDelay": {
|
|
150
|
+
"type": "number"
|
|
151
|
+
},
|
|
152
|
+
"postUpgradeAnnounceTimeout": {
|
|
153
|
+
"type": "number"
|
|
154
|
+
},
|
|
155
|
+
"updates": {
|
|
156
|
+
"type": "array",
|
|
157
|
+
"minItems": 1,
|
|
158
|
+
"items": {
|
|
159
|
+
"$ref": "#/definitions/zigbee-firmware-update"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
35
164
|
"author": {
|
|
36
165
|
"required": ["name"],
|
|
37
166
|
"properties": {
|
|
@@ -1316,6 +1445,13 @@
|
|
|
1316
1445
|
"settings": {
|
|
1317
1446
|
"$ref": "#/definitions/driverSettings"
|
|
1318
1447
|
},
|
|
1448
|
+
"firmwareUpdates": {
|
|
1449
|
+
"oneOf": [
|
|
1450
|
+
{
|
|
1451
|
+
"$ref": "#/definitions/zigbee-firmware-updates"
|
|
1452
|
+
}
|
|
1453
|
+
]
|
|
1454
|
+
},
|
|
1319
1455
|
"gtin": {
|
|
1320
1456
|
"oneOf": [
|
|
1321
1457
|
{
|
package/helpers/index.js
CHANGED
package/index.js
CHANGED
|
@@ -47,3 +47,4 @@ module.exports.getBatteries = Energy.getBatteries.bind(Energy);
|
|
|
47
47
|
|
|
48
48
|
/** @typedef {import('./assets/app/schema').App} AppManifest */
|
|
49
49
|
/** @typedef {import('./assets/capability/schema').Capability} CapabilityDefinition */
|
|
50
|
+
/** @typedef {import('./assets/app/schema').ZigbeeFirmwareUpdates} ZigbeeFirmwareUpdates */
|
package/lib/App/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const Device = require('../Device');
|
|
|
14
14
|
const Capability = require('../Capability');
|
|
15
15
|
const Signal = require('../Signal');
|
|
16
16
|
const Energy = require('../Energy');
|
|
17
|
+
const Util = require('../Util');
|
|
17
18
|
|
|
18
19
|
const {
|
|
19
20
|
openAsync,
|
|
@@ -28,6 +29,7 @@ const {
|
|
|
28
29
|
extname,
|
|
29
30
|
basename,
|
|
30
31
|
dirname,
|
|
32
|
+
toArray,
|
|
31
33
|
} = require('../../helpers');
|
|
32
34
|
|
|
33
35
|
/** @typedef {import('../../assets/app/schema').App} AppManifest */
|
|
@@ -644,6 +646,88 @@ class App {
|
|
|
644
646
|
if (driver.matter && !(driver.connectivity && driver.connectivity.includes('matter'))) {
|
|
645
647
|
throw new Error(`drivers.${driver.id} Matter drivers require 'connectivity' to include 'matter'.`);
|
|
646
648
|
}
|
|
649
|
+
|
|
650
|
+
// Validate driver.firmwareUpdates
|
|
651
|
+
if (driver.firmwareUpdates && Array.isArray(driver.firmwareUpdates.updates)) {
|
|
652
|
+
// TODO: Remove when we want to roll out firmware updates.
|
|
653
|
+
if (level !== 'debug') {
|
|
654
|
+
throw new Error(`drivers.${driver.id} firmwareUpdates can only be included in debug mode validation.`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const isZigbeeDriver = typeof driver.zigbee === 'object' && driver.zigbee !== null;
|
|
658
|
+
|
|
659
|
+
if (!isZigbeeDriver) {
|
|
660
|
+
throw new Error(`drivers.${driver.id} firmwareUpdates are only supported for Zigbee drivers (missing driver.zigbee)`);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
for (const [updateIndex, update] of Object.entries(driver.firmwareUpdates.updates)) {
|
|
664
|
+
const hasChangelogString = typeof update.changelog === 'string';
|
|
665
|
+
const hasChangelogEnString = update.changelog && typeof update.changelog.en === 'string';
|
|
666
|
+
|
|
667
|
+
if (!hasChangelogString && !hasChangelogEnString) {
|
|
668
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] is missing a changelog string`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!Array.isArray(update.files) || update.files.length === 0) {
|
|
672
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}].files must include at least one file`);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (isZigbeeDriver) {
|
|
676
|
+
const driverManufacturerNames = toArray(driver.zigbee.manufacturerName);
|
|
677
|
+
const driverProductIds = toArray(driver.zigbee.productId);
|
|
678
|
+
|
|
679
|
+
const updateManufacturerNames = toArray(update.device.manufacturerName);
|
|
680
|
+
const updateProductIds = toArray(update.device.productId);
|
|
681
|
+
|
|
682
|
+
if (updateManufacturerNames.length === 0 || updateProductIds.length === 0) {
|
|
683
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] must include at least one manufacturerName and productId`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const hasMismatchedManufacturerName = updateManufacturerNames.some(name => !driverManufacturerNames.includes(name));
|
|
687
|
+
const hasMismatchedProductId = updateProductIds.some(id => !driverProductIds.includes(id));
|
|
688
|
+
|
|
689
|
+
if (hasMismatchedManufacturerName) {
|
|
690
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] has a manufacturerName that does not match the driver zigbee.manufacturerName`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (hasMismatchedProductId) {
|
|
694
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] has a productId that does not match the driver zigbee.productId`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const seenFiles = new Set();
|
|
699
|
+
|
|
700
|
+
for (const [fileIndex, file] of Object.entries(update.files)) {
|
|
701
|
+
const relativeFilePath = Util.getOTAFilePath({ appPath: '', driverId: driver.id, fileName: file.name });
|
|
702
|
+
await this._ensureFileExistsCaseSensitive(relativeFilePath);
|
|
703
|
+
const filePath = join(this._path, relativeFilePath);
|
|
704
|
+
const isIntegrityValid = await Util.validateIntegrity(filePath, file.integrity);
|
|
705
|
+
|
|
706
|
+
if (!isIntegrityValid) {
|
|
707
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}].files[${fileIndex}] integrity mismatch`);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (isZigbeeDriver) {
|
|
711
|
+
try {
|
|
712
|
+
await Util.validateZigbeeOTAHeader({
|
|
713
|
+
filePath,
|
|
714
|
+
manufacturerCode: file.manufacturerCode,
|
|
715
|
+
fileVersion: file.fileVersion,
|
|
716
|
+
imageType: file.imageType,
|
|
717
|
+
});
|
|
718
|
+
} catch (err) {
|
|
719
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}].files[${fileIndex}] invalid Zigbee OTA header: ${err.message}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const fileId = `${file.manufacturerCode}-${file.imageType}-${file.fileVersion}`;
|
|
724
|
+
if (seenFiles.has(fileId)) {
|
|
725
|
+
throw new Error(`drivers.${driver.id}.firmwareUpdates.updates[${updateIndex}] has multiple files with the same manufacturerCode, imageType and fileVersion`);
|
|
726
|
+
}
|
|
727
|
+
seenFiles.add(fileId);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
647
731
|
}
|
|
648
732
|
|
|
649
733
|
const allDriversMatter = appJson.drivers.every(driver => driver.connectivity && driver.connectivity.includes('matter'));
|
package/lib/Util/file.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const ALLOWED_INTEGRITY_HASHES = new Set([
|
|
8
|
+
'blake2b512',
|
|
9
|
+
'blake2s256',
|
|
10
|
+
'sha256',
|
|
11
|
+
'sha384',
|
|
12
|
+
'sha512',
|
|
13
|
+
'sha512-256',
|
|
14
|
+
'sha3-256',
|
|
15
|
+
'sha3-384',
|
|
16
|
+
'sha3-512',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Calculate a hex hash for the contents of a file.
|
|
21
|
+
* @param {string} filePath - Absolute or relative path to the file.
|
|
22
|
+
* @param {string} [hashName='sha256'] - Hash algorithm name (e.g. sha256).
|
|
23
|
+
* @returns {Promise<string>} Resolves with a hex-encoded digest.
|
|
24
|
+
*/
|
|
25
|
+
async function hashFile(filePath, hashName = 'sha256') {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const hash = crypto.createHash(hashName);
|
|
28
|
+
const stream = fs.createReadStream(filePath);
|
|
29
|
+
|
|
30
|
+
stream.on('error', reject);
|
|
31
|
+
stream.on('data', chunk => hash.update(chunk));
|
|
32
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate file integrity using a `hashName:hexDigest` string.
|
|
38
|
+
* @param {string} filePath - Absolute or relative path to the file.
|
|
39
|
+
* @param {string} integrity - Hash name and hex digest separated by `:`.
|
|
40
|
+
* @returns {Promise<boolean>} Resolves with true when hashes match.
|
|
41
|
+
*/
|
|
42
|
+
async function validateIntegrity(filePath, integrity) {
|
|
43
|
+
if (typeof integrity !== 'string') {
|
|
44
|
+
throw new TypeError('Integrity must be a string');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parts = integrity.split(':');
|
|
48
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
49
|
+
throw new Error('Integrity must be in the format "hashName:hexDigest"');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const [hashName, expectedHex] = parts;
|
|
53
|
+
const actualHex = await hashFile(filePath, hashName);
|
|
54
|
+
|
|
55
|
+
return actualHex === expectedHex.toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create an integrity string for a file using an allowed hash algorithm.
|
|
60
|
+
* @param {string} filePath - Absolute or relative path to the file.
|
|
61
|
+
* @param {string} [hashName='sha256'] - Allowed hash algorithm name.
|
|
62
|
+
* @returns {Promise<string>} Resolves with `hashName:hexDigest`.
|
|
63
|
+
*/
|
|
64
|
+
async function getIntegrity(filePath, hashName = 'sha256') {
|
|
65
|
+
const normalizedHash = String(hashName || '').toLowerCase();
|
|
66
|
+
|
|
67
|
+
if (!ALLOWED_INTEGRITY_HASHES.has(normalizedHash)) {
|
|
68
|
+
throw new Error('Hash algorithm is not allowed for integrity checks');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const digest = await hashFile(filePath, normalizedHash);
|
|
72
|
+
return `${normalizedHash}:${digest}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getOTAFilePath({ appPath, driverId, fileName }) {
|
|
76
|
+
return path.join(appPath, 'drivers', driverId, 'assets', 'firmware', fileName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
hashFile,
|
|
81
|
+
getIntegrity,
|
|
82
|
+
validateIntegrity,
|
|
83
|
+
getOTAFilePath,
|
|
84
|
+
};
|
package/lib/Util/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fileUtils = require('./file');
|
|
4
|
+
const zigbeeUtils = require('./zigbee');
|
|
5
|
+
|
|
3
6
|
class Util {
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -20,6 +23,65 @@ class Util {
|
|
|
20
23
|
return wantedFeatures.filter(feature => !features.includes(feature));
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Create an integrity string for a file using an allowed hash algorithm.
|
|
28
|
+
* NOTE: This method is only available in Node.js environments.
|
|
29
|
+
* @param {string} filePath - Absolute or relative path to the file.
|
|
30
|
+
* @param {string} hashName - Allowed hash algorithm name.
|
|
31
|
+
* @returns {Promise<string>} Resolves with `hashName:hexDigest`.
|
|
32
|
+
*/
|
|
33
|
+
static getIntegrity(filePath, hashName) {
|
|
34
|
+
return fileUtils.getIntegrity(filePath, hashName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate file integrity using a `hashName:hexDigest` string.
|
|
39
|
+
* NOTE: This method is only available in Node.js environments.
|
|
40
|
+
* @param {string} filePath - Absolute or relative path to the file.
|
|
41
|
+
* @param {string} integrity - Hash name and hex digest separated by `:`.
|
|
42
|
+
* @returns {Promise<boolean>} Resolves with true when hashes match.
|
|
43
|
+
*/
|
|
44
|
+
static validateIntegrity(filePath, integrity) {
|
|
45
|
+
return fileUtils.validateIntegrity(filePath, integrity);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse the Zigbee OTA header from a file.
|
|
50
|
+
* NOTE: This method is only available in Node.js environments.
|
|
51
|
+
* @param {string} filePath - Absolute or relative path to the OTA file.
|
|
52
|
+
* @returns {Promise<import('./zigbee').ZigbeeOTAHeader>} Parsed header values.
|
|
53
|
+
*/
|
|
54
|
+
static parseZigbeeOTAHeader(filePath) {
|
|
55
|
+
return zigbeeUtils.parseZigbeeOTAHeader(filePath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate a Zigbee OTA header against expected values.
|
|
60
|
+
* NOTE: This method is only available in Node.js environments.
|
|
61
|
+
* @param {object} options - Validation options.
|
|
62
|
+
* @param {string} options.filePath - Absolute or relative path to the OTA file.
|
|
63
|
+
* @param {number} [options.manufacturerCode] - Expected manufacturer code.
|
|
64
|
+
* @param {number} [options.fileVersion] - Expected file version.
|
|
65
|
+
* @param {number} [options.imageType] - Expected image type.
|
|
66
|
+
* @returns {Promise<import('./zigbee').ZigbeeOTAHeader>} Parsed header values.
|
|
67
|
+
*/
|
|
68
|
+
static validateZigbeeOTAHeader(options) {
|
|
69
|
+
return zigbeeUtils.validateZigbeeOTAHeader(options);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Returns the expected file path for an OTA file based on app path, driver ID and file name.
|
|
74
|
+
* NOTE: This method is only available in Node.js environments.
|
|
75
|
+
* @param {object} options - File path options.
|
|
76
|
+
* @param {string} options.appPath - Absolute path to the app.
|
|
77
|
+
* @param {string} options.driverId - Driver ID.
|
|
78
|
+
* @param {string} options.fileName - OTA file name.
|
|
79
|
+
* @returns {string} Resolves with the expected file path.
|
|
80
|
+
*/
|
|
81
|
+
static getOTAFilePath({ appPath, driverId, fileName }) {
|
|
82
|
+
return fileUtils.getOTAFilePath({ appPath, driverId, fileName });
|
|
83
|
+
}
|
|
84
|
+
|
|
23
85
|
}
|
|
24
86
|
|
|
25
87
|
Util.FEATURE_SPEAKER = 'speaker';
|