zwave-js 14.3.6 → 14.3.7
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/build/cjs/lib/_version.d.ts +1 -1
- package/build/cjs/lib/_version.js +1 -1
- package/build/cjs/lib/_version.js.map +1 -1
- package/build/cjs/lib/controller/Controller.d.ts +2 -2
- package/build/cjs/lib/controller/Controller.js +1 -1
- package/build/cjs/lib/controller/Controller.js.map +2 -2
- package/build/cjs/lib/driver/Driver.d.ts +2 -2
- package/build/cjs/lib/driver/Driver.js +1 -4
- package/build/cjs/lib/driver/Driver.js.map +2 -2
- package/build/cjs/lib/driver/Statistics.js.map +2 -2
- package/build/cjs/lib/node/Node.d.ts +2 -2
- package/build/cjs/lib/node/Node.js +1 -2
- package/build/cjs/lib/node/Node.js.map +2 -2
- package/build/cjs/lib/node/mixins/70_FirmwareUpdate.js +5 -2
- package/build/cjs/lib/node/mixins/70_FirmwareUpdate.js.map +2 -2
- package/build/cjs/lib/zniffer/Zniffer.d.ts +2 -2
- package/build/cjs/lib/zniffer/Zniffer.js +1 -1
- package/build/cjs/lib/zniffer/Zniffer.js.map +2 -2
- package/build/esm/lib/_version.d.ts +1 -1
- package/build/esm/lib/_version.js +1 -1
- package/build/esm/lib/controller/Controller.d.ts +2 -2
- package/build/esm/lib/controller/Controller.d.ts.map +1 -1
- package/build/esm/lib/controller/Controller.js +2 -2
- package/build/esm/lib/controller/Controller.js.map +1 -1
- package/build/esm/lib/driver/Driver.d.ts +2 -2
- package/build/esm/lib/driver/Driver.d.ts.map +1 -1
- package/build/esm/lib/driver/Driver.js +2 -7
- package/build/esm/lib/driver/Driver.js.map +1 -1
- package/build/esm/lib/driver/Statistics.d.ts.map +1 -1
- package/build/esm/lib/driver/Statistics.js.map +1 -1
- package/build/esm/lib/node/Node.d.ts +2 -2
- package/build/esm/lib/node/Node.d.ts.map +1 -1
- package/build/esm/lib/node/Node.js +2 -3
- package/build/esm/lib/node/Node.js.map +1 -1
- package/build/esm/lib/node/mixins/70_FirmwareUpdate.d.ts.map +1 -1
- package/build/esm/lib/node/mixins/70_FirmwareUpdate.js +5 -2
- package/build/esm/lib/node/mixins/70_FirmwareUpdate.js.map +1 -1
- package/build/esm/lib/zniffer/Zniffer.d.ts +2 -2
- package/build/esm/lib/zniffer/Zniffer.d.ts.map +1 -1
- package/build/esm/lib/zniffer/Zniffer.js +2 -2
- package/build/esm/lib/zniffer/Zniffer.js.map +1 -1
- package/package.json +11 -11
|
@@ -112,6 +112,7 @@ class FirmwareUpdateMixin extends import_ScheduledPoll.SchedulePollMixin {
|
|
|
112
112
|
};
|
|
113
113
|
let fragmentSizeSecure;
|
|
114
114
|
let fragmentSizeNonSecure;
|
|
115
|
+
let hardwareVersion;
|
|
115
116
|
let meta;
|
|
116
117
|
try {
|
|
117
118
|
const prepareResult = await self.prepareFirmwareUpdateInternal(updates.map((u) => u.firmwareTarget ?? 0), abortContext);
|
|
@@ -127,6 +128,7 @@ class FirmwareUpdateMixin extends import_ScheduledPoll.SchedulePollMixin {
|
|
|
127
128
|
({
|
|
128
129
|
fragmentSizeSecure,
|
|
129
130
|
fragmentSizeNonSecure,
|
|
131
|
+
hardwareVersion,
|
|
130
132
|
...meta
|
|
131
133
|
} = prepareResult);
|
|
132
134
|
} catch {
|
|
@@ -172,7 +174,7 @@ class FirmwareUpdateMixin extends import_ScheduledPoll.SchedulePollMixin {
|
|
|
172
174
|
}
|
|
173
175
|
self.driver.controllerLog.logNode(self.id, `Updating firmware (part ${i + 1} / ${updatesWithChecksum.length})...`);
|
|
174
176
|
let fragmentSize = options.nonSecureTransfer ? fragmentSizeNonSecure : fragmentSizeSecure;
|
|
175
|
-
const { resume, nonSecureTransfer } = yield* self.beginFirmwareUpdateInternal(data, target, meta, fragmentSize, checksum, shouldResume, options.nonSecureTransfer);
|
|
177
|
+
const { resume, nonSecureTransfer } = yield* self.beginFirmwareUpdateInternal(data, target, meta, fragmentSize, checksum, hardwareVersion, shouldResume, options.nonSecureTransfer);
|
|
176
178
|
if (options.nonSecureTransfer && !nonSecureTransfer) {
|
|
177
179
|
fragmentSize = fragmentSizeSecure;
|
|
178
180
|
}
|
|
@@ -303,7 +305,7 @@ class FirmwareUpdateMixin extends import_ScheduledPoll.SchedulePollMixin {
|
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
/** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */
|
|
306
|
-
async *beginFirmwareUpdateInternal(data, target, meta, fragmentSize, checksum, resume, nonSecureTransfer) {
|
|
308
|
+
async *beginFirmwareUpdateInternal(data, target, meta, fragmentSize, checksum, hardwareVersion, resume, nonSecureTransfer) {
|
|
307
309
|
const api = this.commandClasses["Firmware Update Meta Data"];
|
|
308
310
|
this.driver.controllerLog.logNode(this.id, {
|
|
309
311
|
message: `Starting firmware update...`,
|
|
@@ -316,6 +318,7 @@ class FirmwareUpdateMixin extends import_ScheduledPoll.SchedulePollMixin {
|
|
|
316
318
|
firmwareTarget: target,
|
|
317
319
|
fragmentSize,
|
|
318
320
|
checksum,
|
|
321
|
+
hardwareVersion,
|
|
319
322
|
resume,
|
|
320
323
|
nonSecureTransfer
|
|
321
324
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/node/mixins/70_FirmwareUpdate.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n\ttype FirmwareUpdateMetaData,\n\tFirmwareUpdateMetaDataCC,\n\tFirmwareUpdateMetaDataCCGet,\n\ttype FirmwareUpdateMetaDataCCMetaDataGet,\n\tFirmwareUpdateMetaDataCCReport,\n\tFirmwareUpdateMetaDataCCRequestReport,\n\tFirmwareUpdateMetaDataCCStatusReport,\n\ttype FirmwareUpdateOptions,\n\ttype FirmwareUpdateProgress,\n\tFirmwareUpdateRequestStatus,\n\ttype FirmwareUpdateResult,\n\tFirmwareUpdateStatus,\n\tgetEffectiveCCVersion,\n} from \"@zwave-js/cc\";\nimport {\n\tCRC16_CCITT,\n\tCommandClasses,\n\tEncapsulationFlags,\n\ttype Firmware,\n\tSecurityClass,\n\tZWaveError,\n\tZWaveErrorCodes,\n\trandomBytes,\n\tsecurityClassIsS2,\n\ttimespan,\n} from \"@zwave-js/core\";\nimport { containsCC } from \"@zwave-js/serial/serialapi\";\nimport { getEnumMemberName, throttle } from \"@zwave-js/shared\";\nimport { distinct } from \"alcalzone-shared/arrays\";\nimport { wait } from \"alcalzone-shared/async\";\nimport {\n\ttype DeferredPromise,\n\tcreateDeferredPromise,\n} from \"alcalzone-shared/deferred-promise\";\nimport { roundTo } from \"alcalzone-shared/math\";\nimport {\n\ttype Task,\n\ttype TaskBuilder,\n\tTaskPriority,\n} from \"../../driver/Task.js\";\nimport { type Transaction } from \"../../driver/Transaction.js\";\nimport { SchedulePollMixin } from \"./60_ScheduledPoll.js\";\n\ninterface AbortFirmwareUpdateContext {\n\tabort: boolean;\n\ttooLateToAbort: boolean;\n\tabortPromise: DeferredPromise<boolean>;\n}\n\ntype PartialFirmwareUpdateResult =\n\t& Pick<FirmwareUpdateResult, \"status\" | \"waitTime\">\n\t& { success: boolean };\n\n/** Checks if a task belongs to a route rebuilding process */\nexport function isFirmwareUpdateOTATask(t: Task<unknown>): boolean {\n\treturn t.tag?.id === \"firmware-update-ota\";\n}\n\nexport interface NodeFirmwareUpdate {\n\t/**\n\t * Aborts an active firmware update process\n\t */\n\tabortFirmwareUpdate(): Promise<void>;\n\n\t/**\n\t * Performs an OTA firmware upgrade of one or more chips on this node.\n\t *\n\t * This method will resolve after the process has **COMPLETED**. Failure to start any one of the provided updates will throw an error.\n\t *\n\t * **WARNING: Use at your own risk! We don't take any responsibility if your devices don't work after an update.**\n\t *\n\t * @param updates An array of firmware updates that will be done in sequence\n\t *\n\t * @returns Whether all of the given updates were successful.\n\t */\n\tupdateFirmware(\n\t\tupdates: Firmware[],\n\t\toptions?: FirmwareUpdateOptions,\n\t): Promise<FirmwareUpdateResult>;\n\n\t/**\n\t * Returns whether a firmware update is in progress for this node.\n\t */\n\tisFirmwareUpdateInProgress(): boolean;\n}\n\nexport abstract class FirmwareUpdateMixin extends SchedulePollMixin\n\timplements NodeFirmwareUpdate\n{\n\tprivate _abortFirmwareUpdate: (() => Promise<void>) | undefined;\n\tpublic async abortFirmwareUpdate(): Promise<void> {\n\t\tif (!this._abortFirmwareUpdate) return;\n\t\tawait this._abortFirmwareUpdate();\n\t}\n\n\t// Stores the CRC of the previously transferred firmware image.\n\t// Allows detecting whether resuming is supported and where to continue in a multi-file transfer.\n\tprivate _previousFirmwareCRC: number | undefined;\n\n\t/** Is used to remember fragment requests that came in before they were able to be handled */\n\tprivate _firmwareUpdatePrematureRequest:\n\t\t| FirmwareUpdateMetaDataCCGet\n\t\t| undefined;\n\n\tpublic async updateFirmware(\n\t\tupdates: Firmware[],\n\t\toptions: FirmwareUpdateOptions = {},\n\t): Promise<FirmwareUpdateResult> {\n\t\tif (updates.length === 0) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`At least one update must be provided`,\n\t\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t\t);\n\t\t}\n\n\t\t// Check that each update has a buffer with at least 1 byte\n\t\tif (updates.some((u) => u.data.length === 0)) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`All firmware updates must have a non-empty data buffer`,\n\t\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t\t);\n\t\t}\n\n\t\t// Check that the targets are not duplicates\n\t\tif (\n\t\t\tdistinct(updates.map((u) => u.firmwareTarget ?? 0)).length\n\t\t\t\t!== updates.length\n\t\t) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`The target of all provided firmware updates must be unique`,\n\t\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t\t);\n\t\t}\n\n\t\t// Don't start the process twice\n\t\tif (this.driver.controller.isFirmwareUpdateInProgress()) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`Failed to start the update: An OTW upgrade of the controller is in progress!`,\n\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_Busy,\n\t\t\t);\n\t\t}\n\n\t\t// Don't allow starting two firmware updates for the same node\n\t\tconst task = this.getUpdateFirmwareTask(updates, options);\n\t\tif (task instanceof Promise) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`Failed to start the update: A firmware update is already in progress for this node!`,\n\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_Busy,\n\t\t\t);\n\t\t}\n\n\t\t// Queue the task\n\t\treturn this.driver.scheduler.queueTask(task);\n\t}\n\n\tpublic isFirmwareUpdateInProgress(): boolean {\n\t\treturn !!this.driver.scheduler.findTask(isFirmwareUpdateOTATask);\n\t}\n\n\tprivate getUpdateFirmwareTask(\n\t\tupdates: Firmware[],\n\t\toptions: FirmwareUpdateOptions = {},\n\t): Promise<FirmwareUpdateResult> | TaskBuilder<FirmwareUpdateResult> {\n\t\tconst self = this;\n\n\t\t// This task should only run once at a time\n\t\tconst existingTask = this.driver.scheduler.findTask<\n\t\t\tFirmwareUpdateResult\n\t\t>((t) =>\n\t\t\tt.tag?.id === \"firmware-update-ota\"\n\t\t\t&& t.tag.nodeId === self.id\n\t\t);\n\t\tif (existingTask) return existingTask;\n\n\t\tlet keepAwake: boolean;\n\n\t\treturn {\n\t\t\t// Firmware updates cause a lot of traffic. Execute them in the background.\n\t\t\tpriority: TaskPriority.Lower,\n\t\t\ttag: { id: \"firmware-update-ota\", nodeId: self.id },\n\t\t\ttask: async function* firmwareUpdateTask() {\n\t\t\t\t// Keep battery powered nodes awake during the process\n\t\t\t\tkeepAwake = self.keepAwake;\n\t\t\t\tself.keepAwake = true;\n\n\t\t\t\t// Support aborting the update\n\t\t\t\tconst abortContext = {\n\t\t\t\t\tabort: false,\n\t\t\t\t\ttooLateToAbort: false,\n\t\t\t\t\tabortPromise: createDeferredPromise<boolean>(),\n\t\t\t\t};\n\n\t\t\t\tself._abortFirmwareUpdate = async () => {\n\t\t\t\t\tif (abortContext.tooLateToAbort) {\n\t\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t\t`The firmware update was transmitted completely, cannot abort anymore.`,\n\t\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\tmessage: `Aborting firmware update...`,\n\t\t\t\t\t\tdirection: \"outbound\",\n\t\t\t\t\t});\n\n\t\t\t\t\t// Trigger the abort\n\t\t\t\t\tabortContext.abort = true;\n\t\t\t\t\tconst aborted = await abortContext.abortPromise;\n\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t\t`The node did not acknowledge the aborted update`,\n\t\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\tmessage: `Firmware update aborted`,\n\t\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\t// Prepare the firmware update\n\t\t\t\tlet fragmentSizeSecure: number;\n\t\t\t\tlet fragmentSizeNonSecure: number;\n\t\t\t\tlet meta: FirmwareUpdateMetaData;\n\t\t\t\ttry {\n\t\t\t\t\tconst prepareResult = await self\n\t\t\t\t\t\t.prepareFirmwareUpdateInternal(\n\t\t\t\t\t\t\tupdates.map((u) => u.firmwareTarget ?? 0),\n\t\t\t\t\t\t\tabortContext,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t// Handle early aborts\n\t\t\t\t\tif (abortContext.abort) {\n\t\t\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\tstatus: FirmwareUpdateStatus\n\t\t\t\t\t\t\t\t.Error_TransmissionFailed,\n\t\t\t\t\t\t\treInterview: false,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tself._emit(\n\t\t\t\t\t\t\t\"firmware update finished\",\n\t\t\t\t\t\t\tself,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\n\t\t\t\t\t// If the firmware update was not aborted, prepareResult is definitely defined\n\t\t\t\t\t({\n\t\t\t\t\t\tfragmentSizeSecure,\n\t\t\t\t\t\tfragmentSizeNonSecure,\n\t\t\t\t\t\t...meta\n\t\t\t\t\t} = prepareResult!);\n\t\t\t\t} catch {\n\t\t\t\t\t// Not sure what the error is, but we'll label it \"transmission failed\"\n\t\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tstatus: FirmwareUpdateStatus.Error_TransmissionFailed,\n\t\t\t\t\t\treInterview: false,\n\t\t\t\t\t};\n\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t\t// The resume and non-secure transfer features may not be supported by the node\n\t\t\t\t// If not, disable them, even though the application requested them\n\t\t\t\tif (!meta.supportsResuming) options.resume = false;\n\n\t\t\t\tconst securityClass = self.getHighestSecurityClass();\n\t\t\t\tconst isSecure = securityClass === SecurityClass.S0_Legacy\n\t\t\t\t\t|| securityClassIsS2(securityClass);\n\t\t\t\tif (!isSecure) {\n\t\t\t\t\t// The nonSecureTransfer option is only relevant for secure devices\n\t\t\t\t\toptions.nonSecureTransfer = false;\n\t\t\t\t} else if (!meta.supportsNonSecureTransfer) {\n\t\t\t\t\toptions.nonSecureTransfer = false;\n\t\t\t\t}\n\n\t\t\t\t// Throttle the progress emitter so applications can handle the load of events\n\t\t\t\tconst notifyProgress = throttle(\n\t\t\t\t\t(progress) =>\n\t\t\t\t\t\tself._emit(\n\t\t\t\t\t\t\t\"firmware update progress\",\n\t\t\t\t\t\t\tself,\n\t\t\t\t\t\t\tprogress,\n\t\t\t\t\t\t),\n\t\t\t\t\t250,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\n\t\t\t\t// If resuming is supported and desired, try to figure out with which file to continue\n\t\t\t\tconst updatesWithChecksum = updates.map((u) => ({\n\t\t\t\t\t...u,\n\t\t\t\t\tchecksum: CRC16_CCITT(u.data),\n\t\t\t\t}));\n\t\t\t\tlet skipFinishedFiles = -1;\n\t\t\t\tlet shouldResume = options.resume\n\t\t\t\t\t&& self._previousFirmwareCRC != undefined;\n\t\t\t\tif (shouldResume) {\n\t\t\t\t\tskipFinishedFiles = updatesWithChecksum.findIndex(\n\t\t\t\t\t\t(u) => u.checksum === self._previousFirmwareCRC,\n\t\t\t\t\t);\n\t\t\t\t\tif (skipFinishedFiles === -1) shouldResume = false;\n\t\t\t\t}\n\n\t\t\t\t// Perform all firmware updates in sequence\n\t\t\t\tlet updateResult!: PartialFirmwareUpdateResult;\n\t\t\t\tlet conservativeWaitTime: number;\n\n\t\t\t\tconst totalBytes: number = updatesWithChecksum.reduce(\n\t\t\t\t\t(total, update) => total + update.data.length,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t\tlet sentBytesOfPreviousFiles = 0;\n\n\t\t\t\tfor (let i = 0; i < updatesWithChecksum.length; i++) {\n\t\t\t\t\tconst { firmwareTarget: target = 0, data, checksum } =\n\t\t\t\t\t\tupdatesWithChecksum[i];\n\n\t\t\t\t\tif (i < skipFinishedFiles) {\n\t\t\t\t\t\t// If we are resuming, skip this file since it was already done before\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Skipping already completed firmware update (part ${\n\t\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t\t} / ${updatesWithChecksum.length})...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsentBytesOfPreviousFiles += data.length;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t`Updating firmware (part ${\n\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t} / ${updatesWithChecksum.length})...`,\n\t\t\t\t\t);\n\n\t\t\t\t\t// For determining the initial fragment size, assume the node respects our choice.\n\t\t\t\t\t// If the node is not secure, these two values are identical anyways.\n\t\t\t\t\tlet fragmentSize = options.nonSecureTransfer\n\t\t\t\t\t\t? fragmentSizeNonSecure\n\t\t\t\t\t\t: fragmentSizeSecure;\n\n\t\t\t\t\t// Tell the node to start requesting fragments\n\t\t\t\t\tconst { resume, nonSecureTransfer } = yield* self\n\t\t\t\t\t\t.beginFirmwareUpdateInternal(\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\tmeta,\n\t\t\t\t\t\t\tfragmentSize,\n\t\t\t\t\t\t\tchecksum,\n\t\t\t\t\t\t\tshouldResume,\n\t\t\t\t\t\t\toptions.nonSecureTransfer,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t// If the node did not accept non-secure transfer, revisit our choice of fragment size\n\t\t\t\t\tif (options.nonSecureTransfer && !nonSecureTransfer) {\n\t\t\t\t\t\tfragmentSize = fragmentSizeSecure;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remember the checksum, so we can resume if necessary\n\t\t\t\t\tself._previousFirmwareCRC = checksum;\n\n\t\t\t\t\tif (shouldResume) {\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Node ${\n\t\t\t\t\t\t\t\tresume ? \"accepted\" : \"did not accept\"\n\t\t\t\t\t\t\t} resuming the update...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (nonSecureTransfer) {\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Firmware will be transferred without encryption...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t\t\t// Listen for firmware update fragment requests and handle them\n\t\t\t\t\tupdateResult = yield* self.doFirmwareUpdateInternal(\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\tfragmentSize,\n\t\t\t\t\t\tnonSecureTransfer,\n\t\t\t\t\t\tabortContext,\n\t\t\t\t\t\t(fragment, total) => {\n\t\t\t\t\t\t\tconst progress: FirmwareUpdateProgress = {\n\t\t\t\t\t\t\t\tcurrentFile: i + 1,\n\t\t\t\t\t\t\t\ttotalFiles: updatesWithChecksum.length,\n\t\t\t\t\t\t\t\tsentFragments: fragment,\n\t\t\t\t\t\t\t\ttotalFragments: total,\n\t\t\t\t\t\t\t\tprogress: roundTo(\n\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t(sentBytesOfPreviousFiles\n\t\t\t\t\t\t\t\t\t\t\t+ Math.min(\n\t\t\t\t\t\t\t\t\t\t\t\tfragment * fragmentSize,\n\t\t\t\t\t\t\t\t\t\t\t\tdata.length,\n\t\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t\t/ totalBytes\n\t\t\t\t\t\t\t\t\t) * 100,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tnotifyProgress(progress);\n\n\t\t\t\t\t\t\t// When this file is done, add the fragments to the total, so we can compute the total progress correctly\n\t\t\t\t\t\t\tif (fragment === total) {\n\t\t\t\t\t\t\t\tsentBytesOfPreviousFiles += data.length;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\n\t\t\t\t\t// If we wait, wait a bit longer than the device told us, so it is actually ready to use\n\t\t\t\t\tconservativeWaitTime = self.driver\n\t\t\t\t\t\t.getConservativeWaitTimeAfterFirmwareUpdate(\n\t\t\t\t\t\t\tupdateResult.waitTime,\n\t\t\t\t\t\t);\n\n\t\t\t\t\tif (!updateResult.success) {\n\t\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\t\tmessage: `Firmware update (part ${\n\t\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t\t} / ${updatesWithChecksum.length}) failed with status ${\n\t\t\t\t\t\t\t\tgetEnumMemberName(\n\t\t\t\t\t\t\t\t\tFirmwareUpdateStatus,\n\t\t\t\t\t\t\t\t\tupdateResult.status,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t\t\t...updateResult,\n\t\t\t\t\t\t\twaitTime: undefined,\n\t\t\t\t\t\t\treInterview: false,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tself._emit(\n\t\t\t\t\t\t\t\"firmware update finished\",\n\t\t\t\t\t\t\tself,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} else if (i < updatesWithChecksum.length - 1) {\n\t\t\t\t\t\t// Update succeeded, but we're not done yet\n\n\t\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\t\tmessage: `Firmware update (part ${\n\t\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t\t} / ${updatesWithChecksum.length}) succeeded with status ${\n\t\t\t\t\t\t\t\tgetEnumMemberName(\n\t\t\t\t\t\t\t\t\tFirmwareUpdateStatus,\n\t\t\t\t\t\t\t\t\tupdateResult.status,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Continuing with next part in ${conservativeWaitTime} seconds...`,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// If we've resumed the previous file, there's no need to resume the next one too\n\t\t\t\t\t\tshouldResume = false;\n\n\t\t\t\t\t\tyield () => wait(conservativeWaitTime * 1000, true);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// We're done. No need to resume this update\n\t\t\t\tself._previousFirmwareCRC = undefined;\n\n\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t...updateResult,\n\t\t\t\t\twaitTime: conservativeWaitTime!,\n\t\t\t\t\treInterview: true,\n\t\t\t\t};\n\n\t\t\t\t// After a successful firmware update, we want to interview sleeping nodes immediately,\n\t\t\t\t// so don't send them to sleep when they wake up\n\t\t\t\tkeepAwake = true;\n\n\t\t\t\tself._emit(\"firmware update finished\", self, result);\n\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tcleanup() {\n\t\t\t\tself._abortFirmwareUpdate = undefined;\n\t\t\t\tself._firmwareUpdatePrematureRequest = undefined;\n\n\t\t\t\t// Make sure that the keepAwake flag gets reset at the end\n\t\t\t\tself.keepAwake = keepAwake;\n\t\t\t\tif (!keepAwake) {\n\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\tself.driver.debounceSendNodeToSleep(self);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn Promise.resolve();\n\t\t\t},\n\t\t};\n\t}\n\n\t/** Prepares the firmware update of a single target by collecting the necessary information */\n\tprivate async prepareFirmwareUpdateInternal(\n\t\ttargets: number[],\n\t\tabortContext: AbortFirmwareUpdateContext,\n\t): Promise<\n\t\t| undefined\n\t\t| (FirmwareUpdateMetaData & {\n\t\t\tfragmentSizeSecure: number;\n\t\t\tfragmentSizeNonSecure: number;\n\t\t})\n\t> {\n\t\tconst api = this.commandClasses[\"Firmware Update Meta Data\"];\n\n\t\t// ================================\n\t\t// STEP 1:\n\t\t// Check if this update is possible\n\t\tconst meta = await api.getMetaData();\n\t\tif (!meta) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`Failed to start the update: The node did not respond in time!`,\n\t\t\t\tZWaveErrorCodes.Controller_NodeTimeout,\n\t\t\t);\n\t\t}\n\n\t\tfor (const target of targets) {\n\t\t\tif (target === 0) {\n\t\t\t\tif (!meta.firmwareUpgradable) {\n\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t`Failed to start the update: The Z-Wave chip firmware is not upgradable!`,\n\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (api.version < 3) {\n\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t`Failed to start the update: The node does not support upgrading a different firmware target than 0!`,\n\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound,\n\t\t\t\t\t);\n\t\t\t\t} else if (\n\t\t\t\t\tmeta.additionalFirmwareIDs[target - 1] == undefined\n\t\t\t\t) {\n\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t`Failed to start the update: Firmware target #${target} not found on this node!`,\n\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// ================================\n\t\t// STEP 2:\n\t\t// Determine the fragment size\n\t\tconst fcc = new FirmwareUpdateMetaDataCC({ nodeId: this.id });\n\t\tfcc.toggleEncapsulationFlag(\n\t\t\tEncapsulationFlags.Security,\n\t\t\tthis.driver.isCCSecure(fcc.ccId, this.id),\n\t\t);\n\t\tconst maxGrossPayloadSizeSecure = this.driver\n\t\t\t.computeNetCCPayloadSize(\n\t\t\t\tfcc,\n\t\t\t);\n\t\tconst maxGrossPayloadSizeNonSecure = this.driver\n\t\t\t.computeNetCCPayloadSize(fcc, true);\n\n\t\tconst ccVersion = getEffectiveCCVersion(this.driver, fcc);\n\t\tconst maxNetPayloadSizeSecure = maxGrossPayloadSizeSecure\n\t\t\t- 2 // report number\n\t\t\t- (ccVersion >= 2 ? 2 : 0); // checksum\n\t\tconst maxNetPayloadSizeNonSecure = maxGrossPayloadSizeNonSecure\n\t\t\t- 2 // report number\n\t\t\t- (ccVersion >= 2 ? 2 : 0); // checksum\n\n\t\t// Use the smallest allowed payload\n\t\tconst fragmentSizeSecure = Math.min(\n\t\t\tmaxNetPayloadSizeSecure,\n\t\t\tmeta.maxFragmentSize ?? Number.POSITIVE_INFINITY,\n\t\t);\n\t\tconst fragmentSizeNonSecure = Math.min(\n\t\t\tmaxNetPayloadSizeNonSecure,\n\t\t\tmeta.maxFragmentSize ?? Number.POSITIVE_INFINITY,\n\t\t);\n\n\t\tif (abortContext.abort) {\n\t\t\tabortContext.abortPromise.resolve(true);\n\t\t\treturn;\n\t\t} else {\n\t\t\treturn {\n\t\t\t\t...meta,\n\t\t\t\tfragmentSizeSecure,\n\t\t\t\tfragmentSizeNonSecure,\n\t\t\t};\n\t\t}\n\t}\n\n\tprotected async handleUnexpectedFirmwareUpdateGet(\n\t\tcommand: FirmwareUpdateMetaDataCCGet,\n\t): Promise<void> {\n\t\t// This method will only be called under two circumstances:\n\t\t// 1. The node is currently busy responding to a firmware update request -> remember the request\n\t\tif (this.isFirmwareUpdateInProgress()) {\n\t\t\tthis._firmwareUpdatePrematureRequest = command;\n\t\t\treturn;\n\t\t}\n\n\t\t// 2. No firmware update is in progress -> abort\n\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\tmessage:\n\t\t\t\t`Received Firmware Update Get, but no firmware update is in progress. Forcing the node to abort...`,\n\t\t\tdirection: \"inbound\",\n\t\t});\n\n\t\t// Since no update is in progress, we need to determine the fragment size again\n\t\tconst fcc = new FirmwareUpdateMetaDataCC({ nodeId: this.id });\n\t\tfcc.toggleEncapsulationFlag(\n\t\t\tEncapsulationFlags.Security,\n\t\t\t!!(command.encapsulationFlags & EncapsulationFlags.Security),\n\t\t);\n\t\tconst ccVersion = getEffectiveCCVersion(this.driver, fcc);\n\t\tconst fragmentSize = this.driver.computeNetCCPayloadSize(fcc)\n\t\t\t- 2 // report number\n\t\t\t- (ccVersion >= 2 ? 2 : 0); // checksum\n\t\tconst fragment = randomBytes(fragmentSize);\n\t\ttry {\n\t\t\tawait this.sendCorruptedFirmwareUpdateReport(\n\t\t\t\tcommand.reportNumber,\n\t\t\t\tfragment,\n\t\t\t);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\t/** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */\n\tprivate async *beginFirmwareUpdateInternal(\n\t\tdata: Uint8Array,\n\t\ttarget: number,\n\t\tmeta: FirmwareUpdateMetaData,\n\t\tfragmentSize: number,\n\t\tchecksum: number,\n\t\tresume: boolean | undefined,\n\t\tnonSecureTransfer: boolean | undefined,\n\t) {\n\t\tconst api = this.commandClasses[\"Firmware Update Meta Data\"];\n\n\t\t// ================================\n\t\t// STEP 3:\n\t\t// Start the update\n\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\tmessage: `Starting firmware update...`,\n\t\t\tdirection: \"outbound\",\n\t\t});\n\n\t\t// Request the node to start the upgrade\n\t\tawait api.requestUpdate({\n\t\t\t// TODO: Should manufacturer id and firmware id be provided externally?\n\t\t\tmanufacturerId: meta.manufacturerId,\n\t\t\tfirmwareId: target == 0\n\t\t\t\t? meta.firmwareId\n\t\t\t\t: meta.additionalFirmwareIDs[target - 1],\n\t\t\tfirmwareTarget: target,\n\t\t\tfragmentSize,\n\t\t\tchecksum,\n\t\t\tresume,\n\t\t\tnonSecureTransfer,\n\t\t});\n\t\t// Pause the task until the response is received, because that can take\n\t\t// up to a minute\n\t\tconst result: FirmwareUpdateMetaDataCCRequestReport = yield () =>\n\t\t\tthis.driver\n\t\t\t\t.waitForCommand<FirmwareUpdateMetaDataCCRequestReport>(\n\t\t\t\t\t(cc) =>\n\t\t\t\t\t\tcc instanceof FirmwareUpdateMetaDataCCRequestReport\n\t\t\t\t\t\t&& cc.nodeId === this.id,\n\t\t\t\t\t60000,\n\t\t\t\t);\n\n\t\tswitch (result.status) {\n\t\t\tcase FirmwareUpdateRequestStatus.Error_AuthenticationExpected:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: A manual authentication event (e.g. button push) was expected!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_BatteryLow:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: The battery level is too low!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus\n\t\t\t\t.Error_FirmwareUpgradeInProgress:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: A firmware upgrade is already in progress!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_Busy,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus\n\t\t\t\t.Error_InvalidManufacturerOrFirmwareID:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: Invalid manufacturer or firmware id!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_InvalidHardwareVersion:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: Invalid hardware version!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_NotUpgradable:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: Firmware target #${target} is not upgradable!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_FragmentSizeTooLarge:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: The chosen fragment size is too large!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.OK:\n\t\t\t\t// All good, we have started!\n\t\t\t\t// Keep the node awake until the update is done.\n\t\t\t\tthis.keepAwake = true;\n\t\t}\n\n\t\treturn {\n\t\t\tresume: !!result.resume,\n\t\t\tnonSecureTransfer: !!result.nonSecureTransfer,\n\t\t};\n\t}\n\n\tprotected async handleFirmwareUpdateMetaDataGet(\n\t\tcommand: FirmwareUpdateMetaDataCCMetaDataGet,\n\t): Promise<void> {\n\t\tconst endpoint = this.getEndpoint(command.endpointIndex)\n\t\t\t?? this;\n\n\t\t// We are being queried, so the device may actually not support the CC, just control it.\n\t\t// Using the commandClasses property would throw in that case\n\t\tconst api = endpoint\n\t\t\t.createAPI(CommandClasses[\"Firmware Update Meta Data\"], false)\n\t\t\t.withOptions({\n\t\t\t\t// Answer with the same encapsulation as asked, but omit\n\t\t\t\t// Supervision as it shouldn't be used for Get-Report flows\n\t\t\t\tencapsulationFlags: command.encapsulationFlags\n\t\t\t\t\t& ~EncapsulationFlags.Supervision,\n\t\t\t});\n\n\t\t// We do not support the firmware to be upgraded.\n\t\tawait api.reportMetaData({\n\t\t\tmanufacturerId: this.driver.options.vendor?.manufacturerId\n\t\t\t\t?? 0xffff,\n\t\t\tfirmwareUpgradable: false,\n\t\t\thardwareVersion: this.driver.options.vendor?.hardwareVersion\n\t\t\t\t?? 0,\n\t\t});\n\t}\n\n\tprivate async sendCorruptedFirmwareUpdateReport(\n\t\treportNum: number,\n\t\tfragment: Uint8Array,\n\t\tnonSecureTransfer: boolean = false,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait this.commandClasses[\"Firmware Update Meta Data\"]\n\t\t\t\t.withOptions({\n\t\t\t\t\t// Only encapsulate if the transfer is secure\n\t\t\t\t\tautoEncapsulate: !nonSecureTransfer,\n\t\t\t\t})\n\t\t\t\t.sendFirmwareFragment(reportNum, true, fragment);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tprivate hasPendingFirmwareUpdateFragment(\n\t\tfragmentNumber: number,\n\t): boolean {\n\t\t// Avoid queuing duplicate fragments\n\t\tconst isCurrentFirmwareFragment = (t: Transaction) =>\n\t\t\tt.message.getNodeId() === this.id\n\t\t\t&& containsCC(t.message)\n\t\t\t&& t.message.command instanceof FirmwareUpdateMetaDataCCReport\n\t\t\t&& t.message.command.reportNumber === fragmentNumber;\n\n\t\treturn this.driver.hasPendingTransactions(\n\t\t\tisCurrentFirmwareFragment,\n\t\t);\n\t}\n\n\tprivate async *doFirmwareUpdateInternal(\n\t\tdata: Uint8Array,\n\t\tfragmentSize: number,\n\t\tnonSecureTransfer: boolean,\n\t\tabortContext: AbortFirmwareUpdateContext,\n\t\tonProgress: (fragment: number, total: number) => void,\n\t): AsyncGenerator<\n\t\tany,\n\t\tPartialFirmwareUpdateResult,\n\t\tany\n\t> {\n\t\tconst numFragments = Math.ceil(data.length / fragmentSize);\n\n\t\t// Make sure we're not responding to an outdated request immediately\n\t\tthis._firmwareUpdatePrematureRequest = undefined;\n\n\t\t// ================================\n\t\t// STEP 4:\n\t\t// Respond to fragment requests from the node\n\t\tupdate: while (true) {\n\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t// During ongoing firmware updates, it can happen that the next request is received before the callback for the previous response\n\t\t\t// is back. In that case we can immediately handle the premature request. Otherwise wait for the next request.\n\t\t\tlet fragmentRequest: FirmwareUpdateMetaDataCCGet;\n\t\t\tif (this._firmwareUpdatePrematureRequest) {\n\t\t\t\tfragmentRequest = this._firmwareUpdatePrematureRequest;\n\t\t\t\tthis._firmwareUpdatePrematureRequest = undefined;\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tfragmentRequest = yield () =>\n\t\t\t\t\t\tthis.driver\n\t\t\t\t\t\t\t.waitForCommand<FirmwareUpdateMetaDataCCGet>(\n\t\t\t\t\t\t\t\t(cc) =>\n\t\t\t\t\t\t\t\t\tcc.nodeId === this.id\n\t\t\t\t\t\t\t\t\t&& cc\n\t\t\t\t\t\t\t\t\t\tinstanceof FirmwareUpdateMetaDataCCGet,\n\t\t\t\t\t\t\t\t// Wait up to 2 minutes for each fragment request.\n\t\t\t\t\t\t\t\t// Some users try to update devices with unstable connections, where 30s can be too short.\n\t\t\t\t\t\t\t\ttimespan.minutes(2),\n\t\t\t\t\t\t\t);\n\t\t\t\t} catch {\n\t\t\t\t\t// In some cases it can happen that the device stops requesting update frames\n\t\t\t\t\t// We need to timeout the update in this case so it can be restarted\n\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\tmessage: `Firmware update timed out`,\n\t\t\t\t\t\tdirection: \"none\",\n\t\t\t\t\t\tlevel: \"warn\",\n\t\t\t\t\t});\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tstatus: FirmwareUpdateStatus.Error_Timeout,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// When a node requests a firmware update fragment, it must be awake\n\t\t\tthis.markAsAwake();\n\n\t\t\tif (fragmentRequest.reportNumber > numFragments) {\n\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Received Firmware Update Get for an out-of-bounds fragment. Forcing the node to abort...`,\n\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t});\n\t\t\t\tawait this.sendCorruptedFirmwareUpdateReport(\n\t\t\t\t\tfragmentRequest.reportNumber,\n\t\t\t\t\trandomBytes(fragmentSize),\n\t\t\t\t\tnonSecureTransfer,\n\t\t\t\t);\n\t\t\t\t// This will cause the node to abort the process, wait for that\n\t\t\t\tbreak update;\n\t\t\t}\n\n\t\t\t// Actually send the requested frames\n\t\t\trequest: for (\n\t\t\t\tlet num = fragmentRequest.reportNumber;\n\t\t\t\tnum\n\t\t\t\t\t< fragmentRequest.reportNumber\n\t\t\t\t\t\t+ fragmentRequest.numReports;\n\t\t\t\tnum++\n\t\t\t) {\n\t\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t\t// Check if the node requested more fragments than are left\n\t\t\t\tif (num > numFragments) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tconst fragment = data.subarray(\n\t\t\t\t\t(num - 1) * fragmentSize,\n\t\t\t\t\tnum * fragmentSize,\n\t\t\t\t);\n\n\t\t\t\tif (abortContext.abort) {\n\t\t\t\t\tawait this.sendCorruptedFirmwareUpdateReport(\n\t\t\t\t\t\tfragmentRequest.reportNumber,\n\t\t\t\t\t\trandomBytes(fragment.length),\n\t\t\t\t\t\tnonSecureTransfer,\n\t\t\t\t\t);\n\t\t\t\t\t// This will cause the node to abort the process, wait for that\n\t\t\t\t\tbreak update;\n\t\t\t\t} else {\n\t\t\t\t\t// Avoid queuing duplicate fragments\n\t\t\t\t\tif (this.hasPendingFirmwareUpdateFragment(num)) {\n\t\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\t\tmessage: `Firmware fragment ${num} already queued`,\n\t\t\t\t\t\t\tlevel: \"warn\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcontinue request;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t`Sending firmware fragment ${num} / ${numFragments}`,\n\t\t\t\t\t\tdirection: \"outbound\",\n\t\t\t\t\t});\n\t\t\t\t\tconst isLast = num === numFragments;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this\n\t\t\t\t\t\t\t.commandClasses[\"Firmware Update Meta Data\"]\n\t\t\t\t\t\t\t.withOptions({\n\t\t\t\t\t\t\t\t// Only encapsulate if the transfer is secure\n\t\t\t\t\t\t\t\tautoEncapsulate: !nonSecureTransfer,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.sendFirmwareFragment(num, isLast, fragment);\n\n\t\t\t\t\t\tonProgress(num, numFragments);\n\n\t\t\t\t\t\t// If that was the last one wait for status report from the node and restart interview\n\t\t\t\t\t\tif (isLast) {\n\t\t\t\t\t\t\tabortContext.tooLateToAbort = true;\n\t\t\t\t\t\t\tbreak update;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// When transmitting fails, simply stop responding to this request and wait for the node to re-request the fragment\n\t\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t`Failed to send firmware fragment ${num} / ${numFragments}`,\n\t\t\t\t\t\t\tdirection: \"outbound\",\n\t\t\t\t\t\t\tlevel: \"warn\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak request;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tyield; // Give the task scheduler time to do something else\n\n\t\t// ================================\n\t\t// STEP 5:\n\t\t// Finalize the update process\n\n\t\tconst statusReport:\n\t\t\t| FirmwareUpdateMetaDataCCStatusReport\n\t\t\t| undefined = yield () =>\n\t\t\t\tthis.driver\n\t\t\t\t\t.waitForCommand(\n\t\t\t\t\t\t(cc) =>\n\t\t\t\t\t\t\tcc.nodeId === this.id\n\t\t\t\t\t\t\t&& cc\n\t\t\t\t\t\t\t\tinstanceof FirmwareUpdateMetaDataCCStatusReport,\n\t\t\t\t\t\t// Wait up to 5 minutes. It should never take that long, but the specs\n\t\t\t\t\t\t// don't say anything specific\n\t\t\t\t\t\t5 * 60000,\n\t\t\t\t\t)\n\t\t\t\t\t.catch(() => undefined);\n\n\t\tif (abortContext.abort) {\n\t\t\tabortContext.abortPromise.resolve(\n\t\t\t\tstatusReport?.status\n\t\t\t\t\t=== FirmwareUpdateStatus.Error_TransmissionFailed,\n\t\t\t);\n\t\t}\n\n\t\tif (!statusReport) {\n\t\t\tthis.driver.controllerLog.logNode(\n\t\t\t\tthis.id,\n\t\t\t\t`The node did not acknowledge the completed update`,\n\t\t\t\t\"warn\",\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tstatus: FirmwareUpdateStatus.Error_Timeout,\n\t\t\t};\n\t\t}\n\n\t\tconst { status, waitTime } = statusReport;\n\n\t\t// Actually, OK_WaitingForActivation should never happen since we don't allow\n\t\t// delayed activation in the RequestGet command\n\t\tconst success = status >= FirmwareUpdateStatus.OK_WaitingForActivation;\n\n\t\treturn {\n\t\t\tsuccess,\n\t\t\tstatus,\n\t\t\twaitTime,\n\t\t};\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import {\n\ttype FirmwareUpdateMetaData,\n\tFirmwareUpdateMetaDataCC,\n\tFirmwareUpdateMetaDataCCGet,\n\ttype FirmwareUpdateMetaDataCCMetaDataGet,\n\tFirmwareUpdateMetaDataCCReport,\n\tFirmwareUpdateMetaDataCCRequestReport,\n\tFirmwareUpdateMetaDataCCStatusReport,\n\ttype FirmwareUpdateOptions,\n\ttype FirmwareUpdateProgress,\n\tFirmwareUpdateRequestStatus,\n\ttype FirmwareUpdateResult,\n\tFirmwareUpdateStatus,\n\tgetEffectiveCCVersion,\n} from \"@zwave-js/cc\";\nimport {\n\tCRC16_CCITT,\n\tCommandClasses,\n\tEncapsulationFlags,\n\ttype Firmware,\n\tSecurityClass,\n\tZWaveError,\n\tZWaveErrorCodes,\n\trandomBytes,\n\tsecurityClassIsS2,\n\ttimespan,\n} from \"@zwave-js/core\";\nimport { containsCC } from \"@zwave-js/serial/serialapi\";\nimport { getEnumMemberName, throttle } from \"@zwave-js/shared\";\nimport { distinct } from \"alcalzone-shared/arrays\";\nimport { wait } from \"alcalzone-shared/async\";\nimport {\n\ttype DeferredPromise,\n\tcreateDeferredPromise,\n} from \"alcalzone-shared/deferred-promise\";\nimport { roundTo } from \"alcalzone-shared/math\";\nimport {\n\ttype Task,\n\ttype TaskBuilder,\n\tTaskPriority,\n} from \"../../driver/Task.js\";\nimport { type Transaction } from \"../../driver/Transaction.js\";\nimport { SchedulePollMixin } from \"./60_ScheduledPoll.js\";\n\ninterface AbortFirmwareUpdateContext {\n\tabort: boolean;\n\ttooLateToAbort: boolean;\n\tabortPromise: DeferredPromise<boolean>;\n}\n\ntype PartialFirmwareUpdateResult =\n\t& Pick<FirmwareUpdateResult, \"status\" | \"waitTime\">\n\t& { success: boolean };\n\n/** Checks if a task belongs to a route rebuilding process */\nexport function isFirmwareUpdateOTATask(t: Task<unknown>): boolean {\n\treturn t.tag?.id === \"firmware-update-ota\";\n}\n\nexport interface NodeFirmwareUpdate {\n\t/**\n\t * Aborts an active firmware update process\n\t */\n\tabortFirmwareUpdate(): Promise<void>;\n\n\t/**\n\t * Performs an OTA firmware upgrade of one or more chips on this node.\n\t *\n\t * This method will resolve after the process has **COMPLETED**. Failure to start any one of the provided updates will throw an error.\n\t *\n\t * **WARNING: Use at your own risk! We don't take any responsibility if your devices don't work after an update.**\n\t *\n\t * @param updates An array of firmware updates that will be done in sequence\n\t *\n\t * @returns Whether all of the given updates were successful.\n\t */\n\tupdateFirmware(\n\t\tupdates: Firmware[],\n\t\toptions?: FirmwareUpdateOptions,\n\t): Promise<FirmwareUpdateResult>;\n\n\t/**\n\t * Returns whether a firmware update is in progress for this node.\n\t */\n\tisFirmwareUpdateInProgress(): boolean;\n}\n\nexport abstract class FirmwareUpdateMixin extends SchedulePollMixin\n\timplements NodeFirmwareUpdate\n{\n\tprivate _abortFirmwareUpdate: (() => Promise<void>) | undefined;\n\tpublic async abortFirmwareUpdate(): Promise<void> {\n\t\tif (!this._abortFirmwareUpdate) return;\n\t\tawait this._abortFirmwareUpdate();\n\t}\n\n\t// Stores the CRC of the previously transferred firmware image.\n\t// Allows detecting whether resuming is supported and where to continue in a multi-file transfer.\n\tprivate _previousFirmwareCRC: number | undefined;\n\n\t/** Is used to remember fragment requests that came in before they were able to be handled */\n\tprivate _firmwareUpdatePrematureRequest:\n\t\t| FirmwareUpdateMetaDataCCGet\n\t\t| undefined;\n\n\tpublic async updateFirmware(\n\t\tupdates: Firmware[],\n\t\toptions: FirmwareUpdateOptions = {},\n\t): Promise<FirmwareUpdateResult> {\n\t\tif (updates.length === 0) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`At least one update must be provided`,\n\t\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t\t);\n\t\t}\n\n\t\t// Check that each update has a buffer with at least 1 byte\n\t\tif (updates.some((u) => u.data.length === 0)) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`All firmware updates must have a non-empty data buffer`,\n\t\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t\t);\n\t\t}\n\n\t\t// Check that the targets are not duplicates\n\t\tif (\n\t\t\tdistinct(updates.map((u) => u.firmwareTarget ?? 0)).length\n\t\t\t\t!== updates.length\n\t\t) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`The target of all provided firmware updates must be unique`,\n\t\t\t\tZWaveErrorCodes.Argument_Invalid,\n\t\t\t);\n\t\t}\n\n\t\t// Don't start the process twice\n\t\tif (this.driver.controller.isFirmwareUpdateInProgress()) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`Failed to start the update: An OTW upgrade of the controller is in progress!`,\n\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_Busy,\n\t\t\t);\n\t\t}\n\n\t\t// Don't allow starting two firmware updates for the same node\n\t\tconst task = this.getUpdateFirmwareTask(updates, options);\n\t\tif (task instanceof Promise) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`Failed to start the update: A firmware update is already in progress for this node!`,\n\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_Busy,\n\t\t\t);\n\t\t}\n\n\t\t// Queue the task\n\t\treturn this.driver.scheduler.queueTask(task);\n\t}\n\n\tpublic isFirmwareUpdateInProgress(): boolean {\n\t\treturn !!this.driver.scheduler.findTask(isFirmwareUpdateOTATask);\n\t}\n\n\tprivate getUpdateFirmwareTask(\n\t\tupdates: Firmware[],\n\t\toptions: FirmwareUpdateOptions = {},\n\t): Promise<FirmwareUpdateResult> | TaskBuilder<FirmwareUpdateResult> {\n\t\tconst self = this;\n\n\t\t// This task should only run once at a time\n\t\tconst existingTask = this.driver.scheduler.findTask<\n\t\t\tFirmwareUpdateResult\n\t\t>((t) =>\n\t\t\tt.tag?.id === \"firmware-update-ota\"\n\t\t\t&& t.tag.nodeId === self.id\n\t\t);\n\t\tif (existingTask) return existingTask;\n\n\t\tlet keepAwake: boolean;\n\n\t\treturn {\n\t\t\t// Firmware updates cause a lot of traffic. Execute them in the background.\n\t\t\tpriority: TaskPriority.Lower,\n\t\t\ttag: { id: \"firmware-update-ota\", nodeId: self.id },\n\t\t\ttask: async function* firmwareUpdateTask() {\n\t\t\t\t// Keep battery powered nodes awake during the process\n\t\t\t\tkeepAwake = self.keepAwake;\n\t\t\t\tself.keepAwake = true;\n\n\t\t\t\t// Support aborting the update\n\t\t\t\tconst abortContext = {\n\t\t\t\t\tabort: false,\n\t\t\t\t\ttooLateToAbort: false,\n\t\t\t\t\tabortPromise: createDeferredPromise<boolean>(),\n\t\t\t\t};\n\n\t\t\t\tself._abortFirmwareUpdate = async () => {\n\t\t\t\t\tif (abortContext.tooLateToAbort) {\n\t\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t\t`The firmware update was transmitted completely, cannot abort anymore.`,\n\t\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\tmessage: `Aborting firmware update...`,\n\t\t\t\t\t\tdirection: \"outbound\",\n\t\t\t\t\t});\n\n\t\t\t\t\t// Trigger the abort\n\t\t\t\t\tabortContext.abort = true;\n\t\t\t\t\tconst aborted = await abortContext.abortPromise;\n\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t\t`The node did not acknowledge the aborted update`,\n\t\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\tmessage: `Firmware update aborted`,\n\t\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\t// Prepare the firmware update\n\t\t\t\tlet fragmentSizeSecure: number;\n\t\t\t\tlet fragmentSizeNonSecure: number;\n\t\t\t\tlet hardwareVersion: number | undefined;\n\t\t\t\tlet meta: FirmwareUpdateMetaData;\n\t\t\t\ttry {\n\t\t\t\t\tconst prepareResult = await self\n\t\t\t\t\t\t.prepareFirmwareUpdateInternal(\n\t\t\t\t\t\t\tupdates.map((u) => u.firmwareTarget ?? 0),\n\t\t\t\t\t\t\tabortContext,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t// Handle early aborts\n\t\t\t\t\tif (abortContext.abort) {\n\t\t\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\tstatus: FirmwareUpdateStatus\n\t\t\t\t\t\t\t\t.Error_TransmissionFailed,\n\t\t\t\t\t\t\treInterview: false,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tself._emit(\n\t\t\t\t\t\t\t\"firmware update finished\",\n\t\t\t\t\t\t\tself,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\n\t\t\t\t\t// If the firmware update was not aborted, prepareResult is definitely defined\n\t\t\t\t\t({\n\t\t\t\t\t\tfragmentSizeSecure,\n\t\t\t\t\t\tfragmentSizeNonSecure,\n\t\t\t\t\t\thardwareVersion,\n\t\t\t\t\t\t...meta\n\t\t\t\t\t} = prepareResult!);\n\t\t\t\t} catch {\n\t\t\t\t\t// Not sure what the error is, but we'll label it \"transmission failed\"\n\t\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tstatus: FirmwareUpdateStatus.Error_TransmissionFailed,\n\t\t\t\t\t\treInterview: false,\n\t\t\t\t\t};\n\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t\t// The resume and non-secure transfer features may not be supported by the node\n\t\t\t\t// If not, disable them, even though the application requested them\n\t\t\t\tif (!meta.supportsResuming) options.resume = false;\n\n\t\t\t\tconst securityClass = self.getHighestSecurityClass();\n\t\t\t\tconst isSecure = securityClass === SecurityClass.S0_Legacy\n\t\t\t\t\t|| securityClassIsS2(securityClass);\n\t\t\t\tif (!isSecure) {\n\t\t\t\t\t// The nonSecureTransfer option is only relevant for secure devices\n\t\t\t\t\toptions.nonSecureTransfer = false;\n\t\t\t\t} else if (!meta.supportsNonSecureTransfer) {\n\t\t\t\t\toptions.nonSecureTransfer = false;\n\t\t\t\t}\n\n\t\t\t\t// Throttle the progress emitter so applications can handle the load of events\n\t\t\t\tconst notifyProgress = throttle(\n\t\t\t\t\t(progress) =>\n\t\t\t\t\t\tself._emit(\n\t\t\t\t\t\t\t\"firmware update progress\",\n\t\t\t\t\t\t\tself,\n\t\t\t\t\t\t\tprogress,\n\t\t\t\t\t\t),\n\t\t\t\t\t250,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\n\t\t\t\t// If resuming is supported and desired, try to figure out with which file to continue\n\t\t\t\tconst updatesWithChecksum = updates.map((u) => ({\n\t\t\t\t\t...u,\n\t\t\t\t\tchecksum: CRC16_CCITT(u.data),\n\t\t\t\t}));\n\t\t\t\tlet skipFinishedFiles = -1;\n\t\t\t\tlet shouldResume = options.resume\n\t\t\t\t\t&& self._previousFirmwareCRC != undefined;\n\t\t\t\tif (shouldResume) {\n\t\t\t\t\tskipFinishedFiles = updatesWithChecksum.findIndex(\n\t\t\t\t\t\t(u) => u.checksum === self._previousFirmwareCRC,\n\t\t\t\t\t);\n\t\t\t\t\tif (skipFinishedFiles === -1) shouldResume = false;\n\t\t\t\t}\n\n\t\t\t\t// Perform all firmware updates in sequence\n\t\t\t\tlet updateResult!: PartialFirmwareUpdateResult;\n\t\t\t\tlet conservativeWaitTime: number;\n\n\t\t\t\tconst totalBytes: number = updatesWithChecksum.reduce(\n\t\t\t\t\t(total, update) => total + update.data.length,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\t\tlet sentBytesOfPreviousFiles = 0;\n\n\t\t\t\tfor (let i = 0; i < updatesWithChecksum.length; i++) {\n\t\t\t\t\tconst { firmwareTarget: target = 0, data, checksum } =\n\t\t\t\t\t\tupdatesWithChecksum[i];\n\n\t\t\t\t\tif (i < skipFinishedFiles) {\n\t\t\t\t\t\t// If we are resuming, skip this file since it was already done before\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Skipping already completed firmware update (part ${\n\t\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t\t} / ${updatesWithChecksum.length})...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsentBytesOfPreviousFiles += data.length;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t`Updating firmware (part ${\n\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t} / ${updatesWithChecksum.length})...`,\n\t\t\t\t\t);\n\n\t\t\t\t\t// For determining the initial fragment size, assume the node respects our choice.\n\t\t\t\t\t// If the node is not secure, these two values are identical anyways.\n\t\t\t\t\tlet fragmentSize = options.nonSecureTransfer\n\t\t\t\t\t\t? fragmentSizeNonSecure\n\t\t\t\t\t\t: fragmentSizeSecure;\n\n\t\t\t\t\t// Tell the node to start requesting fragments\n\t\t\t\t\tconst { resume, nonSecureTransfer } = yield* self\n\t\t\t\t\t\t.beginFirmwareUpdateInternal(\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\tmeta,\n\t\t\t\t\t\t\tfragmentSize,\n\t\t\t\t\t\t\tchecksum,\n\t\t\t\t\t\t\thardwareVersion,\n\t\t\t\t\t\t\tshouldResume,\n\t\t\t\t\t\t\toptions.nonSecureTransfer,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t// If the node did not accept non-secure transfer, revisit our choice of fragment size\n\t\t\t\t\tif (options.nonSecureTransfer && !nonSecureTransfer) {\n\t\t\t\t\t\tfragmentSize = fragmentSizeSecure;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remember the checksum, so we can resume if necessary\n\t\t\t\t\tself._previousFirmwareCRC = checksum;\n\n\t\t\t\t\tif (shouldResume) {\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Node ${\n\t\t\t\t\t\t\t\tresume ? \"accepted\" : \"did not accept\"\n\t\t\t\t\t\t\t} resuming the update...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (nonSecureTransfer) {\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Firmware will be transferred without encryption...`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t\t\t// Listen for firmware update fragment requests and handle them\n\t\t\t\t\tupdateResult = yield* self.doFirmwareUpdateInternal(\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\tfragmentSize,\n\t\t\t\t\t\tnonSecureTransfer,\n\t\t\t\t\t\tabortContext,\n\t\t\t\t\t\t(fragment, total) => {\n\t\t\t\t\t\t\tconst progress: FirmwareUpdateProgress = {\n\t\t\t\t\t\t\t\tcurrentFile: i + 1,\n\t\t\t\t\t\t\t\ttotalFiles: updatesWithChecksum.length,\n\t\t\t\t\t\t\t\tsentFragments: fragment,\n\t\t\t\t\t\t\t\ttotalFragments: total,\n\t\t\t\t\t\t\t\tprogress: roundTo(\n\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t(sentBytesOfPreviousFiles\n\t\t\t\t\t\t\t\t\t\t\t+ Math.min(\n\t\t\t\t\t\t\t\t\t\t\t\tfragment * fragmentSize,\n\t\t\t\t\t\t\t\t\t\t\t\tdata.length,\n\t\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t\t/ totalBytes\n\t\t\t\t\t\t\t\t\t) * 100,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tnotifyProgress(progress);\n\n\t\t\t\t\t\t\t// When this file is done, add the fragments to the total, so we can compute the total progress correctly\n\t\t\t\t\t\t\tif (fragment === total) {\n\t\t\t\t\t\t\t\tsentBytesOfPreviousFiles += data.length;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\n\t\t\t\t\t// If we wait, wait a bit longer than the device told us, so it is actually ready to use\n\t\t\t\t\tconservativeWaitTime = self.driver\n\t\t\t\t\t\t.getConservativeWaitTimeAfterFirmwareUpdate(\n\t\t\t\t\t\t\tupdateResult.waitTime,\n\t\t\t\t\t\t);\n\n\t\t\t\t\tif (!updateResult.success) {\n\t\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\t\tmessage: `Firmware update (part ${\n\t\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t\t} / ${updatesWithChecksum.length}) failed with status ${\n\t\t\t\t\t\t\t\tgetEnumMemberName(\n\t\t\t\t\t\t\t\t\tFirmwareUpdateStatus,\n\t\t\t\t\t\t\t\t\tupdateResult.status,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t\t\t...updateResult,\n\t\t\t\t\t\t\twaitTime: undefined,\n\t\t\t\t\t\t\treInterview: false,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tself._emit(\n\t\t\t\t\t\t\t\"firmware update finished\",\n\t\t\t\t\t\t\tself,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} else if (i < updatesWithChecksum.length - 1) {\n\t\t\t\t\t\t// Update succeeded, but we're not done yet\n\n\t\t\t\t\t\tself.driver.controllerLog.logNode(self.id, {\n\t\t\t\t\t\t\tmessage: `Firmware update (part ${\n\t\t\t\t\t\t\t\ti + 1\n\t\t\t\t\t\t\t} / ${updatesWithChecksum.length}) succeeded with status ${\n\t\t\t\t\t\t\t\tgetEnumMemberName(\n\t\t\t\t\t\t\t\t\tFirmwareUpdateStatus,\n\t\t\t\t\t\t\t\t\tupdateResult.status,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tself.driver.controllerLog.logNode(\n\t\t\t\t\t\t\tself.id,\n\t\t\t\t\t\t\t`Continuing with next part in ${conservativeWaitTime} seconds...`,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// If we've resumed the previous file, there's no need to resume the next one too\n\t\t\t\t\t\tshouldResume = false;\n\n\t\t\t\t\t\tyield () => wait(conservativeWaitTime * 1000, true);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// We're done. No need to resume this update\n\t\t\t\tself._previousFirmwareCRC = undefined;\n\n\t\t\t\tconst result: FirmwareUpdateResult = {\n\t\t\t\t\t...updateResult,\n\t\t\t\t\twaitTime: conservativeWaitTime!,\n\t\t\t\t\treInterview: true,\n\t\t\t\t};\n\n\t\t\t\t// After a successful firmware update, we want to interview sleeping nodes immediately,\n\t\t\t\t// so don't send them to sleep when they wake up\n\t\t\t\tkeepAwake = true;\n\n\t\t\t\tself._emit(\"firmware update finished\", self, result);\n\n\t\t\t\treturn result;\n\t\t\t},\n\t\t\tcleanup() {\n\t\t\t\tself._abortFirmwareUpdate = undefined;\n\t\t\t\tself._firmwareUpdatePrematureRequest = undefined;\n\n\t\t\t\t// Make sure that the keepAwake flag gets reset at the end\n\t\t\t\tself.keepAwake = keepAwake;\n\t\t\t\tif (!keepAwake) {\n\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\tself.driver.debounceSendNodeToSleep(self);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn Promise.resolve();\n\t\t\t},\n\t\t};\n\t}\n\n\t/** Prepares the firmware update of a single target by collecting the necessary information */\n\tprivate async prepareFirmwareUpdateInternal(\n\t\ttargets: number[],\n\t\tabortContext: AbortFirmwareUpdateContext,\n\t): Promise<\n\t\t| undefined\n\t\t| (FirmwareUpdateMetaData & {\n\t\t\tfragmentSizeSecure: number;\n\t\t\tfragmentSizeNonSecure: number;\n\t\t})\n\t> {\n\t\tconst api = this.commandClasses[\"Firmware Update Meta Data\"];\n\n\t\t// ================================\n\t\t// STEP 1:\n\t\t// Check if this update is possible\n\t\tconst meta = await api.getMetaData();\n\t\tif (!meta) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`Failed to start the update: The node did not respond in time!`,\n\t\t\t\tZWaveErrorCodes.Controller_NodeTimeout,\n\t\t\t);\n\t\t}\n\n\t\tfor (const target of targets) {\n\t\t\tif (target === 0) {\n\t\t\t\tif (!meta.firmwareUpgradable) {\n\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t`Failed to start the update: The Z-Wave chip firmware is not upgradable!`,\n\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (api.version < 3) {\n\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t`Failed to start the update: The node does not support upgrading a different firmware target than 0!`,\n\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound,\n\t\t\t\t\t);\n\t\t\t\t} else if (\n\t\t\t\t\tmeta.additionalFirmwareIDs[target - 1] == undefined\n\t\t\t\t) {\n\t\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t\t`Failed to start the update: Firmware target #${target} not found on this node!`,\n\t\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// ================================\n\t\t// STEP 2:\n\t\t// Determine the fragment size\n\t\tconst fcc = new FirmwareUpdateMetaDataCC({ nodeId: this.id });\n\t\tfcc.toggleEncapsulationFlag(\n\t\t\tEncapsulationFlags.Security,\n\t\t\tthis.driver.isCCSecure(fcc.ccId, this.id),\n\t\t);\n\t\tconst maxGrossPayloadSizeSecure = this.driver\n\t\t\t.computeNetCCPayloadSize(\n\t\t\t\tfcc,\n\t\t\t);\n\t\tconst maxGrossPayloadSizeNonSecure = this.driver\n\t\t\t.computeNetCCPayloadSize(fcc, true);\n\n\t\tconst ccVersion = getEffectiveCCVersion(this.driver, fcc);\n\t\tconst maxNetPayloadSizeSecure = maxGrossPayloadSizeSecure\n\t\t\t- 2 // report number\n\t\t\t- (ccVersion >= 2 ? 2 : 0); // checksum\n\t\tconst maxNetPayloadSizeNonSecure = maxGrossPayloadSizeNonSecure\n\t\t\t- 2 // report number\n\t\t\t- (ccVersion >= 2 ? 2 : 0); // checksum\n\n\t\t// Use the smallest allowed payload\n\t\tconst fragmentSizeSecure = Math.min(\n\t\t\tmaxNetPayloadSizeSecure,\n\t\t\tmeta.maxFragmentSize ?? Number.POSITIVE_INFINITY,\n\t\t);\n\t\tconst fragmentSizeNonSecure = Math.min(\n\t\t\tmaxNetPayloadSizeNonSecure,\n\t\t\tmeta.maxFragmentSize ?? Number.POSITIVE_INFINITY,\n\t\t);\n\n\t\tif (abortContext.abort) {\n\t\t\tabortContext.abortPromise.resolve(true);\n\t\t\treturn;\n\t\t} else {\n\t\t\treturn {\n\t\t\t\t...meta,\n\t\t\t\tfragmentSizeSecure,\n\t\t\t\tfragmentSizeNonSecure,\n\t\t\t};\n\t\t}\n\t}\n\n\tprotected async handleUnexpectedFirmwareUpdateGet(\n\t\tcommand: FirmwareUpdateMetaDataCCGet,\n\t): Promise<void> {\n\t\t// This method will only be called under two circumstances:\n\t\t// 1. The node is currently busy responding to a firmware update request -> remember the request\n\t\tif (this.isFirmwareUpdateInProgress()) {\n\t\t\tthis._firmwareUpdatePrematureRequest = command;\n\t\t\treturn;\n\t\t}\n\n\t\t// 2. No firmware update is in progress -> abort\n\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\tmessage:\n\t\t\t\t`Received Firmware Update Get, but no firmware update is in progress. Forcing the node to abort...`,\n\t\t\tdirection: \"inbound\",\n\t\t});\n\n\t\t// Since no update is in progress, we need to determine the fragment size again\n\t\tconst fcc = new FirmwareUpdateMetaDataCC({ nodeId: this.id });\n\t\tfcc.toggleEncapsulationFlag(\n\t\t\tEncapsulationFlags.Security,\n\t\t\t!!(command.encapsulationFlags & EncapsulationFlags.Security),\n\t\t);\n\t\tconst ccVersion = getEffectiveCCVersion(this.driver, fcc);\n\t\tconst fragmentSize = this.driver.computeNetCCPayloadSize(fcc)\n\t\t\t- 2 // report number\n\t\t\t- (ccVersion >= 2 ? 2 : 0); // checksum\n\t\tconst fragment = randomBytes(fragmentSize);\n\t\ttry {\n\t\t\tawait this.sendCorruptedFirmwareUpdateReport(\n\t\t\t\tcommand.reportNumber,\n\t\t\t\tfragment,\n\t\t\t);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\t/** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */\n\tprivate async *beginFirmwareUpdateInternal(\n\t\tdata: Uint8Array,\n\t\ttarget: number,\n\t\tmeta: FirmwareUpdateMetaData,\n\t\tfragmentSize: number,\n\t\tchecksum: number,\n\t\thardwareVersion?: number,\n\t\tresume?: boolean,\n\t\tnonSecureTransfer?: boolean,\n\t) {\n\t\tconst api = this.commandClasses[\"Firmware Update Meta Data\"];\n\n\t\t// ================================\n\t\t// STEP 3:\n\t\t// Start the update\n\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\tmessage: `Starting firmware update...`,\n\t\t\tdirection: \"outbound\",\n\t\t});\n\n\t\t// Request the node to start the upgrade\n\t\tawait api.requestUpdate({\n\t\t\t// TODO: Should manufacturer id and firmware id be provided externally?\n\t\t\tmanufacturerId: meta.manufacturerId,\n\t\t\tfirmwareId: target == 0\n\t\t\t\t? meta.firmwareId\n\t\t\t\t: meta.additionalFirmwareIDs[target - 1],\n\t\t\tfirmwareTarget: target,\n\t\t\tfragmentSize,\n\t\t\tchecksum,\n\t\t\thardwareVersion,\n\t\t\tresume,\n\t\t\tnonSecureTransfer,\n\t\t});\n\t\t// Pause the task until the response is received, because that can take\n\t\t// up to a minute\n\t\tconst result: FirmwareUpdateMetaDataCCRequestReport = yield () =>\n\t\t\tthis.driver\n\t\t\t\t.waitForCommand<FirmwareUpdateMetaDataCCRequestReport>(\n\t\t\t\t\t(cc) =>\n\t\t\t\t\t\tcc instanceof FirmwareUpdateMetaDataCCRequestReport\n\t\t\t\t\t\t&& cc.nodeId === this.id,\n\t\t\t\t\t60000,\n\t\t\t\t);\n\n\t\tswitch (result.status) {\n\t\t\tcase FirmwareUpdateRequestStatus.Error_AuthenticationExpected:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: A manual authentication event (e.g. button push) was expected!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_BatteryLow:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: The battery level is too low!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus\n\t\t\t\t.Error_FirmwareUpgradeInProgress:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: A firmware upgrade is already in progress!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_Busy,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus\n\t\t\t\t.Error_InvalidManufacturerOrFirmwareID:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: Invalid manufacturer or firmware id!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_InvalidHardwareVersion:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: Invalid hardware version!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_NotUpgradable:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: Firmware target #${target} is not upgradable!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.Error_FragmentSizeTooLarge:\n\t\t\t\tthrow new ZWaveError(\n\t\t\t\t\t`Failed to start the update: The chosen fragment size is too large!`,\n\t\t\t\t\tZWaveErrorCodes.FirmwareUpdateCC_FailedToStart,\n\t\t\t\t);\n\t\t\tcase FirmwareUpdateRequestStatus.OK:\n\t\t\t\t// All good, we have started!\n\t\t\t\t// Keep the node awake until the update is done.\n\t\t\t\tthis.keepAwake = true;\n\t\t}\n\n\t\treturn {\n\t\t\tresume: !!result.resume,\n\t\t\tnonSecureTransfer: !!result.nonSecureTransfer,\n\t\t};\n\t}\n\n\tprotected async handleFirmwareUpdateMetaDataGet(\n\t\tcommand: FirmwareUpdateMetaDataCCMetaDataGet,\n\t): Promise<void> {\n\t\tconst endpoint = this.getEndpoint(command.endpointIndex)\n\t\t\t?? this;\n\n\t\t// We are being queried, so the device may actually not support the CC, just control it.\n\t\t// Using the commandClasses property would throw in that case\n\t\tconst api = endpoint\n\t\t\t.createAPI(CommandClasses[\"Firmware Update Meta Data\"], false)\n\t\t\t.withOptions({\n\t\t\t\t// Answer with the same encapsulation as asked, but omit\n\t\t\t\t// Supervision as it shouldn't be used for Get-Report flows\n\t\t\t\tencapsulationFlags: command.encapsulationFlags\n\t\t\t\t\t& ~EncapsulationFlags.Supervision,\n\t\t\t});\n\n\t\t// We do not support the firmware to be upgraded.\n\t\tawait api.reportMetaData({\n\t\t\tmanufacturerId: this.driver.options.vendor?.manufacturerId\n\t\t\t\t?? 0xffff,\n\t\t\tfirmwareUpgradable: false,\n\t\t\thardwareVersion: this.driver.options.vendor?.hardwareVersion\n\t\t\t\t?? 0,\n\t\t});\n\t}\n\n\tprivate async sendCorruptedFirmwareUpdateReport(\n\t\treportNum: number,\n\t\tfragment: Uint8Array,\n\t\tnonSecureTransfer: boolean = false,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait this.commandClasses[\"Firmware Update Meta Data\"]\n\t\t\t\t.withOptions({\n\t\t\t\t\t// Only encapsulate if the transfer is secure\n\t\t\t\t\tautoEncapsulate: !nonSecureTransfer,\n\t\t\t\t})\n\t\t\t\t.sendFirmwareFragment(reportNum, true, fragment);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tprivate hasPendingFirmwareUpdateFragment(\n\t\tfragmentNumber: number,\n\t): boolean {\n\t\t// Avoid queuing duplicate fragments\n\t\tconst isCurrentFirmwareFragment = (t: Transaction) =>\n\t\t\tt.message.getNodeId() === this.id\n\t\t\t&& containsCC(t.message)\n\t\t\t&& t.message.command instanceof FirmwareUpdateMetaDataCCReport\n\t\t\t&& t.message.command.reportNumber === fragmentNumber;\n\n\t\treturn this.driver.hasPendingTransactions(\n\t\t\tisCurrentFirmwareFragment,\n\t\t);\n\t}\n\n\tprivate async *doFirmwareUpdateInternal(\n\t\tdata: Uint8Array,\n\t\tfragmentSize: number,\n\t\tnonSecureTransfer: boolean,\n\t\tabortContext: AbortFirmwareUpdateContext,\n\t\tonProgress: (fragment: number, total: number) => void,\n\t): AsyncGenerator<\n\t\tany,\n\t\tPartialFirmwareUpdateResult,\n\t\tany\n\t> {\n\t\tconst numFragments = Math.ceil(data.length / fragmentSize);\n\n\t\t// Make sure we're not responding to an outdated request immediately\n\t\tthis._firmwareUpdatePrematureRequest = undefined;\n\n\t\t// ================================\n\t\t// STEP 4:\n\t\t// Respond to fragment requests from the node\n\t\tupdate: while (true) {\n\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t// During ongoing firmware updates, it can happen that the next request is received before the callback for the previous response\n\t\t\t// is back. In that case we can immediately handle the premature request. Otherwise wait for the next request.\n\t\t\tlet fragmentRequest: FirmwareUpdateMetaDataCCGet;\n\t\t\tif (this._firmwareUpdatePrematureRequest) {\n\t\t\t\tfragmentRequest = this._firmwareUpdatePrematureRequest;\n\t\t\t\tthis._firmwareUpdatePrematureRequest = undefined;\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tfragmentRequest = yield () =>\n\t\t\t\t\t\tthis.driver\n\t\t\t\t\t\t\t.waitForCommand<FirmwareUpdateMetaDataCCGet>(\n\t\t\t\t\t\t\t\t(cc) =>\n\t\t\t\t\t\t\t\t\tcc.nodeId === this.id\n\t\t\t\t\t\t\t\t\t&& cc\n\t\t\t\t\t\t\t\t\t\tinstanceof FirmwareUpdateMetaDataCCGet,\n\t\t\t\t\t\t\t\t// Wait up to 2 minutes for each fragment request.\n\t\t\t\t\t\t\t\t// Some users try to update devices with unstable connections, where 30s can be too short.\n\t\t\t\t\t\t\t\ttimespan.minutes(2),\n\t\t\t\t\t\t\t);\n\t\t\t\t} catch {\n\t\t\t\t\t// In some cases it can happen that the device stops requesting update frames\n\t\t\t\t\t// We need to timeout the update in this case so it can be restarted\n\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\tmessage: `Firmware update timed out`,\n\t\t\t\t\t\tdirection: \"none\",\n\t\t\t\t\t\tlevel: \"warn\",\n\t\t\t\t\t});\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\tstatus: FirmwareUpdateStatus.Error_Timeout,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// When a node requests a firmware update fragment, it must be awake\n\t\t\tthis.markAsAwake();\n\n\t\t\tif (fragmentRequest.reportNumber > numFragments) {\n\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Received Firmware Update Get for an out-of-bounds fragment. Forcing the node to abort...`,\n\t\t\t\t\tdirection: \"inbound\",\n\t\t\t\t});\n\t\t\t\tawait this.sendCorruptedFirmwareUpdateReport(\n\t\t\t\t\tfragmentRequest.reportNumber,\n\t\t\t\t\trandomBytes(fragmentSize),\n\t\t\t\t\tnonSecureTransfer,\n\t\t\t\t);\n\t\t\t\t// This will cause the node to abort the process, wait for that\n\t\t\t\tbreak update;\n\t\t\t}\n\n\t\t\t// Actually send the requested frames\n\t\t\trequest: for (\n\t\t\t\tlet num = fragmentRequest.reportNumber;\n\t\t\t\tnum\n\t\t\t\t\t< fragmentRequest.reportNumber\n\t\t\t\t\t\t+ fragmentRequest.numReports;\n\t\t\t\tnum++\n\t\t\t) {\n\t\t\t\tyield; // Give the task scheduler time to do something else\n\n\t\t\t\t// Check if the node requested more fragments than are left\n\t\t\t\tif (num > numFragments) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tconst fragment = data.subarray(\n\t\t\t\t\t(num - 1) * fragmentSize,\n\t\t\t\t\tnum * fragmentSize,\n\t\t\t\t);\n\n\t\t\t\tif (abortContext.abort) {\n\t\t\t\t\tawait this.sendCorruptedFirmwareUpdateReport(\n\t\t\t\t\t\tfragmentRequest.reportNumber,\n\t\t\t\t\t\trandomBytes(fragment.length),\n\t\t\t\t\t\tnonSecureTransfer,\n\t\t\t\t\t);\n\t\t\t\t\t// This will cause the node to abort the process, wait for that\n\t\t\t\t\tbreak update;\n\t\t\t\t} else {\n\t\t\t\t\t// Avoid queuing duplicate fragments\n\t\t\t\t\tif (this.hasPendingFirmwareUpdateFragment(num)) {\n\t\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\t\tmessage: `Firmware fragment ${num} already queued`,\n\t\t\t\t\t\t\tlevel: \"warn\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcontinue request;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t`Sending firmware fragment ${num} / ${numFragments}`,\n\t\t\t\t\t\tdirection: \"outbound\",\n\t\t\t\t\t});\n\t\t\t\t\tconst isLast = num === numFragments;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this\n\t\t\t\t\t\t\t.commandClasses[\"Firmware Update Meta Data\"]\n\t\t\t\t\t\t\t.withOptions({\n\t\t\t\t\t\t\t\t// Only encapsulate if the transfer is secure\n\t\t\t\t\t\t\t\tautoEncapsulate: !nonSecureTransfer,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.sendFirmwareFragment(num, isLast, fragment);\n\n\t\t\t\t\t\tonProgress(num, numFragments);\n\n\t\t\t\t\t\t// If that was the last one wait for status report from the node and restart interview\n\t\t\t\t\t\tif (isLast) {\n\t\t\t\t\t\t\tabortContext.tooLateToAbort = true;\n\t\t\t\t\t\t\tbreak update;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// When transmitting fails, simply stop responding to this request and wait for the node to re-request the fragment\n\t\t\t\t\t\tthis.driver.controllerLog.logNode(this.id, {\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t`Failed to send firmware fragment ${num} / ${numFragments}`,\n\t\t\t\t\t\t\tdirection: \"outbound\",\n\t\t\t\t\t\t\tlevel: \"warn\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak request;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tyield; // Give the task scheduler time to do something else\n\n\t\t// ================================\n\t\t// STEP 5:\n\t\t// Finalize the update process\n\n\t\tconst statusReport:\n\t\t\t| FirmwareUpdateMetaDataCCStatusReport\n\t\t\t| undefined = yield () =>\n\t\t\t\tthis.driver\n\t\t\t\t\t.waitForCommand(\n\t\t\t\t\t\t(cc) =>\n\t\t\t\t\t\t\tcc.nodeId === this.id\n\t\t\t\t\t\t\t&& cc\n\t\t\t\t\t\t\t\tinstanceof FirmwareUpdateMetaDataCCStatusReport,\n\t\t\t\t\t\t// Wait up to 5 minutes. It should never take that long, but the specs\n\t\t\t\t\t\t// don't say anything specific\n\t\t\t\t\t\t5 * 60000,\n\t\t\t\t\t)\n\t\t\t\t\t.catch(() => undefined);\n\n\t\tif (abortContext.abort) {\n\t\t\tabortContext.abortPromise.resolve(\n\t\t\t\tstatusReport?.status\n\t\t\t\t\t=== FirmwareUpdateStatus.Error_TransmissionFailed,\n\t\t\t);\n\t\t}\n\n\t\tif (!statusReport) {\n\t\t\tthis.driver.controllerLog.logNode(\n\t\t\t\tthis.id,\n\t\t\t\t`The node did not acknowledge the completed update`,\n\t\t\t\t\"warn\",\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\tstatus: FirmwareUpdateStatus.Error_Timeout,\n\t\t\t};\n\t\t}\n\n\t\tconst { status, waitTime } = statusReport;\n\n\t\t// Actually, OK_WaitingForActivation should never happen since we don't allow\n\t\t// delayed activation in the RequestGet command\n\t\tconst success = status >= FirmwareUpdateStatus.OK_WaitingForActivation;\n\n\t\treturn {\n\t\t\tsuccess,\n\t\t\tstatus,\n\t\t\twaitTime,\n\t\t};\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["result"]
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type LogConfig } from "@zwave-js/core";
|
|
2
2
|
import { type ZWaveSerialPortImplementation } from "@zwave-js/serial";
|
|
3
|
-
import {
|
|
3
|
+
import { TypedEventTarget } from "@zwave-js/shared";
|
|
4
4
|
import { type ZWaveOptions } from "../driver/ZWaveOptions.js";
|
|
5
5
|
import { type CorruptedFrame, type Frame } from "./MPDU.js";
|
|
6
6
|
export interface ZnifferEventCallbacks {
|
|
@@ -43,7 +43,7 @@ export interface CapturedFrame {
|
|
|
43
43
|
frameData: Uint8Array;
|
|
44
44
|
parsedFrame: Frame | CorruptedFrame;
|
|
45
45
|
}
|
|
46
|
-
export declare class Zniffer extends
|
|
46
|
+
export declare class Zniffer extends TypedEventTarget<ZnifferEventCallbacks> {
|
|
47
47
|
private port;
|
|
48
48
|
constructor(port: string | ZWaveSerialPortImplementation, options?: ZnifferOptions);
|
|
49
49
|
private _options;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/zniffer/Zniffer.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n\tCommandClass,\n\tSecurity2CCMessageEncapsulation,\n\tSecurity2CCNonceGet,\n\tSecurity2CCNonceReport,\n\tSecurityCCNonceReport,\n} from \"@zwave-js/cc\";\nimport { DeviceConfig } from \"@zwave-js/config\";\nimport {\n\tCommandClasses,\n\ttype FrameType,\n\ttype LogConfig,\n\tMPDUHeaderType,\n\ttype MaybeNotKnown,\n\tNODE_ID_BROADCAST,\n\tNODE_ID_BROADCAST_LR,\n\ttype RSSI,\n\tSPANState,\n\tSecurityClass,\n\tSecurityManager,\n\tSecurityManager2,\n\ttype SecurityManagers,\n\ttype UnknownZWaveChipType,\n\tZWaveError,\n\tZWaveErrorCodes,\n\tZWaveLogContainer,\n\tZnifferRegion,\n\tZnifferRegionLegacy,\n\tgetChipTypeAndVersion,\n\tisLongRangeNodeId,\n\tsecurityClassIsS2,\n} from \"@zwave-js/core\";\nimport { sdkVersionGte } from \"@zwave-js/core\";\nimport { type CCParsingContext, type HostIDs } from \"@zwave-js/host\";\nimport {\n\ttype ZWaveSerialPortImplementation,\n\ttype ZnifferDataMessage,\n\tZnifferFrameType,\n\tZnifferGetFrequenciesRequest,\n\tZnifferGetFrequenciesResponse,\n\tZnifferGetFrequencyInfoRequest,\n\tZnifferGetFrequencyInfoResponse,\n\tZnifferGetVersionRequest,\n\tZnifferGetVersionResponse,\n\tZnifferMessage,\n\tZnifferMessageType,\n\tZnifferSerialPort,\n\tZnifferSerialPortBase,\n\tZnifferSetBaudRateRequest,\n\tZnifferSetBaudRateResponse,\n\tZnifferSetFrequencyRequest,\n\tZnifferSetFrequencyResponse,\n\tZnifferSocket,\n\tZnifferStartRequest,\n\tZnifferStartResponse,\n\tZnifferStopRequest,\n\tZnifferStopResponse,\n\tisZWaveSerialPortImplementation,\n} from \"@zwave-js/serial\";\nimport {\n\tBytes,\n\tTypedEventEmitter,\n\tgetEnumMemberName,\n\tisEnumMember,\n\tnoop,\n\tnum2hex,\n\tpick,\n} from \"@zwave-js/shared\";\nimport {\n\ttype DeferredPromise,\n\tcreateDeferredPromise,\n} from \"alcalzone-shared/deferred-promise\";\nimport fs from \"node:fs/promises\";\nimport { type ZWaveOptions } from \"../driver/ZWaveOptions.js\";\nimport { ZnifferLogger } from \"../log/Zniffer.js\";\nimport {\n\ttype CorruptedFrame,\n\ttype Frame,\n\tbeamToFrame,\n\tmpduToFrame,\n\tparseBeamFrame,\n\tparseMPDU,\n\tznifferDataMessageToCorruptedFrame,\n} from \"./MPDU.js\";\n\nconst logo: string = `\n\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n\u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\n \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u255D \u2588\u2588\u2554\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\n\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n`.trim();\n\nexport interface ZnifferEventCallbacks {\n\tready: () => void;\n\terror: (err: Error) => void;\n\tframe: (frame: Frame, rawData: Uint8Array) => void;\n\t\"corrupted frame\": (err: CorruptedFrame, rawData: Uint8Array) => void;\n}\n\nexport type ZnifferEvents = Extract<keyof ZnifferEventCallbacks, string>;\n\ninterface AwaitedThing<T> {\n\thandler: (thing: T) => void;\n\ttimeout?: NodeJS.Timeout;\n\tpredicate: (msg: T) => boolean;\n}\n\ntype AwaitedMessageEntry = AwaitedThing<ZnifferMessage>;\n\nexport interface ZnifferOptions {\n\t/**\n\t * Optional log configuration\n\t */\n\tlogConfig?: Partial<LogConfig>;\n\n\t/** Security keys for decrypting Z-Wave traffic */\n\tsecurityKeys?: ZWaveOptions[\"securityKeys\"];\n\t/** Security keys for decrypting Z-Wave Long Range traffic */\n\tsecurityKeysLongRange?: ZWaveOptions[\"securityKeysLongRange\"];\n\n\t/**\n\t * The RSSI values reported by the Zniffer are not actual RSSI values.\n\t * They can be converted to dBm, but the conversion is chip dependent and not documented for 700/800 series Zniffers.\n\t *\n\t * Set this option to `true` enable the conversion. Otherwise the raw values from the Zniffer will be used.\n\t */\n\tconvertRSSI?: boolean;\n\n\t/**\n\t * The frequency to initialize the Zniffer with. If not specified, the current setting will be kept.\n\t *\n\t * On 700/800 series Zniffers, this value matches the {@link ZnifferRegion}.\n\t *\n\t * On 400/500 series Zniffers, the value is firmware-specific.\n\t * Supported regions and their names have to be queried using the `getFrequencies` and `getFrequencyInfo(frequency)` commands.\n\t */\n\tdefaultFrequency?: number;\n\n\t/** Limit the number of frames that are kept in memory. */\n\tmaxCapturedFrames?: number;\n}\n\nfunction is700PlusSeries(\n\tchipType: string | UnknownZWaveChipType,\n): boolean {\n\tif (typeof chipType !== \"string\") {\n\t\treturn chipType.type >= 0x07;\n\t}\n\n\tconst chipTypeNumeric = getChipTypeAndVersion(chipType);\n\tif (chipTypeNumeric) {\n\t\treturn chipTypeNumeric.type >= 0x07;\n\t}\n\n\treturn false;\n}\n\nfunction tryConvertRSSI(\n\trssi: number,\n\tchipType: string | UnknownZWaveChipType,\n): number {\n\t// For 400/500 series, the conversion is documented in the Zniffer user guide.\n\t// The conversion for 700/800 series was reverse-engineered from the Zniffer firmware.\n\t// Here, we assume that only these two representations exist:\n\tif (is700PlusSeries(chipType)) {\n\t\treturn rssi * 4 - 256;\n\t} else {\n\t\treturn rssi * 1.5 - 153.5;\n\t}\n}\n\ninterface CapturedData {\n\ttimestamp: Date;\n\trawData: Uint8Array;\n\tframeData: Uint8Array;\n\tparsedFrame?: Frame | CorruptedFrame;\n}\n\nexport interface CapturedFrame {\n\ttimestamp: Date;\n\tframeData: Uint8Array;\n\tparsedFrame: Frame | CorruptedFrame;\n}\n\nexport class Zniffer extends TypedEventEmitter<ZnifferEventCallbacks> {\n\tpublic constructor(\n\t\tprivate port: string | ZWaveSerialPortImplementation,\n\t\toptions: ZnifferOptions = {},\n\t) {\n\t\tsuper();\n\n\t\t// Ensure the given serial port is valid\n\t\tif (\n\t\t\ttypeof port !== \"string\"\n\t\t\t&& !isZWaveSerialPortImplementation(port)\n\t\t) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`The port must be a string or a valid custom serial port implementation!`,\n\t\t\t\tZWaveErrorCodes.Driver_InvalidOptions,\n\t\t\t);\n\t\t}\n\n\t\t// Initialize logging\n\t\tthis._logContainer = new ZWaveLogContainer(options.logConfig);\n\t\tthis.znifferLog = new ZnifferLogger(this, this._logContainer);\n\n\t\tthis._options = options;\n\n\t\tthis._active = false;\n\n\t\tthis.parsingContext = {\n\t\t\tgetHighestSecurityClass(\n\t\t\t\t_nodeId: number,\n\t\t\t): MaybeNotKnown<SecurityClass> {\n\t\t\t\treturn SecurityClass.S2_AccessControl;\n\t\t\t},\n\n\t\t\thasSecurityClass(\n\t\t\t\t_nodeId: number,\n\t\t\t\t_securityClass: SecurityClass,\n\t\t\t): MaybeNotKnown<boolean> {\n\t\t\t\t// We don't actually know. Attempt parsing with all security classes\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tsetSecurityClass(\n\t\t\t\t_nodeId: number,\n\t\t\t\t_securityClass: SecurityClass,\n\t\t\t\t_granted: boolean,\n\t\t\t): void {\n\t\t\t\t// Do nothing\n\t\t\t},\n\n\t\t\tgetDeviceConfig(_nodeId: number): DeviceConfig | undefined {\n\t\t\t\t// Disable strict validation while parsing certain CCs\n\t\t\t\t// Most of this stuff isn't actually needed, only the compat flags...\n\t\t\t\treturn new DeviceConfig(\n\t\t\t\t\t\"unknown.json\",\n\t\t\t\t\tfalse,\n\t\t\t\t\t\"UNKNOWN_MANUFACTURER\",\n\t\t\t\t\t0x0000,\n\t\t\t\t\t\"UNKNOWN_PRODUCT\",\n\t\t\t\t\t\"UNKNOWN_DESCRIPTION\",\n\t\t\t\t\t[],\n\t\t\t\t\t{\n\t\t\t\t\t\tmin: \"0.0\",\n\t\t\t\t\t\tmax: \"255.255\",\n\t\t\t\t\t},\n\t\t\t\t\ttrue,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\t// ...down here:\n\t\t\t\t\t{\n\t\t\t\t\t\tdisableStrictEntryControlDataValidation: true,\n\t\t\t\t\t\tdisableStrictMeasurementValidation: true,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate _options: ZnifferOptions;\n\n\t/** The serial port instance */\n\tprivate serial: ZnifferSerialPortBase | undefined;\n\tprivate parsingContext: Omit<\n\t\tCCParsingContext,\n\t\tkeyof HostIDs | \"sourceNodeId\" | \"frameType\" | keyof SecurityManagers\n\t>;\n\n\tprivate _destroyPromise: DeferredPromise<void> | undefined;\n\tprivate get wasDestroyed(): boolean {\n\t\treturn !!this._destroyPromise;\n\t}\n\n\tprivate _chipType: string | UnknownZWaveChipType | undefined;\n\n\tprivate _currentFrequency: number | undefined;\n\t/** The currently configured frequency */\n\tpublic get currentFrequency(): number | undefined {\n\t\treturn this._currentFrequency;\n\t}\n\n\tprivate _supportedFrequencies: Map<number, string> = new Map();\n\t/** A map of supported frequency identifiers and their names */\n\tpublic get supportedFrequencies(): ReadonlyMap<number, string> {\n\t\treturn this._supportedFrequencies;\n\t}\n\n\tprivate _logContainer: ZWaveLogContainer;\n\tprivate znifferLog: ZnifferLogger;\n\n\t/** The security managers for each node */\n\tprivate securityManagers: Map<number, {\n\t\tsecurityManager: SecurityManager | undefined;\n\t\tsecurityManager2: SecurityManager2 | undefined;\n\t\tsecurityManagerLR: SecurityManager2 | undefined;\n\t}> = new Map();\n\n\t/** A list of awaited messages */\n\tprivate awaitedMessages: AwaitedMessageEntry[] = [];\n\n\tprivate _active: boolean;\n\t/** Whether the Zniffer instance is currently capturing */\n\tpublic get active(): boolean {\n\t\treturn this._active;\n\t}\n\n\tprivate _capturedFrames: CapturedData[] = [];\n\n\t/** A list of raw captured frames that can be saved to a .zlf file later */\n\tpublic get capturedFrames(): Readonly<CapturedFrame>[] {\n\t\treturn this._capturedFrames.filter((f) => f.parsedFrame !== undefined)\n\t\t\t.map((f) => ({\n\t\t\t\ttimestamp: f.timestamp,\n\t\t\t\tframeData: f.frameData,\n\t\t\t\tparsedFrame: f.parsedFrame!,\n\t\t\t}));\n\t}\n\n\tpublic async init(): Promise<void> {\n\t\tif (this.wasDestroyed) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The Zniffer was destroyed. Create a new instance and initialize that one.\",\n\t\t\t\tZWaveErrorCodes.Driver_Destroyed,\n\t\t\t);\n\t\t}\n\n\t\t// Open the serial port\n\t\tif (typeof this.port === \"string\") {\n\t\t\tif (this.port.startsWith(\"tcp://\")) {\n\t\t\t\tconst url = new URL(this.port);\n\t\t\t\t// this.znifferLog.print(`opening serial port ${this.port}`);\n\t\t\t\tthis.serial = new ZnifferSocket(\n\t\t\t\t\t{\n\t\t\t\t\t\thost: url.hostname,\n\t\t\t\t\t\tport: parseInt(url.port),\n\t\t\t\t\t},\n\t\t\t\t\tthis._logContainer,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// this.znifferLog.print(`opening serial port ${this.port}`);\n\t\t\t\tthis.serial = new ZnifferSerialPort(\n\t\t\t\t\tthis.port,\n\t\t\t\t\tthis._logContainer,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"opening serial port using the provided custom implementation\",\n\t\t\t// );\n\t\t\tthis.serial = new ZnifferSerialPortBase(\n\t\t\t\tthis.port,\n\t\t\t\tthis._logContainer,\n\t\t\t);\n\t\t}\n\t\tthis.serial\n\t\t\t.on(\"data\", this.serialport_onData.bind(this))\n\t\t\t.on(\"error\", (err) => {\n\t\t\t\tthis.emit(\"error\", err);\n\t\t\t});\n\n\t\tawait this.serial.open();\n\n\t\tthis.znifferLog.print(logo, \"info\");\n\n\t\tawait this.stop();\n\n\t\tconst versionInfo = await this.getVersion();\n\t\tthis._chipType = versionInfo.chipType;\n\t\tthis.znifferLog.print(\n\t\t\t`received Zniffer info:\n Chip type: ${\n\t\t\t\ttypeof versionInfo.chipType === \"string\"\n\t\t\t\t\t? versionInfo.chipType\n\t\t\t\t\t: `unknown (${num2hex(versionInfo.chipType.type)}, ${\n\t\t\t\t\t\tnum2hex(versionInfo.chipType.version)\n\t\t\t\t\t})`\n\t\t\t}\n Zniffer version: ${versionInfo.majorVersion}.${versionInfo.minorVersion}`,\n\t\t\t\"info\",\n\t\t);\n\n\t\tawait this.setBaudrate(0);\n\n\t\tconst freqs = await this.getFrequencies();\n\t\tthis._currentFrequency = freqs.currentFrequency;\n\t\tif (is700PlusSeries(this._chipType)) {\n\t\t\t// The frequencies match the ZnifferRegion enum\n\t\t\tfor (const freq of freqs.supportedFrequencies) {\n\t\t\t\tthis._supportedFrequencies.set(\n\t\t\t\t\tfreq,\n\t\t\t\t\tgetEnumMemberName(ZnifferRegion, freq),\n\t\t\t\t);\n\t\t\t}\n\t\t\t// ... but there might be unknown regions. Query those from the Zniffer\n\t\t\tconst unknownRegions = freqs.supportedFrequencies.filter((f) =>\n\t\t\t\t!isEnumMember(ZnifferRegion, f)\n\t\t\t);\n\t\t\tfor (const freq of unknownRegions) {\n\t\t\t\tconst freqInfo = await this.getFrequencyInfo(freq);\n\t\t\t\tthis._supportedFrequencies.set(freq, freqInfo.frequencyName);\n\t\t\t}\n\t\t} else if (\n\t\t\t// Version 2.55+ supports querying the frequency names\n\t\t\tsdkVersionGte(\n\t\t\t\t`${versionInfo.majorVersion}.${versionInfo.minorVersion}`,\n\t\t\t\t\"2.55\",\n\t\t\t)\n\t\t) {\n\t\t\t// The frequencies are firmware-specific. Query them from the Zniffer\n\t\t\tfor (const freq of freqs.supportedFrequencies) {\n\t\t\t\tconst freqInfo = await this.getFrequencyInfo(freq);\n\t\t\t\tthis._supportedFrequencies.set(freq, freqInfo.frequencyName);\n\t\t\t}\n\t\t} else {\n\t\t\t// The frequencies match the ZnifferRegionLegacy enum, and their info cannot be queried\n\t\t\tfor (const freq of freqs.supportedFrequencies) {\n\t\t\t\tthis._supportedFrequencies.set(\n\t\t\t\t\tfreq,\n\t\t\t\t\tgetEnumMemberName(ZnifferRegionLegacy, freq),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.znifferLog.print(\n\t\t\t`received frequency info:\ncurrent frequency: ${\n\t\t\t\tthis._supportedFrequencies.get(freqs.currentFrequency)\n\t\t\t\t\t?? `unknown (${num2hex(freqs.currentFrequency)})`\n\t\t\t}\nsupported frequencies: ${\n\t\t\t\t[...this._supportedFrequencies].map(([region, name]) =>\n\t\t\t\t\t`\\n \u00B7 ${region.toString().padStart(2, \" \")}: ${name}`\n\t\t\t\t).join(\"\")\n\t\t\t}`,\n\t\t\t\"info\",\n\t\t);\n\n\t\tif (\n\t\t\ttypeof this._options.defaultFrequency === \"number\"\n\t\t\t&& freqs.currentFrequency !== this._options.defaultFrequency\n\t\t\t&& this._supportedFrequencies.has(this._options.defaultFrequency)\n\t\t) {\n\t\t\tawait this.setFrequency(this._options.defaultFrequency);\n\t\t}\n\n\t\tthis.emit(\"ready\");\n\t}\n\n\t/**\n\t * Is called when the serial port has received a Zniffer frame\n\t */\n\tprivate async serialport_onData(\n\t\tdata: Uint8Array,\n\t): Promise<void> {\n\t\tlet msg: ZnifferMessage | undefined;\n\t\ttry {\n\t\t\tmsg = ZnifferMessage.parse(data);\n\t\t} catch (e: any) {\n\t\t\tconsole.error(e);\n\t\t\treturn;\n\t\t}\n\n\t\tif (msg.type === ZnifferMessageType.Command) {\n\t\t\tthis.handleResponse(msg);\n\t\t} else {\n\t\t\tconst dataMsg = msg as ZnifferDataMessage;\n\t\t\tconst capture: CapturedData = {\n\t\t\t\ttimestamp: new Date(),\n\t\t\t\trawData: data,\n\t\t\t\tframeData: dataMsg.payload,\n\t\t\t};\n\t\t\tthis._capturedFrames.push(capture);\n\t\t\tif (\n\t\t\t\tthis._options.maxCapturedFrames != undefined\n\t\t\t\t&& this._capturedFrames.length > this._options.maxCapturedFrames\n\t\t\t) {\n\t\t\t\tthis._capturedFrames.shift();\n\t\t\t}\n\t\t\tawait this.handleDataMessage(dataMsg, capture);\n\t\t}\n\t}\n\n\t/**\n\t * Is called when a Request-type message was received\n\t */\n\tprivate handleResponse(msg: ZnifferMessage): void {\n\t\t// Check if we have a dynamic handler waiting for this message\n\t\tfor (const entry of this.awaitedMessages) {\n\t\t\tif (entry.predicate(msg)) {\n\t\t\t\t// We do\n\t\t\t\tentry.handler(msg);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Is called when a Request-type message was received\n\t */\n\tprivate async handleDataMessage(\n\t\tmsg: ZnifferDataMessage,\n\t\tcapture: CapturedData,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tlet convertedRSSI: RSSI | undefined;\n\t\t\tif (this._options.convertRSSI && this._chipType) {\n\t\t\t\tconvertedRSSI = tryConvertRSSI(\n\t\t\t\t\tmsg.rssiRaw,\n\t\t\t\t\tthis._chipType,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Short-circuit if we're dealing with beam frames\n\t\t\tif (\n\t\t\t\tmsg.frameType === ZnifferFrameType.BeamStart\n\t\t\t\t|| msg.frameType === ZnifferFrameType.BeamStop\n\t\t\t) {\n\t\t\t\tconst beam = parseBeamFrame(msg);\n\t\t\t\tbeam.frameInfo.rssi = convertedRSSI;\n\n\t\t\t\t// Emit the captured frame in a format that's easier to work with for applications.\n\t\t\t\tthis.znifferLog.beam(beam);\n\t\t\t\tconst frame = beamToFrame(beam);\n\t\t\t\tcapture.parsedFrame = frame;\n\t\t\t\tthis.emit(\"frame\", frame, capture.frameData);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only handle messages with a valid checksum, expose the others as CRC errors\n\t\t\tif (!msg.checksumOK) {\n\t\t\t\tthis.znifferLog.crcError(msg);\n\t\t\t\tconst frame = znifferDataMessageToCorruptedFrame(msg);\n\t\t\t\tcapture.parsedFrame = frame;\n\t\t\t\tthis.emit(\"corrupted frame\", frame, capture.frameData);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst mpdu = parseMPDU(msg);\n\t\t\tmpdu.frameInfo.rssi = convertedRSSI;\n\n\t\t\t// Try to decode the CC while assuming the role of the receiver\n\t\t\tlet destSecurityManager: SecurityManager | undefined;\n\t\t\tlet destSecurityManager2: SecurityManager2 | undefined;\n\t\t\tlet destSecurityManagerLR: SecurityManager2 | undefined;\n\t\t\t// Only frames with a destination node id contains something that requires access to the own node ID\n\t\t\tlet destNodeId = 0xff;\n\n\t\t\tlet cc: CommandClass | undefined;\n\n\t\t\t// FIXME: Cache data => parsed CC, so we can understand re-transmitted S2 frames\n\n\t\t\tif (\n\t\t\t\tmpdu.payload.length > 0\n\t\t\t\t&& mpdu.headerType !== MPDUHeaderType.Acknowledgement\n\t\t\t) {\n\t\t\t\tif (\"destinationNodeId\" in mpdu) {\n\t\t\t\t\tdestNodeId = mpdu.destinationNodeId;\n\t\t\t\t\t({\n\t\t\t\t\t\tsecurityManager: destSecurityManager,\n\t\t\t\t\t\tsecurityManager2: destSecurityManager2,\n\t\t\t\t\t\tsecurityManagerLR: destSecurityManagerLR,\n\t\t\t\t\t} = await this.getSecurityManagers(mpdu.destinationNodeId));\n\t\t\t\t}\n\n\t\t\t\t// TODO: Support parsing multicast S2 frames\n\t\t\t\tconst frameType: FrameType =\n\t\t\t\t\tmpdu.headerType === MPDUHeaderType.Multicast\n\t\t\t\t\t\t? \"multicast\"\n\t\t\t\t\t\t: (destNodeId === NODE_ID_BROADCAST\n\t\t\t\t\t\t\t\t|| destNodeId === NODE_ID_BROADCAST_LR)\n\t\t\t\t\t\t? \"broadcast\"\n\t\t\t\t\t\t: \"singlecast\";\n\t\t\t\ttry {\n\t\t\t\t\tcc = await CommandClass.parseAsync(\n\t\t\t\t\t\tmpdu.payload,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thomeId: mpdu.homeId,\n\t\t\t\t\t\t\townNodeId: destNodeId,\n\t\t\t\t\t\t\tsourceNodeId: mpdu.sourceNodeId,\n\t\t\t\t\t\t\tframeType,\n\t\t\t\t\t\t\tsecurityManager: destSecurityManager,\n\t\t\t\t\t\t\tsecurityManager2: destSecurityManager2,\n\t\t\t\t\t\t\tsecurityManagerLR: destSecurityManagerLR,\n\t\t\t\t\t\t\t...this.parsingContext,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\t// Ignore\n\t\t\t\t\tconsole.error(e.stack);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.znifferLog.mpdu(mpdu, cc);\n\n\t\t\t// Emit the captured frame in a format that's easier to work with for applications.\n\t\t\tconst frame = mpduToFrame(mpdu, cc);\n\t\t\tcapture.parsedFrame = frame;\n\t\t\tthis.emit(\"frame\", frame, capture.frameData);\n\n\t\t\t// Update the security managers when nonces are exchanged, so we can\n\t\t\t// decrypt the communication\n\t\t\tif (cc?.ccId === CommandClasses[\"Security 2\"]) {\n\t\t\t\tconst securityManagers = await this.getSecurityManagers(\n\t\t\t\t\tmpdu.sourceNodeId,\n\t\t\t\t);\n\t\t\t\tconst isLR = isLongRangeNodeId(mpdu.sourceNodeId)\n\t\t\t\t\t|| isLongRangeNodeId(destNodeId);\n\t\t\t\tconst senderSecurityManager = isLR\n\t\t\t\t\t? securityManagers.securityManagerLR\n\t\t\t\t\t: securityManagers.securityManager2;\n\t\t\t\tconst destSecurityManager = isLR\n\t\t\t\t\t? destSecurityManagerLR\n\t\t\t\t\t: destSecurityManager2;\n\n\t\t\t\tif (senderSecurityManager && destSecurityManager) {\n\t\t\t\t\tif (cc instanceof Security2CCNonceGet) {\n\t\t\t\t\t\t// Nonce Get -> all nonces are now invalid\n\t\t\t\t\t\tsenderSecurityManager.deleteNonce(destNodeId);\n\t\t\t\t\t\tdestSecurityManager.deleteNonce(mpdu.sourceNodeId);\n\t\t\t\t\t} else if (cc instanceof Security2CCNonceReport && cc.SOS) {\n\t\t\t\t\t\t// Nonce Report (SOS) -> We only know the receiver's nonce\n\t\t\t\t\t\tsenderSecurityManager.setSPANState(destNodeId, {\n\t\t\t\t\t\t\ttype: SPANState.LocalEI,\n\t\t\t\t\t\t\treceiverEI: cc.receiverEI!,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tdestSecurityManager.storeRemoteEI(\n\t\t\t\t\t\t\tmpdu.sourceNodeId,\n\t\t\t\t\t\t\tcc.receiverEI!,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (cc instanceof Security2CCMessageEncapsulation) {\n\t\t\t\t\t\tconst senderEI = cc.getSenderEI();\n\t\t\t\t\t\tif (senderEI) {\n\t\t\t\t\t\t\t// The receiver should now have a valid SPAN state, since decoding the S2 CC updates it.\n\t\t\t\t\t\t\t// The security manager for the sender however, does not. Therefore, update it manually,\n\t\t\t\t\t\t\t// if the receiver SPAN is indeed valid.\n\n\t\t\t\t\t\t\tconst receiverSPANState = destSecurityManager\n\t\t\t\t\t\t\t\t.getSPANState(mpdu.sourceNodeId);\n\t\t\t\t\t\t\tif (receiverSPANState.type === SPANState.SPAN) {\n\t\t\t\t\t\t\t\tsenderSecurityManager.setSPANState(\n\t\t\t\t\t\t\t\t\tdestNodeId,\n\t\t\t\t\t\t\t\t\treceiverSPANState,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (\n\t\t\t\tcc?.ccId === CommandClasses.Security\n\t\t\t\t&& cc instanceof SecurityCCNonceReport\n\t\t\t) {\n\t\t\t\tconst senderSecurityManager =\n\t\t\t\t\t(await this.getSecurityManagers(mpdu.sourceNodeId))\n\t\t\t\t\t\t.securityManager;\n\t\t\t\tconst destSecurityManager =\n\t\t\t\t\t(await this.getSecurityManagers(destNodeId))\n\t\t\t\t\t\t.securityManager;\n\n\t\t\t\tif (senderSecurityManager && destSecurityManager) {\n\t\t\t\t\t// Both nodes have a shared nonce now\n\t\t\t\t\tsenderSecurityManager.setNonce(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tissuer: mpdu.sourceNodeId,\n\t\t\t\t\t\t\tnonceId: senderSecurityManager.getNonceId(\n\t\t\t\t\t\t\t\tcc.nonce,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnonce: cc.nonce,\n\t\t\t\t\t\t\treceiver: destNodeId,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ free: true },\n\t\t\t\t\t);\n\n\t\t\t\t\tdestSecurityManager.setNonce(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tissuer: mpdu.sourceNodeId,\n\t\t\t\t\t\t\tnonceId: senderSecurityManager.getNonceId(\n\t\t\t\t\t\t\t\tcc.nonce,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnonce: cc.nonce,\n\t\t\t\t\t\t\treceiver: destNodeId,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ free: true },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e: any) {\n\t\t\tconsole.error(e);\n\t\t}\n\t}\n\n\t/**\n\t * Waits until a certain serial message is received or a timeout has elapsed. Returns the received message.\n\t * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected\n\t * @param predicate A predicate function to test all incoming messages.\n\t */\n\tprivate waitForMessage<T extends ZnifferMessage>(\n\t\tpredicate: (msg: ZnifferMessage) => boolean,\n\t\ttimeout: number,\n\t): Promise<T> {\n\t\treturn new Promise<T>((resolve, reject) => {\n\t\t\tconst promise = createDeferredPromise<ZnifferMessage>();\n\t\t\tconst entry: AwaitedMessageEntry = {\n\t\t\t\tpredicate,\n\t\t\t\thandler: (msg) => promise.resolve(msg),\n\t\t\t\ttimeout: undefined,\n\t\t\t};\n\t\t\tthis.awaitedMessages.push(entry);\n\t\t\tconst removeEntry = () => {\n\t\t\t\tif (entry.timeout) clearTimeout(entry.timeout);\n\t\t\t\tconst index = this.awaitedMessages.indexOf(entry);\n\t\t\t\tif (index !== -1) this.awaitedMessages.splice(index, 1);\n\t\t\t};\n\t\t\t// When the timeout elapses, remove the wait entry and reject the returned Promise\n\t\t\tentry.timeout = setTimeout(() => {\n\t\t\t\tremoveEntry();\n\t\t\t\treject(\n\t\t\t\t\tnew ZWaveError(\n\t\t\t\t\t\t`Received no matching message within the provided timeout!`,\n\t\t\t\t\t\tZWaveErrorCodes.Controller_Timeout,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}, timeout);\n\t\t\t// When the promise is resolved, remove the wait entry and resolve the returned Promise\n\t\t\tvoid promise.then((cc) => {\n\t\t\t\tremoveEntry();\n\t\t\t\tresolve(cc as T);\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async getVersion() {\n\t\tconst req = new ZnifferGetVersionRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tconst res = await this.waitForMessage<ZnifferGetVersionResponse>(\n\t\t\t(msg) => msg instanceof ZnifferGetVersionResponse,\n\t\t\t1000,\n\t\t);\n\n\t\treturn pick(res, [\"chipType\", \"majorVersion\", \"minorVersion\"]);\n\t}\n\n\tprivate async getFrequencies() {\n\t\tconst req = new ZnifferGetFrequenciesRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tconst res = await this.waitForMessage<ZnifferGetFrequenciesResponse>(\n\t\t\t(msg) => msg instanceof ZnifferGetFrequenciesResponse,\n\t\t\t1000,\n\t\t);\n\n\t\treturn pick(res, [\n\t\t\t\"currentFrequency\",\n\t\t\t\"supportedFrequencies\",\n\t\t]);\n\t}\n\n\tpublic async setFrequency(frequency: number): Promise<void> {\n\t\tconst req = new ZnifferSetFrequencyRequest({ frequency });\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferSetFrequencyResponse>(\n\t\t\t(msg) => msg instanceof ZnifferSetFrequencyResponse,\n\t\t\t1000,\n\t\t);\n\t\tthis._currentFrequency = frequency;\n\t}\n\n\tprivate async getFrequencyInfo(frequency: number) {\n\t\tconst req = new ZnifferGetFrequencyInfoRequest({ frequency });\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tconst res = await this.waitForMessage<ZnifferGetFrequencyInfoResponse>(\n\t\t\t(msg) =>\n\t\t\t\tmsg instanceof ZnifferGetFrequencyInfoResponse\n\t\t\t\t&& msg.frequency === frequency,\n\t\t\t1000,\n\t\t);\n\n\t\treturn pick(res, [\"numChannels\", \"frequencyName\"]);\n\t}\n\n\t/** Starts the capture and discards all previously captured frames */\n\tpublic async start(): Promise<void> {\n\t\tif (this.wasDestroyed) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The Zniffer is not ready or has been destroyed\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\tif (this._active) return;\n\t\tthis._capturedFrames = [];\n\t\tthis._active = true;\n\n\t\tconst req = new ZnifferStartRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferStartResponse>(\n\t\t\t(msg) => msg instanceof ZnifferStartResponse,\n\t\t\t1000,\n\t\t);\n\t}\n\n\tpublic async stop(): Promise<void> {\n\t\tif (!this._active) return;\n\t\tthis._active = false;\n\n\t\tif (!this.serial) return;\n\n\t\tconst req = new ZnifferStopRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferStopResponse>(\n\t\t\t(msg) => msg instanceof ZnifferStopResponse,\n\t\t\t1000,\n\t\t);\n\t}\n\n\tprivate async setBaudrate(baudrate: 0): Promise<void> {\n\t\tconst req = new ZnifferSetBaudRateRequest({ baudrate });\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferSetBaudRateResponse>(\n\t\t\t(msg) => msg instanceof ZnifferSetBaudRateResponse,\n\t\t\t1000,\n\t\t);\n\t}\n\n\tprivate async getSecurityManagers(\n\t\tsourceNodeId: number,\n\t) {\n\t\tif (this.securityManagers.has(sourceNodeId)) {\n\t\t\treturn this.securityManagers.get(sourceNodeId)!;\n\t\t}\n\t\t// Initialize security\n\t\t// Set up the S0 security manager. We can only do that after the controller\n\t\t// interview because we need to know the controller node id.\n\t\tconst S0Key = this._options.securityKeys?.S0_Legacy;\n\t\tlet securityManager: SecurityManager | undefined;\n\t\tif (S0Key) {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"Network key for S0 configured, enabling S0 security manager...\",\n\t\t\t// );\n\t\t\tsecurityManager = new SecurityManager({\n\t\t\t\tnetworkKey: S0Key,\n\t\t\t\t// FIXME: Track nonces separately for each destination node\n\t\t\t\townNodeId: sourceNodeId,\n\t\t\t\tnonceTimeout: Number.POSITIVE_INFINITY,\n\t\t\t});\n\t\t\t// } else {\n\t\t\t// \tthis.znifferLog.print(\n\t\t\t// \t\t\"No network key for S0 configured, cannot decrypt communication from secure (S0) devices!\",\n\t\t\t// \t\t\"warn\",\n\t\t\t// \t);\n\t\t}\n\n\t\tlet securityManager2: SecurityManager2 | undefined;\n\t\tif (\n\t\t\tthis._options.securityKeys\n\t\t\t// Only set it up if we have security keys for at least one S2 security class\n\t\t\t&& Object.keys(this._options.securityKeys).some(\n\t\t\t\t(key) =>\n\t\t\t\t\tkey.startsWith(\"S2_\")\n\t\t\t\t\t&& key in SecurityClass\n\t\t\t\t\t&& securityClassIsS2((SecurityClass as any)[key]),\n\t\t\t)\n\t\t) {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"At least one network key for S2 configured, enabling S2 security manager...\",\n\t\t\t// );\n\t\t\tsecurityManager2 = await SecurityManager2.create();\n\t\t\t// Small hack: Zniffer does not care about S2 duplicates\n\t\t\tsecurityManager2.isDuplicateSinglecast = () => false;\n\n\t\t\t// Set up all keys\n\t\t\tfor (\n\t\t\t\tconst secClass of [\n\t\t\t\t\t\"S2_Unauthenticated\",\n\t\t\t\t\t\"S2_Authenticated\",\n\t\t\t\t\t\"S2_AccessControl\",\n\t\t\t\t\t\"S0_Legacy\",\n\t\t\t\t] as const\n\t\t\t) {\n\t\t\t\tconst key = this._options.securityKeys[secClass];\n\t\t\t\tif (key) {\n\t\t\t\t\tawait securityManager2.setKeyAsync(\n\t\t\t\t\t\tSecurityClass[secClass],\n\t\t\t\t\t\tkey,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// } else {\n\t\t\t// \tthis.znifferLog.print(\n\t\t\t// \t\t\"No network key for S2 configured, cannot decrypt communication from secure (S2) devices!\",\n\t\t\t// \t\t\"warn\",\n\t\t\t// \t);\n\t\t}\n\n\t\tlet securityManagerLR: SecurityManager2 | undefined;\n\t\tif (\n\t\t\tthis._options.securityKeysLongRange?.S2_AccessControl\n\t\t\t|| this._options.securityKeysLongRange?.S2_Authenticated\n\t\t) {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"At least one network key for Z-Wave Long Range configured, enabling security manager...\",\n\t\t\t// );\n\t\t\tsecurityManagerLR = await SecurityManager2.create();\n\t\t\t// Small hack: Zniffer does not care about S2 duplicates\n\t\t\tsecurityManagerLR.isDuplicateSinglecast = () => false;\n\n\t\t\t// Set up all keys\n\t\t\tif (this._options.securityKeysLongRange?.S2_AccessControl) {\n\t\t\t\tawait securityManagerLR.setKeyAsync(\n\t\t\t\t\tSecurityClass.S2_AccessControl,\n\t\t\t\t\tthis._options.securityKeysLongRange.S2_AccessControl,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (this._options.securityKeysLongRange?.S2_Authenticated) {\n\t\t\t\tawait securityManagerLR.setKeyAsync(\n\t\t\t\t\tSecurityClass.S2_Authenticated,\n\t\t\t\t\tthis._options.securityKeysLongRange.S2_Authenticated,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// } else {\n\t\t\t// \tthis.znifferLog.print(\n\t\t\t// \t\t\"No network key for Z-Wave Long Range configured, cannot decrypt Long Range communication!\",\n\t\t\t// \t\t\"warn\",\n\t\t\t// \t);\n\t\t}\n\n\t\tconst ret = {\n\t\t\tsecurityManager,\n\t\t\tsecurityManager2,\n\t\t\tsecurityManagerLR,\n\t\t};\n\t\tthis.securityManagers.set(sourceNodeId, ret);\n\t\treturn ret;\n\t}\n\n\t/** Clears the list of captured frames */\n\tpublic clearCapturedFrames(): void {\n\t\tthis._capturedFrames = [];\n\t}\n\n\t/**\n\t * Get the captured frames in the official Zniffer application format.\n\t * @param frameFilter Optional predicate function to filter the frames included in the capture\n\t */\n\tpublic getCaptureAsZLFBuffer(\n\t\tframeFilter?: (frame: CapturedFrame) => boolean,\n\t): Uint8Array {\n\t\t// Mimics the current Zniffer software, without using features like sessions and comments\n\t\tconst header = new Bytes(2048).fill(0);\n\t\theader[0] = 0x68; // zniffer version\n\t\theader.writeUInt16BE(0x2312, 0x07fe); // checksum\n\t\tlet filteredFrames = this._capturedFrames;\n\t\tif (frameFilter) {\n\t\t\tfilteredFrames = filteredFrames.filter((f) =>\n\t\t\t\t// Always include Zniffer-protocol frames\n\t\t\t\tf.parsedFrame == undefined\n\t\t\t\t// Apply the filter to all other frames\n\t\t\t\t|| frameFilter({\n\t\t\t\t\tframeData: f.frameData,\n\t\t\t\t\tparsedFrame: f.parsedFrame,\n\t\t\t\t\ttimestamp: f.timestamp,\n\t\t\t\t})\n\t\t\t);\n\t\t}\n\t\treturn Bytes.concat([\n\t\t\theader,\n\t\t\t...filteredFrames.map(captureToZLFEntry),\n\t\t]);\n\t}\n\n\t/**\n\t * Saves the captured frames in a `.zlf` file that can be read by the official Zniffer application.\n\t * @param frameFilter Optional predicate function to filter the frames included in the capture\n\t */\n\tpublic async saveCaptureToFile(\n\t\tfilePath: string,\n\t\tframeFilter?: (frame: CapturedFrame) => boolean,\n\t): Promise<void> {\n\t\tawait fs.writeFile(filePath, this.getCaptureAsZLFBuffer(frameFilter));\n\t}\n\n\t/**\n\t * Terminates the Zniffer instance and closes the underlying serial connection.\n\t * Must be called under any circumstances.\n\t */\n\tpublic async destroy(): Promise<void> {\n\t\t// Ensure this is only called once and all subsequent calls block\n\t\tif (this._destroyPromise) return this._destroyPromise;\n\t\tthis._destroyPromise = createDeferredPromise();\n\n\t\tthis.znifferLog.print(\"Destroying Zniffer instance...\");\n\n\t\tif (this._active) {\n\t\t\tawait this.stop().catch(noop);\n\t\t}\n\n\t\tif (this.serial != undefined) {\n\t\t\t// Avoid spewing errors if the port was in the middle of receiving something\n\t\t\tthis.serial.removeAllListeners();\n\t\t\tif (this.serial.isOpen) await this.serial.close();\n\t\t\tthis.serial = undefined;\n\t\t}\n\n\t\tthis.znifferLog.print(\"Zniffer instance destroyed\");\n\n\t\t// destroy loggers as the very last thing\n\t\tthis._logContainer.destroy();\n\n\t\tthis._destroyPromise.resolve();\n\t}\n}\n\nfunction captureToZLFEntry(\n\tcapture: CapturedData,\n): Uint8Array {\n\tconst buffer = new Bytes(14 + capture.rawData.length).fill(0);\n\t// Convert the date to a .NET datetime\n\tlet ticks = BigInt(capture.timestamp.getTime()) * 10000n\n\t\t+ 621355968000000000n;\n\tticks = ticks | 4000000000000000n; // marks the time as .NET DateTimeKind.Local\n\n\tbuffer.writeBigUInt64LE(ticks, 0);\n\tconst direction = 0b0000_0000; // inbound, outbound would be 0b1000_0000\n\n\tbuffer[8] = direction | 0x01; // dir + session ID\n\tbuffer[9] = capture.rawData.length;\n\t// bytes 10-12 are empty\n\tbuffer.set(capture.rawData, 13);\n\tbuffer[buffer.length - 1] = 0xfe; // end of frame\n\treturn buffer;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import {\n\tCommandClass,\n\tSecurity2CCMessageEncapsulation,\n\tSecurity2CCNonceGet,\n\tSecurity2CCNonceReport,\n\tSecurityCCNonceReport,\n} from \"@zwave-js/cc\";\nimport { DeviceConfig } from \"@zwave-js/config\";\nimport {\n\tCommandClasses,\n\ttype FrameType,\n\ttype LogConfig,\n\tMPDUHeaderType,\n\ttype MaybeNotKnown,\n\tNODE_ID_BROADCAST,\n\tNODE_ID_BROADCAST_LR,\n\ttype RSSI,\n\tSPANState,\n\tSecurityClass,\n\tSecurityManager,\n\tSecurityManager2,\n\ttype SecurityManagers,\n\ttype UnknownZWaveChipType,\n\tZWaveError,\n\tZWaveErrorCodes,\n\tZWaveLogContainer,\n\tZnifferRegion,\n\tZnifferRegionLegacy,\n\tgetChipTypeAndVersion,\n\tisLongRangeNodeId,\n\tsecurityClassIsS2,\n} from \"@zwave-js/core\";\nimport { sdkVersionGte } from \"@zwave-js/core\";\nimport { type CCParsingContext, type HostIDs } from \"@zwave-js/host\";\nimport {\n\ttype ZWaveSerialPortImplementation,\n\ttype ZnifferDataMessage,\n\tZnifferFrameType,\n\tZnifferGetFrequenciesRequest,\n\tZnifferGetFrequenciesResponse,\n\tZnifferGetFrequencyInfoRequest,\n\tZnifferGetFrequencyInfoResponse,\n\tZnifferGetVersionRequest,\n\tZnifferGetVersionResponse,\n\tZnifferMessage,\n\tZnifferMessageType,\n\tZnifferSerialPort,\n\tZnifferSerialPortBase,\n\tZnifferSetBaudRateRequest,\n\tZnifferSetBaudRateResponse,\n\tZnifferSetFrequencyRequest,\n\tZnifferSetFrequencyResponse,\n\tZnifferSocket,\n\tZnifferStartRequest,\n\tZnifferStartResponse,\n\tZnifferStopRequest,\n\tZnifferStopResponse,\n\tisZWaveSerialPortImplementation,\n} from \"@zwave-js/serial\";\nimport {\n\tBytes,\n\tTypedEventTarget,\n\tgetEnumMemberName,\n\tisEnumMember,\n\tnoop,\n\tnum2hex,\n\tpick,\n} from \"@zwave-js/shared\";\nimport {\n\ttype DeferredPromise,\n\tcreateDeferredPromise,\n} from \"alcalzone-shared/deferred-promise\";\nimport fs from \"node:fs/promises\";\nimport { type ZWaveOptions } from \"../driver/ZWaveOptions.js\";\nimport { ZnifferLogger } from \"../log/Zniffer.js\";\nimport {\n\ttype CorruptedFrame,\n\ttype Frame,\n\tbeamToFrame,\n\tmpduToFrame,\n\tparseBeamFrame,\n\tparseMPDU,\n\tznifferDataMessageToCorruptedFrame,\n} from \"./MPDU.js\";\n\nconst logo: string = `\n\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n\u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\n \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u255D \u2588\u2588\u2554\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\n\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n`.trim();\n\nexport interface ZnifferEventCallbacks {\n\tready: () => void;\n\terror: (err: Error) => void;\n\tframe: (frame: Frame, rawData: Uint8Array) => void;\n\t\"corrupted frame\": (err: CorruptedFrame, rawData: Uint8Array) => void;\n}\n\nexport type ZnifferEvents = Extract<keyof ZnifferEventCallbacks, string>;\n\ninterface AwaitedThing<T> {\n\thandler: (thing: T) => void;\n\ttimeout?: NodeJS.Timeout;\n\tpredicate: (msg: T) => boolean;\n}\n\ntype AwaitedMessageEntry = AwaitedThing<ZnifferMessage>;\n\nexport interface ZnifferOptions {\n\t/**\n\t * Optional log configuration\n\t */\n\tlogConfig?: Partial<LogConfig>;\n\n\t/** Security keys for decrypting Z-Wave traffic */\n\tsecurityKeys?: ZWaveOptions[\"securityKeys\"];\n\t/** Security keys for decrypting Z-Wave Long Range traffic */\n\tsecurityKeysLongRange?: ZWaveOptions[\"securityKeysLongRange\"];\n\n\t/**\n\t * The RSSI values reported by the Zniffer are not actual RSSI values.\n\t * They can be converted to dBm, but the conversion is chip dependent and not documented for 700/800 series Zniffers.\n\t *\n\t * Set this option to `true` enable the conversion. Otherwise the raw values from the Zniffer will be used.\n\t */\n\tconvertRSSI?: boolean;\n\n\t/**\n\t * The frequency to initialize the Zniffer with. If not specified, the current setting will be kept.\n\t *\n\t * On 700/800 series Zniffers, this value matches the {@link ZnifferRegion}.\n\t *\n\t * On 400/500 series Zniffers, the value is firmware-specific.\n\t * Supported regions and their names have to be queried using the `getFrequencies` and `getFrequencyInfo(frequency)` commands.\n\t */\n\tdefaultFrequency?: number;\n\n\t/** Limit the number of frames that are kept in memory. */\n\tmaxCapturedFrames?: number;\n}\n\nfunction is700PlusSeries(\n\tchipType: string | UnknownZWaveChipType,\n): boolean {\n\tif (typeof chipType !== \"string\") {\n\t\treturn chipType.type >= 0x07;\n\t}\n\n\tconst chipTypeNumeric = getChipTypeAndVersion(chipType);\n\tif (chipTypeNumeric) {\n\t\treturn chipTypeNumeric.type >= 0x07;\n\t}\n\n\treturn false;\n}\n\nfunction tryConvertRSSI(\n\trssi: number,\n\tchipType: string | UnknownZWaveChipType,\n): number {\n\t// For 400/500 series, the conversion is documented in the Zniffer user guide.\n\t// The conversion for 700/800 series was reverse-engineered from the Zniffer firmware.\n\t// Here, we assume that only these two representations exist:\n\tif (is700PlusSeries(chipType)) {\n\t\treturn rssi * 4 - 256;\n\t} else {\n\t\treturn rssi * 1.5 - 153.5;\n\t}\n}\n\ninterface CapturedData {\n\ttimestamp: Date;\n\trawData: Uint8Array;\n\tframeData: Uint8Array;\n\tparsedFrame?: Frame | CorruptedFrame;\n}\n\nexport interface CapturedFrame {\n\ttimestamp: Date;\n\tframeData: Uint8Array;\n\tparsedFrame: Frame | CorruptedFrame;\n}\n\nexport class Zniffer extends TypedEventTarget<ZnifferEventCallbacks> {\n\tpublic constructor(\n\t\tprivate port: string | ZWaveSerialPortImplementation,\n\t\toptions: ZnifferOptions = {},\n\t) {\n\t\tsuper();\n\n\t\t// Ensure the given serial port is valid\n\t\tif (\n\t\t\ttypeof port !== \"string\"\n\t\t\t&& !isZWaveSerialPortImplementation(port)\n\t\t) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t`The port must be a string or a valid custom serial port implementation!`,\n\t\t\t\tZWaveErrorCodes.Driver_InvalidOptions,\n\t\t\t);\n\t\t}\n\n\t\t// Initialize logging\n\t\tthis._logContainer = new ZWaveLogContainer(options.logConfig);\n\t\tthis.znifferLog = new ZnifferLogger(this, this._logContainer);\n\n\t\tthis._options = options;\n\n\t\tthis._active = false;\n\n\t\tthis.parsingContext = {\n\t\t\tgetHighestSecurityClass(\n\t\t\t\t_nodeId: number,\n\t\t\t): MaybeNotKnown<SecurityClass> {\n\t\t\t\treturn SecurityClass.S2_AccessControl;\n\t\t\t},\n\n\t\t\thasSecurityClass(\n\t\t\t\t_nodeId: number,\n\t\t\t\t_securityClass: SecurityClass,\n\t\t\t): MaybeNotKnown<boolean> {\n\t\t\t\t// We don't actually know. Attempt parsing with all security classes\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tsetSecurityClass(\n\t\t\t\t_nodeId: number,\n\t\t\t\t_securityClass: SecurityClass,\n\t\t\t\t_granted: boolean,\n\t\t\t): void {\n\t\t\t\t// Do nothing\n\t\t\t},\n\n\t\t\tgetDeviceConfig(_nodeId: number): DeviceConfig | undefined {\n\t\t\t\t// Disable strict validation while parsing certain CCs\n\t\t\t\t// Most of this stuff isn't actually needed, only the compat flags...\n\t\t\t\treturn new DeviceConfig(\n\t\t\t\t\t\"unknown.json\",\n\t\t\t\t\tfalse,\n\t\t\t\t\t\"UNKNOWN_MANUFACTURER\",\n\t\t\t\t\t0x0000,\n\t\t\t\t\t\"UNKNOWN_PRODUCT\",\n\t\t\t\t\t\"UNKNOWN_DESCRIPTION\",\n\t\t\t\t\t[],\n\t\t\t\t\t{\n\t\t\t\t\t\tmin: \"0.0\",\n\t\t\t\t\t\tmax: \"255.255\",\n\t\t\t\t\t},\n\t\t\t\t\ttrue,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\t// ...down here:\n\t\t\t\t\t{\n\t\t\t\t\t\tdisableStrictEntryControlDataValidation: true,\n\t\t\t\t\t\tdisableStrictMeasurementValidation: true,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate _options: ZnifferOptions;\n\n\t/** The serial port instance */\n\tprivate serial: ZnifferSerialPortBase | undefined;\n\tprivate parsingContext: Omit<\n\t\tCCParsingContext,\n\t\tkeyof HostIDs | \"sourceNodeId\" | \"frameType\" | keyof SecurityManagers\n\t>;\n\n\tprivate _destroyPromise: DeferredPromise<void> | undefined;\n\tprivate get wasDestroyed(): boolean {\n\t\treturn !!this._destroyPromise;\n\t}\n\n\tprivate _chipType: string | UnknownZWaveChipType | undefined;\n\n\tprivate _currentFrequency: number | undefined;\n\t/** The currently configured frequency */\n\tpublic get currentFrequency(): number | undefined {\n\t\treturn this._currentFrequency;\n\t}\n\n\tprivate _supportedFrequencies: Map<number, string> = new Map();\n\t/** A map of supported frequency identifiers and their names */\n\tpublic get supportedFrequencies(): ReadonlyMap<number, string> {\n\t\treturn this._supportedFrequencies;\n\t}\n\n\tprivate _logContainer: ZWaveLogContainer;\n\tprivate znifferLog: ZnifferLogger;\n\n\t/** The security managers for each node */\n\tprivate securityManagers: Map<number, {\n\t\tsecurityManager: SecurityManager | undefined;\n\t\tsecurityManager2: SecurityManager2 | undefined;\n\t\tsecurityManagerLR: SecurityManager2 | undefined;\n\t}> = new Map();\n\n\t/** A list of awaited messages */\n\tprivate awaitedMessages: AwaitedMessageEntry[] = [];\n\n\tprivate _active: boolean;\n\t/** Whether the Zniffer instance is currently capturing */\n\tpublic get active(): boolean {\n\t\treturn this._active;\n\t}\n\n\tprivate _capturedFrames: CapturedData[] = [];\n\n\t/** A list of raw captured frames that can be saved to a .zlf file later */\n\tpublic get capturedFrames(): Readonly<CapturedFrame>[] {\n\t\treturn this._capturedFrames.filter((f) => f.parsedFrame !== undefined)\n\t\t\t.map((f) => ({\n\t\t\t\ttimestamp: f.timestamp,\n\t\t\t\tframeData: f.frameData,\n\t\t\t\tparsedFrame: f.parsedFrame!,\n\t\t\t}));\n\t}\n\n\tpublic async init(): Promise<void> {\n\t\tif (this.wasDestroyed) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The Zniffer was destroyed. Create a new instance and initialize that one.\",\n\t\t\t\tZWaveErrorCodes.Driver_Destroyed,\n\t\t\t);\n\t\t}\n\n\t\t// Open the serial port\n\t\tif (typeof this.port === \"string\") {\n\t\t\tif (this.port.startsWith(\"tcp://\")) {\n\t\t\t\tconst url = new URL(this.port);\n\t\t\t\t// this.znifferLog.print(`opening serial port ${this.port}`);\n\t\t\t\tthis.serial = new ZnifferSocket(\n\t\t\t\t\t{\n\t\t\t\t\t\thost: url.hostname,\n\t\t\t\t\t\tport: parseInt(url.port),\n\t\t\t\t\t},\n\t\t\t\t\tthis._logContainer,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// this.znifferLog.print(`opening serial port ${this.port}`);\n\t\t\t\tthis.serial = new ZnifferSerialPort(\n\t\t\t\t\tthis.port,\n\t\t\t\t\tthis._logContainer,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"opening serial port using the provided custom implementation\",\n\t\t\t// );\n\t\t\tthis.serial = new ZnifferSerialPortBase(\n\t\t\t\tthis.port,\n\t\t\t\tthis._logContainer,\n\t\t\t);\n\t\t}\n\t\tthis.serial\n\t\t\t.on(\"data\", this.serialport_onData.bind(this))\n\t\t\t.on(\"error\", (err) => {\n\t\t\t\tthis.emit(\"error\", err);\n\t\t\t});\n\n\t\tawait this.serial.open();\n\n\t\tthis.znifferLog.print(logo, \"info\");\n\n\t\tawait this.stop();\n\n\t\tconst versionInfo = await this.getVersion();\n\t\tthis._chipType = versionInfo.chipType;\n\t\tthis.znifferLog.print(\n\t\t\t`received Zniffer info:\n Chip type: ${\n\t\t\t\ttypeof versionInfo.chipType === \"string\"\n\t\t\t\t\t? versionInfo.chipType\n\t\t\t\t\t: `unknown (${num2hex(versionInfo.chipType.type)}, ${\n\t\t\t\t\t\tnum2hex(versionInfo.chipType.version)\n\t\t\t\t\t})`\n\t\t\t}\n Zniffer version: ${versionInfo.majorVersion}.${versionInfo.minorVersion}`,\n\t\t\t\"info\",\n\t\t);\n\n\t\tawait this.setBaudrate(0);\n\n\t\tconst freqs = await this.getFrequencies();\n\t\tthis._currentFrequency = freqs.currentFrequency;\n\t\tif (is700PlusSeries(this._chipType)) {\n\t\t\t// The frequencies match the ZnifferRegion enum\n\t\t\tfor (const freq of freqs.supportedFrequencies) {\n\t\t\t\tthis._supportedFrequencies.set(\n\t\t\t\t\tfreq,\n\t\t\t\t\tgetEnumMemberName(ZnifferRegion, freq),\n\t\t\t\t);\n\t\t\t}\n\t\t\t// ... but there might be unknown regions. Query those from the Zniffer\n\t\t\tconst unknownRegions = freqs.supportedFrequencies.filter((f) =>\n\t\t\t\t!isEnumMember(ZnifferRegion, f)\n\t\t\t);\n\t\t\tfor (const freq of unknownRegions) {\n\t\t\t\tconst freqInfo = await this.getFrequencyInfo(freq);\n\t\t\t\tthis._supportedFrequencies.set(freq, freqInfo.frequencyName);\n\t\t\t}\n\t\t} else if (\n\t\t\t// Version 2.55+ supports querying the frequency names\n\t\t\tsdkVersionGte(\n\t\t\t\t`${versionInfo.majorVersion}.${versionInfo.minorVersion}`,\n\t\t\t\t\"2.55\",\n\t\t\t)\n\t\t) {\n\t\t\t// The frequencies are firmware-specific. Query them from the Zniffer\n\t\t\tfor (const freq of freqs.supportedFrequencies) {\n\t\t\t\tconst freqInfo = await this.getFrequencyInfo(freq);\n\t\t\t\tthis._supportedFrequencies.set(freq, freqInfo.frequencyName);\n\t\t\t}\n\t\t} else {\n\t\t\t// The frequencies match the ZnifferRegionLegacy enum, and their info cannot be queried\n\t\t\tfor (const freq of freqs.supportedFrequencies) {\n\t\t\t\tthis._supportedFrequencies.set(\n\t\t\t\t\tfreq,\n\t\t\t\t\tgetEnumMemberName(ZnifferRegionLegacy, freq),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.znifferLog.print(\n\t\t\t`received frequency info:\ncurrent frequency: ${\n\t\t\t\tthis._supportedFrequencies.get(freqs.currentFrequency)\n\t\t\t\t\t?? `unknown (${num2hex(freqs.currentFrequency)})`\n\t\t\t}\nsupported frequencies: ${\n\t\t\t\t[...this._supportedFrequencies].map(([region, name]) =>\n\t\t\t\t\t`\\n \u00B7 ${region.toString().padStart(2, \" \")}: ${name}`\n\t\t\t\t).join(\"\")\n\t\t\t}`,\n\t\t\t\"info\",\n\t\t);\n\n\t\tif (\n\t\t\ttypeof this._options.defaultFrequency === \"number\"\n\t\t\t&& freqs.currentFrequency !== this._options.defaultFrequency\n\t\t\t&& this._supportedFrequencies.has(this._options.defaultFrequency)\n\t\t) {\n\t\t\tawait this.setFrequency(this._options.defaultFrequency);\n\t\t}\n\n\t\tthis.emit(\"ready\");\n\t}\n\n\t/**\n\t * Is called when the serial port has received a Zniffer frame\n\t */\n\tprivate async serialport_onData(\n\t\tdata: Uint8Array,\n\t): Promise<void> {\n\t\tlet msg: ZnifferMessage | undefined;\n\t\ttry {\n\t\t\tmsg = ZnifferMessage.parse(data);\n\t\t} catch (e: any) {\n\t\t\tconsole.error(e);\n\t\t\treturn;\n\t\t}\n\n\t\tif (msg.type === ZnifferMessageType.Command) {\n\t\t\tthis.handleResponse(msg);\n\t\t} else {\n\t\t\tconst dataMsg = msg as ZnifferDataMessage;\n\t\t\tconst capture: CapturedData = {\n\t\t\t\ttimestamp: new Date(),\n\t\t\t\trawData: data,\n\t\t\t\tframeData: dataMsg.payload,\n\t\t\t};\n\t\t\tthis._capturedFrames.push(capture);\n\t\t\tif (\n\t\t\t\tthis._options.maxCapturedFrames != undefined\n\t\t\t\t&& this._capturedFrames.length > this._options.maxCapturedFrames\n\t\t\t) {\n\t\t\t\tthis._capturedFrames.shift();\n\t\t\t}\n\t\t\tawait this.handleDataMessage(dataMsg, capture);\n\t\t}\n\t}\n\n\t/**\n\t * Is called when a Request-type message was received\n\t */\n\tprivate handleResponse(msg: ZnifferMessage): void {\n\t\t// Check if we have a dynamic handler waiting for this message\n\t\tfor (const entry of this.awaitedMessages) {\n\t\t\tif (entry.predicate(msg)) {\n\t\t\t\t// We do\n\t\t\t\tentry.handler(msg);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Is called when a Request-type message was received\n\t */\n\tprivate async handleDataMessage(\n\t\tmsg: ZnifferDataMessage,\n\t\tcapture: CapturedData,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tlet convertedRSSI: RSSI | undefined;\n\t\t\tif (this._options.convertRSSI && this._chipType) {\n\t\t\t\tconvertedRSSI = tryConvertRSSI(\n\t\t\t\t\tmsg.rssiRaw,\n\t\t\t\t\tthis._chipType,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Short-circuit if we're dealing with beam frames\n\t\t\tif (\n\t\t\t\tmsg.frameType === ZnifferFrameType.BeamStart\n\t\t\t\t|| msg.frameType === ZnifferFrameType.BeamStop\n\t\t\t) {\n\t\t\t\tconst beam = parseBeamFrame(msg);\n\t\t\t\tbeam.frameInfo.rssi = convertedRSSI;\n\n\t\t\t\t// Emit the captured frame in a format that's easier to work with for applications.\n\t\t\t\tthis.znifferLog.beam(beam);\n\t\t\t\tconst frame = beamToFrame(beam);\n\t\t\t\tcapture.parsedFrame = frame;\n\t\t\t\tthis.emit(\"frame\", frame, capture.frameData);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only handle messages with a valid checksum, expose the others as CRC errors\n\t\t\tif (!msg.checksumOK) {\n\t\t\t\tthis.znifferLog.crcError(msg);\n\t\t\t\tconst frame = znifferDataMessageToCorruptedFrame(msg);\n\t\t\t\tcapture.parsedFrame = frame;\n\t\t\t\tthis.emit(\"corrupted frame\", frame, capture.frameData);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst mpdu = parseMPDU(msg);\n\t\t\tmpdu.frameInfo.rssi = convertedRSSI;\n\n\t\t\t// Try to decode the CC while assuming the role of the receiver\n\t\t\tlet destSecurityManager: SecurityManager | undefined;\n\t\t\tlet destSecurityManager2: SecurityManager2 | undefined;\n\t\t\tlet destSecurityManagerLR: SecurityManager2 | undefined;\n\t\t\t// Only frames with a destination node id contains something that requires access to the own node ID\n\t\t\tlet destNodeId = 0xff;\n\n\t\t\tlet cc: CommandClass | undefined;\n\n\t\t\t// FIXME: Cache data => parsed CC, so we can understand re-transmitted S2 frames\n\n\t\t\tif (\n\t\t\t\tmpdu.payload.length > 0\n\t\t\t\t&& mpdu.headerType !== MPDUHeaderType.Acknowledgement\n\t\t\t) {\n\t\t\t\tif (\"destinationNodeId\" in mpdu) {\n\t\t\t\t\tdestNodeId = mpdu.destinationNodeId;\n\t\t\t\t\t({\n\t\t\t\t\t\tsecurityManager: destSecurityManager,\n\t\t\t\t\t\tsecurityManager2: destSecurityManager2,\n\t\t\t\t\t\tsecurityManagerLR: destSecurityManagerLR,\n\t\t\t\t\t} = await this.getSecurityManagers(mpdu.destinationNodeId));\n\t\t\t\t}\n\n\t\t\t\t// TODO: Support parsing multicast S2 frames\n\t\t\t\tconst frameType: FrameType =\n\t\t\t\t\tmpdu.headerType === MPDUHeaderType.Multicast\n\t\t\t\t\t\t? \"multicast\"\n\t\t\t\t\t\t: (destNodeId === NODE_ID_BROADCAST\n\t\t\t\t\t\t\t\t|| destNodeId === NODE_ID_BROADCAST_LR)\n\t\t\t\t\t\t? \"broadcast\"\n\t\t\t\t\t\t: \"singlecast\";\n\t\t\t\ttry {\n\t\t\t\t\tcc = await CommandClass.parseAsync(\n\t\t\t\t\t\tmpdu.payload,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thomeId: mpdu.homeId,\n\t\t\t\t\t\t\townNodeId: destNodeId,\n\t\t\t\t\t\t\tsourceNodeId: mpdu.sourceNodeId,\n\t\t\t\t\t\t\tframeType,\n\t\t\t\t\t\t\tsecurityManager: destSecurityManager,\n\t\t\t\t\t\t\tsecurityManager2: destSecurityManager2,\n\t\t\t\t\t\t\tsecurityManagerLR: destSecurityManagerLR,\n\t\t\t\t\t\t\t...this.parsingContext,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\t// Ignore\n\t\t\t\t\tconsole.error(e.stack);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.znifferLog.mpdu(mpdu, cc);\n\n\t\t\t// Emit the captured frame in a format that's easier to work with for applications.\n\t\t\tconst frame = mpduToFrame(mpdu, cc);\n\t\t\tcapture.parsedFrame = frame;\n\t\t\tthis.emit(\"frame\", frame, capture.frameData);\n\n\t\t\t// Update the security managers when nonces are exchanged, so we can\n\t\t\t// decrypt the communication\n\t\t\tif (cc?.ccId === CommandClasses[\"Security 2\"]) {\n\t\t\t\tconst securityManagers = await this.getSecurityManagers(\n\t\t\t\t\tmpdu.sourceNodeId,\n\t\t\t\t);\n\t\t\t\tconst isLR = isLongRangeNodeId(mpdu.sourceNodeId)\n\t\t\t\t\t|| isLongRangeNodeId(destNodeId);\n\t\t\t\tconst senderSecurityManager = isLR\n\t\t\t\t\t? securityManagers.securityManagerLR\n\t\t\t\t\t: securityManagers.securityManager2;\n\t\t\t\tconst destSecurityManager = isLR\n\t\t\t\t\t? destSecurityManagerLR\n\t\t\t\t\t: destSecurityManager2;\n\n\t\t\t\tif (senderSecurityManager && destSecurityManager) {\n\t\t\t\t\tif (cc instanceof Security2CCNonceGet) {\n\t\t\t\t\t\t// Nonce Get -> all nonces are now invalid\n\t\t\t\t\t\tsenderSecurityManager.deleteNonce(destNodeId);\n\t\t\t\t\t\tdestSecurityManager.deleteNonce(mpdu.sourceNodeId);\n\t\t\t\t\t} else if (cc instanceof Security2CCNonceReport && cc.SOS) {\n\t\t\t\t\t\t// Nonce Report (SOS) -> We only know the receiver's nonce\n\t\t\t\t\t\tsenderSecurityManager.setSPANState(destNodeId, {\n\t\t\t\t\t\t\ttype: SPANState.LocalEI,\n\t\t\t\t\t\t\treceiverEI: cc.receiverEI!,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tdestSecurityManager.storeRemoteEI(\n\t\t\t\t\t\t\tmpdu.sourceNodeId,\n\t\t\t\t\t\t\tcc.receiverEI!,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else if (cc instanceof Security2CCMessageEncapsulation) {\n\t\t\t\t\t\tconst senderEI = cc.getSenderEI();\n\t\t\t\t\t\tif (senderEI) {\n\t\t\t\t\t\t\t// The receiver should now have a valid SPAN state, since decoding the S2 CC updates it.\n\t\t\t\t\t\t\t// The security manager for the sender however, does not. Therefore, update it manually,\n\t\t\t\t\t\t\t// if the receiver SPAN is indeed valid.\n\n\t\t\t\t\t\t\tconst receiverSPANState = destSecurityManager\n\t\t\t\t\t\t\t\t.getSPANState(mpdu.sourceNodeId);\n\t\t\t\t\t\t\tif (receiverSPANState.type === SPANState.SPAN) {\n\t\t\t\t\t\t\t\tsenderSecurityManager.setSPANState(\n\t\t\t\t\t\t\t\t\tdestNodeId,\n\t\t\t\t\t\t\t\t\treceiverSPANState,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (\n\t\t\t\tcc?.ccId === CommandClasses.Security\n\t\t\t\t&& cc instanceof SecurityCCNonceReport\n\t\t\t) {\n\t\t\t\tconst senderSecurityManager =\n\t\t\t\t\t(await this.getSecurityManagers(mpdu.sourceNodeId))\n\t\t\t\t\t\t.securityManager;\n\t\t\t\tconst destSecurityManager =\n\t\t\t\t\t(await this.getSecurityManagers(destNodeId))\n\t\t\t\t\t\t.securityManager;\n\n\t\t\t\tif (senderSecurityManager && destSecurityManager) {\n\t\t\t\t\t// Both nodes have a shared nonce now\n\t\t\t\t\tsenderSecurityManager.setNonce(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tissuer: mpdu.sourceNodeId,\n\t\t\t\t\t\t\tnonceId: senderSecurityManager.getNonceId(\n\t\t\t\t\t\t\t\tcc.nonce,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnonce: cc.nonce,\n\t\t\t\t\t\t\treceiver: destNodeId,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ free: true },\n\t\t\t\t\t);\n\n\t\t\t\t\tdestSecurityManager.setNonce(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tissuer: mpdu.sourceNodeId,\n\t\t\t\t\t\t\tnonceId: senderSecurityManager.getNonceId(\n\t\t\t\t\t\t\t\tcc.nonce,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnonce: cc.nonce,\n\t\t\t\t\t\t\treceiver: destNodeId,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ free: true },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e: any) {\n\t\t\tconsole.error(e);\n\t\t}\n\t}\n\n\t/**\n\t * Waits until a certain serial message is received or a timeout has elapsed. Returns the received message.\n\t * @param timeout The number of milliseconds to wait. If the timeout elapses, the returned promise will be rejected\n\t * @param predicate A predicate function to test all incoming messages.\n\t */\n\tprivate waitForMessage<T extends ZnifferMessage>(\n\t\tpredicate: (msg: ZnifferMessage) => boolean,\n\t\ttimeout: number,\n\t): Promise<T> {\n\t\treturn new Promise<T>((resolve, reject) => {\n\t\t\tconst promise = createDeferredPromise<ZnifferMessage>();\n\t\t\tconst entry: AwaitedMessageEntry = {\n\t\t\t\tpredicate,\n\t\t\t\thandler: (msg) => promise.resolve(msg),\n\t\t\t\ttimeout: undefined,\n\t\t\t};\n\t\t\tthis.awaitedMessages.push(entry);\n\t\t\tconst removeEntry = () => {\n\t\t\t\tif (entry.timeout) clearTimeout(entry.timeout);\n\t\t\t\tconst index = this.awaitedMessages.indexOf(entry);\n\t\t\t\tif (index !== -1) this.awaitedMessages.splice(index, 1);\n\t\t\t};\n\t\t\t// When the timeout elapses, remove the wait entry and reject the returned Promise\n\t\t\tentry.timeout = setTimeout(() => {\n\t\t\t\tremoveEntry();\n\t\t\t\treject(\n\t\t\t\t\tnew ZWaveError(\n\t\t\t\t\t\t`Received no matching message within the provided timeout!`,\n\t\t\t\t\t\tZWaveErrorCodes.Controller_Timeout,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}, timeout);\n\t\t\t// When the promise is resolved, remove the wait entry and resolve the returned Promise\n\t\t\tvoid promise.then((cc) => {\n\t\t\t\tremoveEntry();\n\t\t\t\tresolve(cc as T);\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async getVersion() {\n\t\tconst req = new ZnifferGetVersionRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tconst res = await this.waitForMessage<ZnifferGetVersionResponse>(\n\t\t\t(msg) => msg instanceof ZnifferGetVersionResponse,\n\t\t\t1000,\n\t\t);\n\n\t\treturn pick(res, [\"chipType\", \"majorVersion\", \"minorVersion\"]);\n\t}\n\n\tprivate async getFrequencies() {\n\t\tconst req = new ZnifferGetFrequenciesRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tconst res = await this.waitForMessage<ZnifferGetFrequenciesResponse>(\n\t\t\t(msg) => msg instanceof ZnifferGetFrequenciesResponse,\n\t\t\t1000,\n\t\t);\n\n\t\treturn pick(res, [\n\t\t\t\"currentFrequency\",\n\t\t\t\"supportedFrequencies\",\n\t\t]);\n\t}\n\n\tpublic async setFrequency(frequency: number): Promise<void> {\n\t\tconst req = new ZnifferSetFrequencyRequest({ frequency });\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferSetFrequencyResponse>(\n\t\t\t(msg) => msg instanceof ZnifferSetFrequencyResponse,\n\t\t\t1000,\n\t\t);\n\t\tthis._currentFrequency = frequency;\n\t}\n\n\tprivate async getFrequencyInfo(frequency: number) {\n\t\tconst req = new ZnifferGetFrequencyInfoRequest({ frequency });\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tconst res = await this.waitForMessage<ZnifferGetFrequencyInfoResponse>(\n\t\t\t(msg) =>\n\t\t\t\tmsg instanceof ZnifferGetFrequencyInfoResponse\n\t\t\t\t&& msg.frequency === frequency,\n\t\t\t1000,\n\t\t);\n\n\t\treturn pick(res, [\"numChannels\", \"frequencyName\"]);\n\t}\n\n\t/** Starts the capture and discards all previously captured frames */\n\tpublic async start(): Promise<void> {\n\t\tif (this.wasDestroyed) {\n\t\t\tthrow new ZWaveError(\n\t\t\t\t\"The Zniffer is not ready or has been destroyed\",\n\t\t\t\tZWaveErrorCodes.Driver_NotReady,\n\t\t\t);\n\t\t}\n\n\t\tif (this._active) return;\n\t\tthis._capturedFrames = [];\n\t\tthis._active = true;\n\n\t\tconst req = new ZnifferStartRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferStartResponse>(\n\t\t\t(msg) => msg instanceof ZnifferStartResponse,\n\t\t\t1000,\n\t\t);\n\t}\n\n\tpublic async stop(): Promise<void> {\n\t\tif (!this._active) return;\n\t\tthis._active = false;\n\n\t\tif (!this.serial) return;\n\n\t\tconst req = new ZnifferStopRequest();\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferStopResponse>(\n\t\t\t(msg) => msg instanceof ZnifferStopResponse,\n\t\t\t1000,\n\t\t);\n\t}\n\n\tprivate async setBaudrate(baudrate: 0): Promise<void> {\n\t\tconst req = new ZnifferSetBaudRateRequest({ baudrate });\n\t\tawait this.serial?.writeAsync(req.serialize());\n\t\tawait this.waitForMessage<ZnifferSetBaudRateResponse>(\n\t\t\t(msg) => msg instanceof ZnifferSetBaudRateResponse,\n\t\t\t1000,\n\t\t);\n\t}\n\n\tprivate async getSecurityManagers(\n\t\tsourceNodeId: number,\n\t) {\n\t\tif (this.securityManagers.has(sourceNodeId)) {\n\t\t\treturn this.securityManagers.get(sourceNodeId)!;\n\t\t}\n\t\t// Initialize security\n\t\t// Set up the S0 security manager. We can only do that after the controller\n\t\t// interview because we need to know the controller node id.\n\t\tconst S0Key = this._options.securityKeys?.S0_Legacy;\n\t\tlet securityManager: SecurityManager | undefined;\n\t\tif (S0Key) {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"Network key for S0 configured, enabling S0 security manager...\",\n\t\t\t// );\n\t\t\tsecurityManager = new SecurityManager({\n\t\t\t\tnetworkKey: S0Key,\n\t\t\t\t// FIXME: Track nonces separately for each destination node\n\t\t\t\townNodeId: sourceNodeId,\n\t\t\t\tnonceTimeout: Number.POSITIVE_INFINITY,\n\t\t\t});\n\t\t\t// } else {\n\t\t\t// \tthis.znifferLog.print(\n\t\t\t// \t\t\"No network key for S0 configured, cannot decrypt communication from secure (S0) devices!\",\n\t\t\t// \t\t\"warn\",\n\t\t\t// \t);\n\t\t}\n\n\t\tlet securityManager2: SecurityManager2 | undefined;\n\t\tif (\n\t\t\tthis._options.securityKeys\n\t\t\t// Only set it up if we have security keys for at least one S2 security class\n\t\t\t&& Object.keys(this._options.securityKeys).some(\n\t\t\t\t(key) =>\n\t\t\t\t\tkey.startsWith(\"S2_\")\n\t\t\t\t\t&& key in SecurityClass\n\t\t\t\t\t&& securityClassIsS2((SecurityClass as any)[key]),\n\t\t\t)\n\t\t) {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"At least one network key for S2 configured, enabling S2 security manager...\",\n\t\t\t// );\n\t\t\tsecurityManager2 = await SecurityManager2.create();\n\t\t\t// Small hack: Zniffer does not care about S2 duplicates\n\t\t\tsecurityManager2.isDuplicateSinglecast = () => false;\n\n\t\t\t// Set up all keys\n\t\t\tfor (\n\t\t\t\tconst secClass of [\n\t\t\t\t\t\"S2_Unauthenticated\",\n\t\t\t\t\t\"S2_Authenticated\",\n\t\t\t\t\t\"S2_AccessControl\",\n\t\t\t\t\t\"S0_Legacy\",\n\t\t\t\t] as const\n\t\t\t) {\n\t\t\t\tconst key = this._options.securityKeys[secClass];\n\t\t\t\tif (key) {\n\t\t\t\t\tawait securityManager2.setKeyAsync(\n\t\t\t\t\t\tSecurityClass[secClass],\n\t\t\t\t\t\tkey,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// } else {\n\t\t\t// \tthis.znifferLog.print(\n\t\t\t// \t\t\"No network key for S2 configured, cannot decrypt communication from secure (S2) devices!\",\n\t\t\t// \t\t\"warn\",\n\t\t\t// \t);\n\t\t}\n\n\t\tlet securityManagerLR: SecurityManager2 | undefined;\n\t\tif (\n\t\t\tthis._options.securityKeysLongRange?.S2_AccessControl\n\t\t\t|| this._options.securityKeysLongRange?.S2_Authenticated\n\t\t) {\n\t\t\t// this.znifferLog.print(\n\t\t\t// \t\"At least one network key for Z-Wave Long Range configured, enabling security manager...\",\n\t\t\t// );\n\t\t\tsecurityManagerLR = await SecurityManager2.create();\n\t\t\t// Small hack: Zniffer does not care about S2 duplicates\n\t\t\tsecurityManagerLR.isDuplicateSinglecast = () => false;\n\n\t\t\t// Set up all keys\n\t\t\tif (this._options.securityKeysLongRange?.S2_AccessControl) {\n\t\t\t\tawait securityManagerLR.setKeyAsync(\n\t\t\t\t\tSecurityClass.S2_AccessControl,\n\t\t\t\t\tthis._options.securityKeysLongRange.S2_AccessControl,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (this._options.securityKeysLongRange?.S2_Authenticated) {\n\t\t\t\tawait securityManagerLR.setKeyAsync(\n\t\t\t\t\tSecurityClass.S2_Authenticated,\n\t\t\t\t\tthis._options.securityKeysLongRange.S2_Authenticated,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// } else {\n\t\t\t// \tthis.znifferLog.print(\n\t\t\t// \t\t\"No network key for Z-Wave Long Range configured, cannot decrypt Long Range communication!\",\n\t\t\t// \t\t\"warn\",\n\t\t\t// \t);\n\t\t}\n\n\t\tconst ret = {\n\t\t\tsecurityManager,\n\t\t\tsecurityManager2,\n\t\t\tsecurityManagerLR,\n\t\t};\n\t\tthis.securityManagers.set(sourceNodeId, ret);\n\t\treturn ret;\n\t}\n\n\t/** Clears the list of captured frames */\n\tpublic clearCapturedFrames(): void {\n\t\tthis._capturedFrames = [];\n\t}\n\n\t/**\n\t * Get the captured frames in the official Zniffer application format.\n\t * @param frameFilter Optional predicate function to filter the frames included in the capture\n\t */\n\tpublic getCaptureAsZLFBuffer(\n\t\tframeFilter?: (frame: CapturedFrame) => boolean,\n\t): Uint8Array {\n\t\t// Mimics the current Zniffer software, without using features like sessions and comments\n\t\tconst header = new Bytes(2048).fill(0);\n\t\theader[0] = 0x68; // zniffer version\n\t\theader.writeUInt16BE(0x2312, 0x07fe); // checksum\n\t\tlet filteredFrames = this._capturedFrames;\n\t\tif (frameFilter) {\n\t\t\tfilteredFrames = filteredFrames.filter((f) =>\n\t\t\t\t// Always include Zniffer-protocol frames\n\t\t\t\tf.parsedFrame == undefined\n\t\t\t\t// Apply the filter to all other frames\n\t\t\t\t|| frameFilter({\n\t\t\t\t\tframeData: f.frameData,\n\t\t\t\t\tparsedFrame: f.parsedFrame,\n\t\t\t\t\ttimestamp: f.timestamp,\n\t\t\t\t})\n\t\t\t);\n\t\t}\n\t\treturn Bytes.concat([\n\t\t\theader,\n\t\t\t...filteredFrames.map(captureToZLFEntry),\n\t\t]);\n\t}\n\n\t/**\n\t * Saves the captured frames in a `.zlf` file that can be read by the official Zniffer application.\n\t * @param frameFilter Optional predicate function to filter the frames included in the capture\n\t */\n\tpublic async saveCaptureToFile(\n\t\tfilePath: string,\n\t\tframeFilter?: (frame: CapturedFrame) => boolean,\n\t): Promise<void> {\n\t\tawait fs.writeFile(filePath, this.getCaptureAsZLFBuffer(frameFilter));\n\t}\n\n\t/**\n\t * Terminates the Zniffer instance and closes the underlying serial connection.\n\t * Must be called under any circumstances.\n\t */\n\tpublic async destroy(): Promise<void> {\n\t\t// Ensure this is only called once and all subsequent calls block\n\t\tif (this._destroyPromise) return this._destroyPromise;\n\t\tthis._destroyPromise = createDeferredPromise();\n\n\t\tthis.znifferLog.print(\"Destroying Zniffer instance...\");\n\n\t\tif (this._active) {\n\t\t\tawait this.stop().catch(noop);\n\t\t}\n\n\t\tif (this.serial != undefined) {\n\t\t\t// Avoid spewing errors if the port was in the middle of receiving something\n\t\t\tthis.serial.removeAllListeners();\n\t\t\tif (this.serial.isOpen) await this.serial.close();\n\t\t\tthis.serial = undefined;\n\t\t}\n\n\t\tthis.znifferLog.print(\"Zniffer instance destroyed\");\n\n\t\t// destroy loggers as the very last thing\n\t\tthis._logContainer.destroy();\n\n\t\tthis._destroyPromise.resolve();\n\t}\n}\n\nfunction captureToZLFEntry(\n\tcapture: CapturedData,\n): Uint8Array {\n\tconst buffer = new Bytes(14 + capture.rawData.length).fill(0);\n\t// Convert the date to a .NET datetime\n\tlet ticks = BigInt(capture.timestamp.getTime()) * 10000n\n\t\t+ 621355968000000000n;\n\tticks = ticks | 4000000000000000n; // marks the time as .NET DateTimeKind.Local\n\n\tbuffer.writeBigUInt64LE(ticks, 0);\n\tconst direction = 0b0000_0000; // inbound, outbound would be 0b1000_0000\n\n\tbuffer[8] = direction | 0x01; // dir + session ID\n\tbuffer[9] = capture.rawData.length;\n\t// bytes 10-12 are empty\n\tbuffer.set(capture.rawData, 13);\n\tbuffer[buffer.length - 1] = 0xfe; // end of frame\n\treturn buffer;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_core", "frame", "destSecurityManager", "fs"]
|
|
7
7
|
}
|
|
@@ -5,7 +5,7 @@ import { FunctionType } from "@zwave-js/serial";
|
|
|
5
5
|
import { SerialAPISetupCommand, type SerialAPISetup_GetPowerlevelResponse } from "@zwave-js/serial/serialapi";
|
|
6
6
|
import { ExtendedNVMOperationsCommand } from "@zwave-js/serial/serialapi";
|
|
7
7
|
import { type NVMId } from "@zwave-js/serial/serialapi";
|
|
8
|
-
import { type ReadonlyObjectKeyMap, type ReadonlyThrowingMap,
|
|
8
|
+
import { type ReadonlyObjectKeyMap, type ReadonlyThrowingMap, TypedEventTarget } from "@zwave-js/shared";
|
|
9
9
|
import type { StatisticsEventCallbacks } from "../driver/Statistics.js";
|
|
10
10
|
import { ZWaveNode } from "../node/Node.js";
|
|
11
11
|
import { VirtualNode } from "../node/VirtualNode.js";
|
|
@@ -40,7 +40,7 @@ interface ControllerEventCallbacks extends StatisticsEventCallbacks<ControllerSt
|
|
|
40
40
|
export type ControllerEvents = Extract<keyof ControllerEventCallbacks, string>;
|
|
41
41
|
export interface ZWaveController extends ControllerStatisticsHost {
|
|
42
42
|
}
|
|
43
|
-
export declare class ZWaveController extends
|
|
43
|
+
export declare class ZWaveController extends TypedEventTarget<ControllerEventCallbacks> {
|
|
44
44
|
private readonly driver;
|
|
45
45
|
private _type;
|
|
46
46
|
get type(): MaybeNotKnown<ZWaveLibraryTypes>;
|