zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
565 lines (564 loc) • 28 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var FirmwareUpdate_exports = {};
__export(FirmwareUpdate_exports, {
FirmwareUpdateMixin: () => FirmwareUpdateMixin,
isFirmwareUpdateOTATask: () => isFirmwareUpdateOTATask
});
module.exports = __toCommonJS(FirmwareUpdate_exports);
var import_cc = require("@zwave-js/cc");
var import_core = require("@zwave-js/core");
var import_serialapi = require("@zwave-js/serial/serialapi");
var import_shared = require("@zwave-js/shared");
var import_waddle = require("@zwave-js/waddle");
var import_arrays = require("alcalzone-shared/arrays");
var import_async = require("alcalzone-shared/async");
var import_deferred_promise = require("alcalzone-shared/deferred-promise");
var import_math = require("alcalzone-shared/math");
var import_typeguards = require("alcalzone-shared/typeguards");
var import_Task = require("../../driver/Task.js");
var import_ScheduledPoll = require("./60_ScheduledPoll.js");
function isFirmwareUpdateOTATask(t) {
return t.tag?.id === "firmware-update-ota";
}
__name(isFirmwareUpdateOTATask, "isFirmwareUpdateOTATask");
class FirmwareUpdateMixin extends import_ScheduledPoll.SchedulePollMixin {
static {
__name(this, "FirmwareUpdateMixin");
}
async getFirmwareUpdateCapabilities() {
const api = this.commandClasses["Firmware Update Meta Data"];
const meta = await api.getMetaData();
if (!meta) {
throw new import_core.ZWaveError(`Failed to request firmware update capabilities: The node did not respond in time!`, import_core.ZWaveErrorCodes.Controller_NodeTimeout);
} else if (!meta.firmwareUpgradable) {
return {
firmwareUpgradable: false
};
}
return {
firmwareUpgradable: true,
// TODO: Targets are not the list of IDs - maybe expose the IDs as well?
firmwareTargets: Array.from({ length: 1 + meta.additionalFirmwareIDs.length }).fill(0).map((_, i) => i),
continuesToFunction: meta.continuesToFunction,
supportsActivation: meta.supportsActivation,
supportsResuming: meta.supportsResuming,
supportsNonSecureTransfer: meta.supportsNonSecureTransfer
};
}
getFirmwareUpdateCapabilitiesCached() {
const firmwareUpgradable = this.getValue(import_cc.FirmwareUpdateMetaDataCCValues.firmwareUpgradable.id);
const supportsActivation = this.getValue(import_cc.FirmwareUpdateMetaDataCCValues.supportsActivation.id);
const continuesToFunction = this.getValue(import_cc.FirmwareUpdateMetaDataCCValues.continuesToFunction.id);
const additionalFirmwareIDs = this.getValue(import_cc.FirmwareUpdateMetaDataCCValues.additionalFirmwareIDs.id);
const supportsResuming = this.getValue(import_cc.FirmwareUpdateMetaDataCCValues.supportsResuming.id);
const supportsNonSecureTransfer = this.getValue(import_cc.FirmwareUpdateMetaDataCCValues.supportsNonSecureTransfer.id);
if (!firmwareUpgradable || !(0, import_typeguards.isArray)(additionalFirmwareIDs)) {
return { firmwareUpgradable: false };
}
return {
firmwareUpgradable: true,
// TODO: Targets are not the list of IDs - maybe expose the IDs as well?
firmwareTargets: Array.from({ length: 1 + additionalFirmwareIDs.length }).fill(0).map((_, i) => i),
continuesToFunction,
supportsActivation,
supportsResuming,
supportsNonSecureTransfer
};
}
_abortFirmwareUpdate;
async abortFirmwareUpdate() {
if (!this._abortFirmwareUpdate)
return;
await this._abortFirmwareUpdate();
}
// Stores the CRC of the previously transferred firmware image.
// Allows detecting whether resuming is supported and where to continue in a multi-file transfer.
_previousFirmwareCRC;
/** Is used to remember fragment requests that came in before they were able to be handled */
_firmwareUpdatePrematureRequest;
async updateFirmware(updates, options = {}) {
if (updates.length === 0) {
throw new import_core.ZWaveError(`At least one update must be provided`, import_core.ZWaveErrorCodes.Argument_Invalid);
}
if (updates.some((u) => u.data.length === 0)) {
throw new import_core.ZWaveError(`All firmware updates must have a non-empty data buffer`, import_core.ZWaveErrorCodes.Argument_Invalid);
}
if ((0, import_arrays.distinct)(updates.map((u) => u.firmwareTarget ?? 0)).length !== updates.length) {
throw new import_core.ZWaveError(`The target of all provided firmware updates must be unique`, import_core.ZWaveErrorCodes.Argument_Invalid);
}
if (this.driver.isOTWFirmwareUpdateInProgress()) {
throw new import_core.ZWaveError(`Failed to start the update: An OTW upgrade of the controller is in progress!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_Busy);
}
const task = this.getUpdateFirmwareTask(updates, options);
if (task instanceof Promise) {
throw new import_core.ZWaveError(`Failed to start the update: A firmware update is already in progress for this node!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_Busy);
}
return this.driver.scheduler.queueTask(task);
}
isFirmwareUpdateInProgress() {
return !!this.driver.scheduler.findTask(isFirmwareUpdateOTATask);
}
getUpdateFirmwareTask(updates, options = {}) {
const self = this;
const existingTask = this.driver.scheduler.findTask((t) => t.tag?.id === "firmware-update-ota" && t.tag.nodeId === self.id);
if (existingTask)
return existingTask;
let keepAwake;
return {
// Firmware updates cause a lot of traffic. Execute them in the background.
priority: import_Task.TaskPriority.Lower,
tag: { id: "firmware-update-ota", nodeId: self.id },
task: /* @__PURE__ */ __name(async function* firmwareUpdateTask() {
keepAwake = self.keepAwake;
self.keepAwake = true;
const abortContext = {
abort: false,
tooLateToAbort: false,
abortPromise: (0, import_deferred_promise.createDeferredPromise)()
};
self._abortFirmwareUpdate = async () => {
if (abortContext.tooLateToAbort) {
throw new import_core.ZWaveError(`The firmware update was transmitted completely, cannot abort anymore.`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort);
}
self.driver.controllerLog.logNode(self.id, {
message: `Aborting firmware update...`,
direction: "outbound"
});
abortContext.abort = true;
const aborted = await abortContext.abortPromise;
if (!aborted) {
throw new import_core.ZWaveError(`The node did not acknowledge the aborted update`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_FailedToAbort);
}
self.driver.controllerLog.logNode(self.id, {
message: `Firmware update aborted`,
direction: "inbound"
});
};
let fragmentSizeSecure;
let fragmentSizeNonSecure;
let hardwareVersion;
let meta;
try {
const prepareResult = await self.prepareFirmwareUpdateInternal(updates.map((u) => u.firmwareTarget ?? 0), abortContext);
if (abortContext.abort) {
const result2 = {
success: false,
status: import_cc.FirmwareUpdateStatus.Error_TransmissionFailed,
reInterview: false
};
self._emit("firmware update finished", self, result2);
return result2;
}
({
fragmentSizeSecure,
fragmentSizeNonSecure,
hardwareVersion,
...meta
} = prepareResult);
} catch {
const result2 = {
success: false,
status: import_cc.FirmwareUpdateStatus.Error_TransmissionFailed,
reInterview: false
};
return result2;
}
yield;
if (!meta.supportsResuming)
options.resume = false;
const securityClass = self.getHighestSecurityClass();
const isSecure = securityClass === import_core.SecurityClass.S0_Legacy || (0, import_core.securityClassIsS2)(securityClass);
if (!isSecure) {
options.nonSecureTransfer = false;
} else if (!meta.supportsNonSecureTransfer) {
options.nonSecureTransfer = false;
}
const notifyProgress = (0, import_shared.throttle)((progress) => self._emit("firmware update progress", self, progress), 250, true);
const updatesWithChecksum = updates.map((u) => ({
...u,
checksum: (0, import_core.CRC16_CCITT)(u.data)
}));
let skipFinishedFiles = -1;
let shouldResume = options.resume && self._previousFirmwareCRC != void 0;
if (shouldResume) {
skipFinishedFiles = updatesWithChecksum.findIndex((u) => u.checksum === self._previousFirmwareCRC);
if (skipFinishedFiles === -1)
shouldResume = false;
}
let updateResult;
let conservativeWaitTime;
const totalBytes = updatesWithChecksum.reduce((total, update) => total + update.data.length, 0);
let sentBytesOfPreviousFiles = 0;
for (let i = 0; i < updatesWithChecksum.length; i++) {
const {
// If the firmware target is not given, update the Z-Wave chip
firmwareTarget: target = 0,
// If the firmware ID is not given, use the current one for the given chip
firmwareId = target === 0 ? meta.firmwareId : meta.additionalFirmwareIDs[target - 1],
data,
checksum
} = updatesWithChecksum[i];
if (i < skipFinishedFiles) {
self.driver.controllerLog.logNode(self.id, `Skipping already completed firmware update (part ${i + 1} / ${updatesWithChecksum.length})...`);
sentBytesOfPreviousFiles += data.length;
continue;
}
self.driver.controllerLog.logNode(self.id, `Updating firmware (part ${i + 1} / ${updatesWithChecksum.length})...`);
let fragmentSize = options.nonSecureTransfer ? fragmentSizeNonSecure : fragmentSizeSecure;
const { resume, nonSecureTransfer } = yield* self.beginFirmwareUpdateInternal(data, meta.manufacturerId, target, firmwareId, fragmentSize, checksum, hardwareVersion, shouldResume, options.nonSecureTransfer);
if (options.nonSecureTransfer && !nonSecureTransfer) {
fragmentSize = fragmentSizeSecure;
}
self._previousFirmwareCRC = checksum;
if (shouldResume) {
self.driver.controllerLog.logNode(self.id, `Node ${resume ? "accepted" : "did not accept"} resuming the update...`);
}
if (nonSecureTransfer) {
self.driver.controllerLog.logNode(self.id, `Firmware will be transferred without encryption...`);
}
yield;
updateResult = yield* self.doFirmwareUpdateInternal(data, fragmentSize, nonSecureTransfer, abortContext, (fragment, total) => {
const progress = {
currentFile: i + 1,
totalFiles: updatesWithChecksum.length,
sentFragments: fragment,
totalFragments: total,
progress: (0, import_math.roundTo)((sentBytesOfPreviousFiles + Math.min(fragment * fragmentSize, data.length)) / totalBytes * 100, 2)
};
notifyProgress(progress);
if (fragment === total) {
sentBytesOfPreviousFiles += data.length;
}
});
conservativeWaitTime = self.driver.getConservativeWaitTimeAfterFirmwareUpdate(updateResult.waitTime);
if (!updateResult.success) {
self.driver.controllerLog.logNode(self.id, {
message: `Firmware update (part ${i + 1} / ${updatesWithChecksum.length}) failed with status ${(0, import_shared.getEnumMemberName)(import_cc.FirmwareUpdateStatus, updateResult.status)}`,
direction: "inbound"
});
const result2 = {
...updateResult,
waitTime: void 0,
reInterview: false
};
self._emit("firmware update finished", self, result2);
return result2;
} else if (i < updatesWithChecksum.length - 1) {
self.driver.controllerLog.logNode(self.id, {
message: `Firmware update (part ${i + 1} / ${updatesWithChecksum.length}) succeeded with status ${(0, import_shared.getEnumMemberName)(import_cc.FirmwareUpdateStatus, updateResult.status)}`,
direction: "inbound"
});
self.driver.controllerLog.logNode(self.id, `Continuing with next part in ${conservativeWaitTime} seconds...`);
shouldResume = false;
yield* (0, import_waddle.waitFor)((0, import_async.wait)(conservativeWaitTime * 1e3, true));
}
}
self._previousFirmwareCRC = void 0;
const result = {
...updateResult,
waitTime: conservativeWaitTime,
reInterview: true
};
keepAwake = true;
self._emit("firmware update finished", self, result);
return result;
}, "firmwareUpdateTask"),
cleanup() {
self._abortFirmwareUpdate = void 0;
self._firmwareUpdatePrematureRequest = void 0;
self.keepAwake = keepAwake;
if (!keepAwake) {
setImmediate(() => {
self.driver.debounceSendNodeToSleep(self);
});
}
return Promise.resolve();
}
};
}
/** Prepares the firmware update of a single target by collecting the necessary information */
async prepareFirmwareUpdateInternal(targets, abortContext) {
const api = this.commandClasses["Firmware Update Meta Data"];
const meta = await api.getMetaData();
if (!meta) {
throw new import_core.ZWaveError(`Failed to start the update: The node did not respond in time!`, import_core.ZWaveErrorCodes.Controller_NodeTimeout);
}
for (const target of targets) {
if (target === 0) {
if (!meta.firmwareUpgradable) {
throw new import_core.ZWaveError(`Failed to start the update: The Z-Wave chip firmware is not upgradable!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable);
}
} else {
if (api.version < 3) {
throw new import_core.ZWaveError(`Failed to start the update: The node does not support upgrading a different firmware target than 0!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound);
} else if (meta.additionalFirmwareIDs[target - 1] == void 0) {
throw new import_core.ZWaveError(`Failed to start the update: Firmware target #${target} not found on this node!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_TargetNotFound);
}
}
}
const fcc = new import_cc.FirmwareUpdateMetaDataCC({ nodeId: this.id });
fcc.toggleEncapsulationFlag(import_core.EncapsulationFlags.Security, this.driver.isCCSecure(fcc.ccId, this.id));
const maxGrossPayloadSizeSecure = this.driver.computeNetCCPayloadSize(fcc);
const maxGrossPayloadSizeNonSecure = this.driver.computeNetCCPayloadSize(fcc, true);
const ccVersion = (0, import_cc.getEffectiveCCVersion)(this.driver, fcc);
const maxNetPayloadSizeSecure = maxGrossPayloadSizeSecure - 2 - (ccVersion >= 2 ? 2 : 0);
const maxNetPayloadSizeNonSecure = maxGrossPayloadSizeNonSecure - 2 - (ccVersion >= 2 ? 2 : 0);
const fragmentSizeSecure = Math.min(maxNetPayloadSizeSecure, meta.maxFragmentSize ?? Number.POSITIVE_INFINITY);
const fragmentSizeNonSecure = Math.min(maxNetPayloadSizeNonSecure, meta.maxFragmentSize ?? Number.POSITIVE_INFINITY);
if (abortContext.abort) {
abortContext.abortPromise.resolve(true);
return;
} else {
return {
...meta,
fragmentSizeSecure,
fragmentSizeNonSecure
};
}
}
async handleUnexpectedFirmwareUpdateGet(command) {
if (this.isFirmwareUpdateInProgress()) {
this._firmwareUpdatePrematureRequest = command;
return;
}
this.driver.controllerLog.logNode(this.id, {
message: `Received Firmware Update Get, but no firmware update is in progress. Forcing the node to abort...`,
direction: "inbound"
});
const fcc = new import_cc.FirmwareUpdateMetaDataCC({ nodeId: this.id });
fcc.toggleEncapsulationFlag(import_core.EncapsulationFlags.Security, !!(command.encapsulationFlags & import_core.EncapsulationFlags.Security));
const ccVersion = (0, import_cc.getEffectiveCCVersion)(this.driver, fcc);
const fragmentSize = this.driver.computeNetCCPayloadSize(fcc) - 2 - (ccVersion >= 2 ? 2 : 0);
const fragment = (0, import_core.randomBytes)(fragmentSize);
try {
await this.sendCorruptedFirmwareUpdateReport(command.reportNumber, fragment);
} catch {
}
}
/** Kicks off a firmware update of a single target. Returns whether the node accepted resuming and non-secure transfer */
async *beginFirmwareUpdateInternal(data, manufacturerId, target, firmwareId, fragmentSize, checksum, hardwareVersion, resume, nonSecureTransfer) {
const api = this.commandClasses["Firmware Update Meta Data"];
this.driver.controllerLog.logNode(this.id, {
message: `Starting firmware update...`,
direction: "outbound"
});
let result = await api.requestUpdate({
// TODO: Should manufacturer id be provided externally?
manufacturerId,
firmwareId,
firmwareTarget: target,
fragmentSize,
checksum,
hardwareVersion,
resume,
nonSecureTransfer
});
if (!result) {
result = yield* (0, import_waddle.waitFor)(this.driver.waitForCommand((cc) => cc instanceof import_cc.FirmwareUpdateMetaDataCCRequestReport && cc.nodeId === this.id, 6e4));
}
switch (result.status) {
case import_cc.FirmwareUpdateRequestStatus.Error_AuthenticationExpected:
throw new import_core.ZWaveError(`Failed to start the update: A manual authentication event (e.g. button push) was expected!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart);
case import_cc.FirmwareUpdateRequestStatus.Error_BatteryLow:
throw new import_core.ZWaveError(`Failed to start the update: The battery level is too low!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart);
case import_cc.FirmwareUpdateRequestStatus.Error_FirmwareUpgradeInProgress:
throw new import_core.ZWaveError(`Failed to start the update: A firmware upgrade is already in progress!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_Busy);
case import_cc.FirmwareUpdateRequestStatus.Error_InvalidManufacturerOrFirmwareID:
throw new import_core.ZWaveError(`Failed to start the update: Invalid manufacturer or firmware id!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart);
case import_cc.FirmwareUpdateRequestStatus.Error_InvalidHardwareVersion:
throw new import_core.ZWaveError(`Failed to start the update: Invalid hardware version!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart);
case import_cc.FirmwareUpdateRequestStatus.Error_NotUpgradable:
throw new import_core.ZWaveError(`Failed to start the update: Firmware target #${target} is not upgradable!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_NotUpgradable);
case import_cc.FirmwareUpdateRequestStatus.Error_FragmentSizeTooLarge:
throw new import_core.ZWaveError(`Failed to start the update: The chosen fragment size is too large!`, import_core.ZWaveErrorCodes.FirmwareUpdateCC_FailedToStart);
case import_cc.FirmwareUpdateRequestStatus.OK:
this.keepAwake = true;
}
return {
resume: !!result.resume,
nonSecureTransfer: !!result.nonSecureTransfer
};
}
async handleFirmwareUpdateMetaDataGet(command) {
const endpoint = this.getEndpoint(command.endpointIndex) ?? this;
const api = endpoint.createAPI(import_core.CommandClasses["Firmware Update Meta Data"], false).withOptions({
// Answer with the same encapsulation as asked, but omit
// Supervision as it shouldn't be used for Get-Report flows
encapsulationFlags: command.encapsulationFlags & ~import_core.EncapsulationFlags.Supervision
});
await api.reportMetaData({
manufacturerId: this.driver.options.vendor?.manufacturerId ?? 65535,
firmwareUpgradable: false,
hardwareVersion: this.driver.options.vendor?.hardwareVersion ?? 0,
// We must advertise Z-Wave JS itself as firmware 1
// No firmware is upgradable, so we advertise firmware id 0
additionalFirmwareIDs: [0]
});
}
async handleFirmwareUpdateRequestGet(command) {
const endpoint = this.getEndpoint(command.endpointIndex) ?? this;
const api = endpoint.createAPI(import_core.CommandClasses["Firmware Update Meta Data"], false).withOptions({
// Answer with the same encapsulation as asked, but omit
// Supervision as it shouldn't be used for Get-Report flows
encapsulationFlags: command.encapsulationFlags & ~import_core.EncapsulationFlags.Supervision
});
await api.respondToUpdateRequest({
status: import_cc.FirmwareUpdateRequestStatus.Error_NotUpgradable
});
}
async handleFirmwareUpdatePrepareGet(command) {
const endpoint = this.getEndpoint(command.endpointIndex) ?? this;
const api = endpoint.createAPI(import_core.CommandClasses["Firmware Update Meta Data"], false).withOptions({
// Answer with the same encapsulation as asked, but omit
// Supervision as it shouldn't be used for Get-Report flows
encapsulationFlags: command.encapsulationFlags & ~import_core.EncapsulationFlags.Supervision
});
await api.respondToDownloadRequest({
status: import_cc.FirmwareDownloadStatus.Error_NotDownloadable,
checksum: 0
});
}
async sendCorruptedFirmwareUpdateReport(reportNum, fragment, nonSecureTransfer = false) {
try {
await this.commandClasses["Firmware Update Meta Data"].withOptions({
// Only encapsulate if the transfer is secure
autoEncapsulate: !nonSecureTransfer
}).sendFirmwareFragment(reportNum, true, fragment);
} catch {
}
}
hasPendingFirmwareUpdateFragment(fragmentNumber) {
const isCurrentFirmwareFragment = /* @__PURE__ */ __name((t) => t.message.getNodeId() === this.id && (0, import_serialapi.containsCC)(t.message) && t.message.command instanceof import_cc.FirmwareUpdateMetaDataCCReport && t.message.command.reportNumber === fragmentNumber, "isCurrentFirmwareFragment");
return this.driver.hasPendingTransactions(isCurrentFirmwareFragment);
}
async *doFirmwareUpdateInternal(data, fragmentSize, nonSecureTransfer, abortContext, onProgress) {
const numFragments = Math.ceil(data.length / fragmentSize);
this._firmwareUpdatePrematureRequest = void 0;
update: while (true) {
yield;
let fragmentRequest;
if (this._firmwareUpdatePrematureRequest) {
fragmentRequest = this._firmwareUpdatePrematureRequest;
this._firmwareUpdatePrematureRequest = void 0;
} else {
try {
fragmentRequest = yield* (0, import_waddle.waitFor)(this.driver.waitForCommand(
(cc) => cc.nodeId === this.id && cc instanceof import_cc.FirmwareUpdateMetaDataCCGet,
// Wait up to 2 minutes for each fragment request.
// Some users try to update devices with unstable connections, where 30s can be too short.
import_core.timespan.minutes(2)
));
} catch {
this.driver.controllerLog.logNode(this.id, {
message: `Firmware update timed out`,
direction: "none",
level: "warn"
});
return {
success: false,
status: import_cc.FirmwareUpdateStatus.Error_Timeout
};
}
}
this.markAsAwake();
if (fragmentRequest.reportNumber > numFragments) {
this.driver.controllerLog.logNode(this.id, {
message: `Received Firmware Update Get for an out-of-bounds fragment. Forcing the node to abort...`,
direction: "inbound"
});
await this.sendCorruptedFirmwareUpdateReport(fragmentRequest.reportNumber, (0, import_core.randomBytes)(fragmentSize), nonSecureTransfer);
break update;
}
request: for (let num = fragmentRequest.reportNumber; num < fragmentRequest.reportNumber + fragmentRequest.numReports; num++) {
yield;
if (num > numFragments) {
break;
}
const fragment = data.subarray((num - 1) * fragmentSize, num * fragmentSize);
if (abortContext.abort) {
await this.sendCorruptedFirmwareUpdateReport(fragmentRequest.reportNumber, (0, import_core.randomBytes)(fragment.length), nonSecureTransfer);
break update;
} else {
if (this.hasPendingFirmwareUpdateFragment(num)) {
this.driver.controllerLog.logNode(this.id, {
message: `Firmware fragment ${num} already queued`,
level: "warn"
});
continue request;
}
this.driver.controllerLog.logNode(this.id, {
message: `Sending firmware fragment ${num} / ${numFragments}`,
direction: "outbound"
});
const isLast = num === numFragments;
try {
await this.commandClasses["Firmware Update Meta Data"].withOptions({
// Only encapsulate if the transfer is secure
autoEncapsulate: !nonSecureTransfer
}).sendFirmwareFragment(num, isLast, fragment);
onProgress(num, numFragments);
if (isLast) {
abortContext.tooLateToAbort = true;
break update;
}
} catch {
this.driver.controllerLog.logNode(this.id, {
message: `Failed to send firmware fragment ${num} / ${numFragments}`,
direction: "outbound",
level: "warn"
});
break request;
}
}
}
}
yield;
const statusReport = yield* (0, import_waddle.waitFor)(this.driver.waitForCommand(
(cc) => cc.nodeId === this.id && cc instanceof import_cc.FirmwareUpdateMetaDataCCStatusReport,
// Wait up to 5 minutes. It should never take that long, but the specs
// don't say anything specific
5 * 6e4
).catch(() => void 0));
if (abortContext.abort) {
abortContext.abortPromise.resolve(statusReport?.status === import_cc.FirmwareUpdateStatus.Error_TransmissionFailed);
}
if (!statusReport) {
this.driver.controllerLog.logNode(this.id, `The node did not acknowledge the completed update`, "warn");
return {
success: false,
status: import_cc.FirmwareUpdateStatus.Error_Timeout
};
}
const { status, waitTime } = statusReport;
const success = status >= import_cc.FirmwareUpdateStatus.OK_WaitingForActivation;
return {
success,
status,
waitTime
};
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FirmwareUpdateMixin,
isFirmwareUpdateOTATask
});
//# sourceMappingURL=70_FirmwareUpdate.js.map