"use strict";
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: new Array(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: new Array(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
