UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

1,127 lines 237 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var Driver_exports = {}; __export(Driver_exports, { Driver: () => Driver, libName: () => libName, libVersion: () => libVersion }); module.exports = __toCommonJS(Driver_exports); var import_cc = require("@zwave-js/cc"); var import_UserCodeCC = require("@zwave-js/cc/UserCodeCC"); var import_config = require("@zwave-js/config"); var import_core = require("@zwave-js/core"); var import_serial = require("@zwave-js/serial"); var import_serialapi = require("@zwave-js/serial/serialapi"); var import_shared = require("@zwave-js/shared"); 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_pathe = __toESM(require("pathe"), 1); var import_version = require("../_version.js"); var import_Controller = require("../controller/Controller.js"); var import_FirmwareUpdateService = require("../controller/FirmwareUpdateService.js"); var import_Inclusion = require("../controller/Inclusion.js"); var import_NodeInformationFrame = require("../controller/NodeInformationFrame.js"); var import_Types = require("../controller/_Types.js"); var import_Driver = require("../log/Driver.js"); var import_Types2 = require("../node/_Types.js"); var import_deviceConfig = require("../telemetry/deviceConfig.js"); var import_statistics = require("../telemetry/statistics.js"); var import_Bootloader = require("./Bootloader.js"); var import_DriverMode = require("./DriverMode.js"); var import_EndDeviceCLI = require("./EndDeviceCLI.js"); var import_MessageGenerators = require("./MessageGenerators.js"); var import_NetworkCache = require("./NetworkCache.js"); var import_Queue = require("./Queue.js"); var import_SerialAPICommandMachine = require("./SerialAPICommandMachine.js"); var import_StateMachineShared = require("./StateMachineShared.js"); var import_Task = require("./Task.js"); var import_ThrottlePresets = require("./ThrottlePresets.js"); var import_Transaction = require("./Transaction.js"); var import_TransportServiceMachine = require("./TransportServiceMachine.js"); var import_UpdateConfig = require("./UpdateConfig.js"); var import_UserAgent = require("./UserAgent.js"); var import_Types3 = require("./_Types.js"); var import_mDNSDiscovery = require("./mDNSDiscovery.js"); (0, import_cc.registerCCs)(); const libVersion = import_version.PACKAGE_VERSION; const libName = import_version.PACKAGE_NAME; const libNameString = ` \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2554\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D `; const defaultOptions = { timeouts: { ack: 1600, // A sending interface MUST wait for 1600ms or more for an ACK Frame after transmitting a Data Frame. byte: 150, // Ideally we'd want to have this as low as possible, but some // 500 series controllers can take several seconds to respond sometimes. response: 1e4, report: 1e3, // ReportTime timeout SHOULD be set to CommandTime + 1 second nonce: 5e3, sendDataAbort: 2e4, // If a controller takes over 20 seconds to reach a node, it's probably not going to happen sendDataCallback: 3e4, // INS13954 defines this to be 65000 ms, but waiting that long causes issues with reporting devices sendToSleep: 250, // The default should be enough time for applications to react to devices waking up retryJammed: 1e3, refreshValue: 5e3, // Default should handle most slow devices until we have a better solution refreshValueAfterTransition: 1e3, // To account for delays in the device serialAPIStarted: 5e3, pollTime: 1e4 // PollTime SHOULD be 10 seconds + CommandTime as per the spec }, attempts: { openSerialPort: 10, controller: 3, sendData: 3, sendDataJammed: 5, nodeInterview: 5, smartStartInclusion: 5, firmwareUpdateOTW: 3 }, disableOptimisticValueUpdate: false, features: { // By default enable soft reset unless the env variable is set softReset: !(0, import_shared.getenv)("ZWAVEJS_DISABLE_SOFT_RESET"), // By default enable the unresponsive controller recovery unless the env variable is set unresponsiveControllerRecovery: !(0, import_shared.getenv)("ZWAVEJS_DISABLE_UNRESPONSIVE_CONTROLLER_RECOVERY"), // By default disable the watchdog watchdog: false, // Support all CCs unless specified otherwise disableCommandClasses: [] }, // By default, try to recover from bootloader mode bootloaderMode: "recover", interview: { queryAllUserCodes: false, applyRecommendedConfigParamValues: false }, storage: { cacheDir: typeof process !== "undefined" ? import_pathe.default.join(process.cwd(), "cache") : "/cache", lockDir: (0, import_shared.getenv)("ZWAVEJS_LOCK_DIRECTORY"), throttle: "normal" }, preferences: { scales: {}, lookupUserIdInNotificationEvents: false } }; function checkOptions(options) { if (options.timeouts.ack < 1) { throw new import_core.ZWaveError(`The ACK timeout must be positive!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.byte < 1) { throw new import_core.ZWaveError(`The BYTE timeout must be positive!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.response < 500 || options.timeouts.response > 6e4) { throw new import_core.ZWaveError(`The Response timeout must be between 500 and 60000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.report < 500 || options.timeouts.report > 1e4) { throw new import_core.ZWaveError(`The Report timeout must be between 500 and 10000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.nonce < 3e3 || options.timeouts.nonce > 2e4) { throw new import_core.ZWaveError(`The Nonce timeout must be between 3000 and 20000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.retryJammed < 10 || options.timeouts.retryJammed > 5e3) { throw new import_core.ZWaveError(`The timeout for retrying while jammed must be between 10 and 5000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.sendToSleep < 10 || options.timeouts.sendToSleep > 5e3) { throw new import_core.ZWaveError(`The Send To Sleep timeout must be between 10 and 5000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.sendDataCallback < 1e4) { throw new import_core.ZWaveError(`The Send Data Callback timeout must be at least 10000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.pollTime < 1e3 || options.timeouts.pollTime > 3e4) { throw new import_core.ZWaveError(`The Poll Time must be between 1000 and 30000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.sendDataAbort < 5e3 || options.timeouts.sendDataAbort > options.timeouts.sendDataCallback - 5e3) { throw new import_core.ZWaveError(`The Send Data Abort Callback timeout must be between 5000 and ${options.timeouts.sendDataCallback - 5e3} milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.timeouts.serialAPIStarted < 1e3 || options.timeouts.serialAPIStarted > 3e4) { throw new import_core.ZWaveError(`The Serial API started timeout must be between 1000 and 30000 milliseconds!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.securityKeys != void 0) { const keys = Object.entries(options.securityKeys); for (let i = 0; i < keys.length; i++) { const [secClass, key] = keys[i]; if (key.length !== 16) { throw new import_core.ZWaveError(`The security key for class ${secClass} must be a buffer with length 16!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (keys.findIndex(([, k]) => (0, import_shared.areUint8ArraysEqual)(k, key)) !== i) { throw new import_core.ZWaveError(`The security key for class ${secClass} was used multiple times!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } } } if (options.attempts.controller < 1 || options.attempts.controller > 3) { throw new import_core.ZWaveError(`The Controller attempts must be between 1 and 3!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.attempts.sendData < 1 || options.attempts.sendData > import_serialapi.MAX_SEND_ATTEMPTS) { throw new import_core.ZWaveError(`The SendData attempts must be between 1 and ${import_serialapi.MAX_SEND_ATTEMPTS}!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.attempts.sendDataJammed < 1 || options.attempts.sendDataJammed > 10) { throw new import_core.ZWaveError(`The SendData attempts while jammed must be between 1 and 10!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.attempts.nodeInterview < 1 || options.attempts.nodeInterview > 10) { throw new import_core.ZWaveError(`The Node interview attempts must be between 1 and 10!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.attempts.smartStartInclusion < 1 || options.attempts.smartStartInclusion > 25) { throw new import_core.ZWaveError(`The SmartStart inclusion attempts must be between 1 and 25!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.attempts.firmwareUpdateOTW < 1 || options.attempts.firmwareUpdateOTW > 5) { throw new import_core.ZWaveError(`The OTW firmware update attempts must be between 1 and 5!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.inclusionUserCallbacks) { if (!(0, import_typeguards.isObject)(options.inclusionUserCallbacks)) { throw new import_core.ZWaveError(`The inclusionUserCallbacks must be an object!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } else if (typeof options.inclusionUserCallbacks.grantSecurityClasses !== "function" || typeof options.inclusionUserCallbacks.validateDSKAndEnterPIN !== "function" || typeof options.inclusionUserCallbacks.abort !== "function") { throw new import_core.ZWaveError(`The inclusionUserCallbacks must contain the following functions: grantSecurityClasses, validateDSKAndEnterPIN, abort!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } } if (options.joinNetworkUserCallbacks) { if (!(0, import_typeguards.isObject)(options.joinNetworkUserCallbacks)) { throw new import_core.ZWaveError(`The joinNetworkUserCallbacks must be an object!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } else if (typeof options.joinNetworkUserCallbacks.showDSK !== "function" || typeof options.joinNetworkUserCallbacks.done !== "function") { throw new import_core.ZWaveError(`The joinNetworkUserCallbacks must contain the following functions: showDSK, done!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } } if (options.rf != void 0) { if (options.rf.region != void 0) { if (typeof options.rf.region !== "number" || !(options.rf.region in import_core.RFRegion) || options.rf.region === import_core.RFRegion.Unknown) { throw new import_core.ZWaveError(`${options.rf.region} is not a valid RF region!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } } if (options.rf.txPower != void 0) { if (!(0, import_typeguards.isObject)(options.rf.txPower)) { throw new import_core.ZWaveError(`rf.txPower must be an object!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (typeof options.rf.txPower.powerlevel !== "number" && options.rf.txPower.powerlevel !== "auto") { throw new import_core.ZWaveError(`rf.txPower.powerlevel must be a number or "auto"!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } if (options.rf.txPower.measured0dBm != void 0 && typeof options.rf.txPower.measured0dBm !== "number") { throw new import_core.ZWaveError(`rf.txPower.measured0dBm must be a number!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } } if (options.features.disableCommandClasses?.length) { const mandatory = /* @__PURE__ */ new Set([ // Encapsulation CCs are always supported ...import_core.encapsulationCCs, // All Root Devices or nodes MUST support import_core.CommandClasses.Association, import_core.CommandClasses["Association Group Information"], import_core.CommandClasses["Device Reset Locally"], import_core.CommandClasses["Firmware Update Meta Data"], import_core.CommandClasses.Indicator, import_core.CommandClasses["Manufacturer Specific"], import_core.CommandClasses["Multi Channel Association"], import_core.CommandClasses.Powerlevel, import_core.CommandClasses.Version, import_core.CommandClasses["Z-Wave Plus Info"] ]); const mandatoryDisabled = options.features.disableCommandClasses.filter((cc) => mandatory.has(cc)); if (mandatoryDisabled.length > 0) { throw new import_core.ZWaveError(`The following CCs are mandatory and cannot be disabled using features.disableCommandClasses: ${mandatoryDisabled.map((cc) => (0, import_core.getCCName)(cc)).join(", ")}!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } } } } __name(checkOptions, "checkOptions"); function messageIsPing(msg) { return (0, import_serialapi.containsCC)(msg) && msg.command instanceof import_cc.NoOperationCC; } __name(messageIsPing, "messageIsPing"); function assertValidCCs(container) { if (container.command instanceof import_cc.InvalidCC) { if (typeof container.command.reason === "number") { throw new import_core.ZWaveError("The message payload failed validation!", container.command.reason); } else { throw new import_core.ZWaveError("The message payload is invalid!", import_core.ZWaveErrorCodes.PacketFormat_InvalidPayload, container.command.reason); } } else if ((0, import_serialapi.containsCC)(container.command)) { assertValidCCs(container.command); } } __name(assertValidCCs, "assertValidCCs"); function wrapLegacyFSDriverForCacheMigrationOnly(legacy) { return { async readFile(path2) { const text = await legacy.readFile(path2, "utf8"); return import_shared.Bytes.from(text, "utf8"); }, async stat(path2) { if (await legacy.pathExists(path2)) { return { isDirectory() { return false; }, isFile() { return true; }, mtime: /* @__PURE__ */ new Date(), size: 0 }; } else { throw new Error("File not found"); } }, readDir(_path) { return Promise.reject(new Error("Not implemented for the legacy FS driver")); } }; } __name(wrapLegacyFSDriverForCacheMigrationOnly, "wrapLegacyFSDriverForCacheMigrationOnly"); class Driver extends import_shared.TypedEventTarget { static { __name(this, "Driver"); } port; constructor(port, ...optionsAndPresets) { super(); this.port = port; if (typeof port !== "string" && !(0, import_serial.isZWaveSerialPortImplementation)(port) && !(0, import_serial.isZWaveSerialBindingFactory)(port)) { throw new import_core.ZWaveError(`The port must be a string or a valid custom serial port implementation!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } const definedOptionsAndPresets = optionsAndPresets.filter((o) => !!o); let mergedOptions = {}; for (const preset of definedOptionsAndPresets) { mergedOptions = (0, import_shared.mergeDeep)(mergedOptions, preset, true); } this._options = (0, import_shared.mergeDeep)(mergedOptions, (0, import_shared.cloneDeep)(defaultOptions)); checkOptions(this._options); if (this._options.userAgent) { if (!(0, import_typeguards.isObject)(this._options.userAgent)) { throw new import_core.ZWaveError(`The userAgent property must be an object!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } this.updateUserAgent(this._options.userAgent); } this.cacheDir = this._options.storage.cacheDir; const self = this; this.messageEncodingContext = { getHighestSecurityClass: /* @__PURE__ */ __name((nodeId) => this.getHighestSecurityClass(nodeId), "getHighestSecurityClass"), hasSecurityClass: /* @__PURE__ */ __name((nodeId, securityClass) => this.hasSecurityClass(nodeId, securityClass), "hasSecurityClass"), setSecurityClass: /* @__PURE__ */ __name((nodeId, securityClass, granted) => this.setSecurityClass(nodeId, securityClass, granted), "setSecurityClass"), getDeviceConfig: /* @__PURE__ */ __name((nodeId) => this.getDeviceConfig(nodeId), "getDeviceConfig"), // These are evaluated lazily, so we cannot spread messageParsingContext unfortunately get securityManager() { return self.securityManager; }, get securityManager2() { return self.securityManager2; }, get securityManagerLR() { return self.securityManagerLR; }, getSupportedCCVersion: /* @__PURE__ */ __name((cc, nodeId, endpointIndex) => this.getSupportedCCVersion(cc, nodeId, endpointIndex), "getSupportedCCVersion") }; this._scheduler = new import_Task.TaskScheduler(() => { return new import_core.ZWaveError("Task was removed", import_core.ZWaveErrorCodes.Driver_TaskRemoved); }); } serialFactory; /** The serial port instance */ serial; messageEncodingContext; getEncodingContext() { return { ...this.messageEncodingContext, ownNodeId: this.controller.ownNodeId, homeId: this.controller.homeId, nodeIdType: this._controller?.nodeIdType ?? import_core.NodeIDType.Short }; } getMessageParsingContext() { return { getDeviceConfig: /* @__PURE__ */ __name((nodeId) => this.getDeviceConfig(nodeId), "getDeviceConfig"), sdkVersion: this._controller?.sdkVersion, requestStorage: this._requestStorage, ownNodeId: this._controller?.ownNodeId ?? 0, // Unspecified node ID homeId: this._controller?.homeId ?? 1431655765, // Invalid home ID nodeIdType: this._controller?.nodeIdType ?? import_core.NodeIDType.Short }; } getCCParsingContext() { return { ...this.messageEncodingContext, ownNodeId: this.controller.ownNodeId, homeId: this.controller.homeId }; } // We have multiple queues to achieve multiple "layers" of communication priority: // The default queue for most messages queue; // Is initialized in initTransactionQueues() // An immediate queue for handling queries that need to be handled ASAP, e.g. Nonce Get immediateQueue; // Is initialized in initTransactionQueues() // And all of them feed into the serial API queue, which contains commands that will be sent ASAP serialAPIQueue; // Is initialized in initControllerAndNodes() // Timers for delayed transaction re-queuing requeueTimers = /* @__PURE__ */ new Map(); // Poll timing state per the Z-Wave specification. // After any transaction completes, we must wait at least pollTime // before starting the next poll transaction. _lastTransactionEnd = 0; // CommandTime is measured from when the poll command is sent to when // the successful transmit report is received. The required wait before // the next poll is pollTime + commandTime. _lastPollCommandTime = 0; _pollDelayTimer; /** Gives access to the transaction queues, ordered by priority */ get queues() { return [this.immediateQueue, this.queue]; } initTransactionQueues() { this.immediateQueue = new import_Queue.TransactionQueue({ name: "immediate", mayStartNextTransaction: /* @__PURE__ */ __name((t) => { if (this.controller.status === import_core.ControllerStatus.Unresponsive) { return t.message instanceof import_serialapi.SoftResetRequest || t.message instanceof import_serialapi.GetControllerVersionRequest; } if (this.controller.status === import_core.ControllerStatus.Jammed) { return t.message instanceof import_serialapi.SoftResetRequest; } return !this.queuePaused && this.controller.status === import_core.ControllerStatus.Ready; }, "mayStartNextTransaction") }); this.queue = new import_Queue.TransactionQueue({ name: "normal", mayStartNextTransaction: /* @__PURE__ */ __name((t) => this.mayStartTransaction(t), "mayStartNextTransaction") }); this._queueIdle = false; for (const queue of this.queues) { void this.drainTransactionQueue(queue); } } async destroyTransactionQueues(reason, errorCode) { for (const set of this.requeueTimers.values()) { for (const timer of set) { timer.clear(); } } this.requeueTimers.clear(); this._pollDelayTimer?.clear(); this._pollDelayTimer = void 0; for (const queue of this.queues) { if (!queue) return; } if ((0, import_shared.getenv)("NODE_ENV") !== "test") { await this.rejectTransactions((_t) => true, reason, errorCode ?? import_core.ZWaveErrorCodes.Driver_TaskRemoved); } for (const queue of this.queues) { queue.abort(); } } _scheduler; get scheduler() { return this._scheduler; } queuePaused = false; /** Used to immediately abort ongoing Serial API commands */ abortSerialAPICommand; initSerialAPIQueue() { this.serialAPIQueue = new import_shared.AsyncQueue(); void this.drainSerialAPIQueue(); } destroySerialAPIQueue(reason, errorCode) { if (!this.serialAPIQueue) return; this.serialAPIQueue.abort(); this.abortSerialAPICommand?.reject(new import_core.ZWaveError(reason, errorCode ?? import_core.ZWaveErrorCodes.Driver_Destroyed)); } // Keep track of which queues are currently busy _queuesBusyFlags = 0; _queueIdle = false; /** Whether the queue is currently idle */ get queueIdle() { return this._queueIdle; } set queueIdle(value) { if (this._queueIdle !== value) { this.driverLog.print(value ? "all queues idle" : "one or more queues busy"); this._queueIdle = value; this.handleQueueIdleChange(value); } } /** A map of handlers for all sorts of requests */ requestHandlers = /* @__PURE__ */ new Map(); /** A list of awaited message headers */ awaitedMessageHeaders = []; /** A list of awaited messages */ awaitedMessages = []; /** A list of awaited commands */ awaitedCommands = []; /** A list of awaited chunks from the bootloader */ awaitedBootloaderChunks = []; /** A list of awaited chunks from the end device CLI */ awaitedCLIChunks = []; /** A list of promises waiting for the queues to become idle */ awaitedIdle = []; /** A map of Node ID -> ongoing sessions */ nodeSessions = /* @__PURE__ */ new Map(); ensureNodeSessions(nodeId) { if (!this.nodeSessions.has(nodeId)) { this.nodeSessions.set(nodeId, { transportService: /* @__PURE__ */ new Map(), supervision: /* @__PURE__ */ new Map() }); } return this.nodeSessions.get(nodeId); } _requestStorage = /* @__PURE__ */ new Map(); /** * @internal * Stores data from Serial API command requests to be used by their responses */ get requestStorage() { return this._requestStorage; } cacheDir; _valueDB; /** @internal */ get valueDB() { return this._valueDB; } _metadataDB; /** @internal */ get metadataDB() { return this._metadataDB; } _networkCache; /** @internal */ get networkCache() { if (this._networkCache == void 0) { throw new import_core.ZWaveError("The network cache was not yet initialized!", import_core.ZWaveErrorCodes.Driver_NotReady); } return this._networkCache; } // This is set during `start()` and should not be accessed before _configManager; get configManager() { return this._configManager; } get configVersion() { return this.configManager?.configVersion ?? require("zwave-js/package.json")?.dependencies?.["@zwave-js/config"] ?? libVersion; } // This is set during `start()` and should not be accessed before _logContainer; // This is set during `start()` and should not be accessed before _driverLog; /** @internal */ get driverLog() { return this._driverLog; } // This is set during `start()` and should not be accessed before _controllerLog; /** @internal */ get controllerLog() { return this._controllerLog; } logNode(...args) { this._controllerLog.logNode(...args); } _controller; /** Encapsulates information about the Z-Wave controller and provides access to its nodes */ get controller() { if (this._controller == void 0) { throw new import_core.ZWaveError("The controller is not yet ready!", import_core.ZWaveErrorCodes.Driver_NotReady); } return this._controller; } /** While in bootloader mode, this encapsulates information about the bootloader and its state */ _bootloader; get bootloader() { if (this._bootloader == void 0) { throw new import_core.ZWaveError("The controller is not in bootloader mode!", import_core.ZWaveErrorCodes.Driver_NotReady); } return this._bootloader; } _cli; /** While in end device CLI mode, this encapsulates information about the CLI and its state */ get cli() { if (this._cli == void 0) { throw new import_core.ZWaveError("The Z-Wave module is not in CLI mode!", import_core.ZWaveErrorCodes.Driver_NotReady); } return this._cli; } /** Determines which kind of Z-Wave application the driver is currently communicating with */ get mode() { if (this._bootloader) return import_DriverMode.DriverMode.Bootloader; if (this._cli) return import_DriverMode.DriverMode.CLI; if (this._controller) return import_DriverMode.DriverMode.SerialAPI; return import_DriverMode.DriverMode.Unknown; } _recoveryPhase = 0; _securityManager; /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ get securityManager() { return this._securityManager; } _securityManager2; /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ get securityManager2() { return this._securityManager2; } _securityManagerLR; /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ get securityManagerLR() { return this._securityManagerLR; } /** @internal */ getSecurityManager2(destination) { const nodeId = (0, import_typeguards.isArray)(destination) ? destination[0] : destination; const isLongRange = (0, import_core.isLongRangeNodeId)(nodeId); return isLongRange ? this.securityManagerLR : this.securityManager2; } _learnModeAuthenticatedKeyPair; /** @internal */ async getLearnModeAuthenticatedKeyPair() { if (this._learnModeAuthenticatedKeyPair == void 0) { const privateKey = this.cacheGet(import_NetworkCache.cacheKeys.controller.privateKey); if (privateKey) { this._learnModeAuthenticatedKeyPair = await (0, import_core.keyPairFromRawECDHPrivateKey)(privateKey); } else { this._learnModeAuthenticatedKeyPair = await (0, import_core.generateECDHKeyPair)(); this.cacheSet(import_NetworkCache.cacheKeys.controller.privateKey, this._learnModeAuthenticatedKeyPair.privateKey); } } return this._learnModeAuthenticatedKeyPair; } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications. Use `controller.homeId` instead! */ get homeId() { return this.controller.homeId; } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications. Use `controller.ownNodeId` instead! */ get ownNodeId() { return this.controller.ownNodeId; } /** @internal Used for compatibility with the CCAPIHost interface */ getNode(nodeId) { return this.controller.nodes.get(nodeId); } /** @internal Used for compatibility with the CCAPIHost interface */ getNodeOrThrow(nodeId) { return this.controller.nodes.getOrThrow(nodeId); } /** @internal Used for compatibility with the CCAPIHost interface */ getAllNodes() { return [...this.controller.nodes.values()]; } tryGetNode(msg) { const nodeId = msg.getNodeId(); if (nodeId != void 0) return this.controller.nodes.get(nodeId); } tryGetEndpoint(cc) { if (cc.isSinglecast()) { return this.controller.nodes.get(cc.nodeId)?.getEndpoint(cc.endpointIndex); } } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ getValueDB(nodeId) { const node = this.controller.nodes.getOrThrow(nodeId); return node.valueDB; } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ tryGetValueDB(nodeId) { const node = this.controller.nodes.get(nodeId); return node?.valueDB; } getDeviceConfig(nodeId) { return this.controller.nodes.get(nodeId)?.deviceConfig; } lookupManufacturer(manufacturerId) { return this.configManager.lookupManufacturer(manufacturerId); } getHighestSecurityClass(nodeId) { const node = this.controller.nodes.getOrThrow(nodeId); return node.getHighestSecurityClass(); } hasSecurityClass(nodeId, securityClass) { const node = this.controller.nodes.getOrThrow(nodeId); return node.hasSecurityClass(securityClass); } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ setSecurityClass(nodeId, securityClass, granted) { const node = this.controller.nodes.getOrThrow(nodeId); node.setSecurityClass(securityClass, granted); } /** Updates the logging configuration without having to restart the driver. */ updateLogConfig(config) { this._logContainer.updateConfiguration(config); } /** Returns the current logging configuration. */ getLogConfig() { return this._logContainer.getConfiguration(); } /** Updates the preferred sensor scales to use for node queries */ setPreferredScales(scales) { this._options.preferences.scales = (0, import_shared.mergeDeep)(defaultOptions.preferences.scales, scales); } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ getUserPreferences() { return this._options.preferences; } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ getInterviewOptions() { return this._options.interview; } /** * **!!! INTERNAL !!!** * * Not intended to be used by applications */ getRefreshValueTimeouts() { return { refreshValue: this._options.timeouts.refreshValue, refreshValueAfterTransition: this._options.timeouts.refreshValueAfterTransition }; } /** * Enumerates all existing serial ports. * @param local Whether to include local serial ports * @param remote Whether to discover remote serial ports using an mDNS query for the `_zwave._tcp` domain */ static async enumerateSerialPorts({ local = true, remote = true } = {}) { const ret = []; const bindings = ( // oxlint-disable-next-line typescript/ban-ts-comment // @ts-ignore - For some reason, VSCode does not like this import, although tsc is fine with it (await import("#default_bindings/serial")).serial ); if (local && typeof bindings.list === "function") { for (const port of await bindings.list()) { if (port.type === "custom") continue; ret.push(port); } } if (remote) { const ports = await (0, import_mDNSDiscovery.discoverRemoteSerialPorts)(); if (ports) { ret.push(...ports.map((p) => ({ type: "socket", path: p.port }))); } } const portOrder = ["link", "socket", "tty"]; ret.sort((a, b) => { const typeA = portOrder.indexOf(a.type); const typeB = portOrder.indexOf(b.type); if (typeA !== typeB) return typeA - typeB; return a.path.localeCompare(b.path); }); return (0, import_arrays.distinct)(ret.map((p) => p.path)); } /** Updates a subset of the driver options on the fly */ updateOptions(options) { const safeOptions = (0, import_shared.pick)(options, [ "attempts", "disableOptimisticValueUpdate", "emitValueUpdateAfterSetValue", "inclusionUserCallbacks", "joinNetworkUserCallbacks", "interview", "preferences", "vendor" ]); const { logConfig, host, ...rest } = this._options; const newOptions = (0, import_shared.mergeDeep)((0, import_shared.cloneDeep)(rest), safeOptions, true); newOptions.logConfig = logConfig; newOptions.host = host; checkOptions(newOptions); if (options.userAgent && !(0, import_typeguards.isObject)(options.userAgent)) { throw new import_core.ZWaveError(`The userAgent property must be an object!`, import_core.ZWaveErrorCodes.Driver_InvalidOptions); } this._options = newOptions; if (options.logConfig) { this.updateLogConfig(options.logConfig); } if (options.userAgent) { this.updateUserAgent(options.userAgent); } } _options; get options() { return this._options; } /** * The host bindings used to access file system etc. */ // This is set during `start()` and should not be accessed before bindings; _wasStarted = false; _isOpen = false; /** Start the driver */ async start() { if (this.wasDestroyed) { throw new import_core.ZWaveError("The driver was destroyed. Create a new instance and start that one.", import_core.ZWaveErrorCodes.Driver_Destroyed); } if (this._wasStarted) return Promise.resolve(); this._wasStarted = true; this.bindings = { fs: this._options.host?.fs ?? (await import("#default_bindings/fs")).fs, serial: this._options.host?.serial ?? (await import("#default_bindings/serial")).serial, db: this._options.host?.db ?? (await import("#default_bindings/db")).db, log: this._options.host?.log ?? (await import("#default_bindings/log")).log }; this._logContainer = this.bindings.log(this._options.logConfig); this._driverLog = new import_Driver.DriverLogger(this, this._logContainer); this._controllerLog = new import_core.ControllerLogger(this._logContainer); this._configManager = new import_config.ConfigManager({ bindings: this.bindings.fs, logContainer: this._logContainer, deviceConfigPriorityDir: this._options.storage.deviceConfigPriorityDir, deviceConfigExternalDir: this._options.storage.deviceConfigExternalDir }); const spOpenPromise = (0, import_deferred_promise.createDeferredPromise)(); if (this._options.logConfig?.showLogo !== false) { this.driverLog.print(libNameString, "info"); } this.driverLog.print(`version ${libVersion}`, "info"); this.driverLog.print("", "info"); this.driverLog.print("starting driver..."); let binding; if (typeof this.port === "string") { if (typeof this.bindings.serial.createFactoryByPath === "function") { this.driverLog.print(`opening serial port ${this.port}`); binding = await this.bindings.serial.createFactoryByPath(this.port); } else { spOpenPromise.reject(new import_core.ZWaveError("This platform does not support creating a serial connection by path", import_core.ZWaveErrorCodes.Driver_Failed)); void this.destroy(); return; } } else if ((0, import_serial.isZWaveSerialPortImplementation)(this.port)) { this.driverLog.print("opening serial port using the provided custom implementation"); this.driverLog.print("This is deprecated! Switch to the factory pattern instead.", "warn"); binding = (0, import_serial.wrapLegacySerialBinding)(this.port); } else { this.driverLog.print("opening serial port using the provided custom factory"); binding = this.port; } this.serialFactory = new import_serial.ZWaveSerialStreamFactory(binding, this._logContainer); setImmediate(async () => { try { await this.openSerialport(); } catch (e) { spOpenPromise.reject(e); void this.destroy(); return; } this.driverLog.print("serial port opened"); this._isOpen = true; spOpenPromise.resolve(); this._scheduler.start(); if (typeof this._options.testingHooks?.onSerialPortOpen === "function") { await this._options.testingHooks.onSerialPortOpen(this.serial); } if (this._options.testingHooks?.skipFirmwareIdentification) { await this.writeHeader(import_serial.MessageHeaders.NAK); if ((0, import_shared.getenv)("NODE_ENV") !== "test") { await (0, import_async.wait)(1e3); } } else { const mode = await this.detectMode(); if (mode === import_DriverMode.DriverMode.CLI) { this.emit("cli ready"); return; } if (mode === import_DriverMode.DriverMode.Bootloader) { if (this._options.bootloaderMode === "stay") { this.driverLog.print("Controller is in bootloader mode. Staying in bootloader as requested.", "warn"); this.emit("bootloader ready"); return; } this.driverLog.print("Controller is in bootloader, attempting to recover...", "warn"); await this.leaveBootloaderInternal(); await (0, import_async.wait)(1e3); if (this._bootloader) { if (this._options.bootloaderMode === "allow") { this.driverLog.print("Failed to recover from bootloader. Staying in bootloader mode as requested.", "warn"); this.emit("bootloader ready"); } else { void this.destroyWithMessage("Failed to recover from bootloader. Please flash a new firmware to continue..."); } return; } } } try { if (this._options.storage.driver) { await this._options.storage.driver.ensureDir(this.cacheDir); } else { await this.bindings.fs.ensureDir(this.cacheDir); } } catch (e) { let message; if (/\.yarn[/\\]cache[/\\]zwave-js/i.test((0, import_shared.getErrorMessage)(e, true))) { message = `Failed to create the cache directory ${this.cacheDir}. When using Yarn PnP, you need to change the location with the "storage.cacheDir" driver option.`; } else { message = `Failed to create the cache directory ${this.cacheDir}. Please make sure that it is writable or change the location with the "storage.cacheDir" driver option.`; } void this.destroyWithMessage(message); return; } if (this._options.testingHooks?.loadConfiguration !== false) { this.driverLog.print("loading configuration..."); try { await this.configManager.loadAll(); } catch (e) { const message = `Failed to load the configuration: ${(0, import_shared.getErrorMessage)(e)}`; void this.destroyWithMessage(message); return; } } this.driverLog.print("beginning interview..."); try { await this.initializeControllerAndNodes(); } catch (e) { let message; if ((0, import_core.isZWaveError)(e) && e.code === import_core.ZWaveErrorCodes.Controller_MessageDropped) { message = `Failed to initialize the driver, no response from the controller. Are you sure this is a Z-Wave controller?`; } else { message = `Failed to initialize the driver: ${(0, import_shared.getErrorMessage)(e, true)}`; } this.driverLog.print(message, "error"); this.emit("error", new import_core.ZWaveError(message, import_core.ZWaveErrorCodes.Driver_Failed)); void this.destroy(); return; } }); return spOpenPromise; } async detectMode() { const incomingNAK = this.waitForMessageHeader((h) => h === import_serial.MessageHeaders.NAK, 500).then(() => true).catch(() => false); await this.writeHeader(import_serial.MessageHeaders.NAK); if (await incomingNAK) { await this.writeSerial(import_shared.Bytes.from("\n", "ascii")); } await (0, import_async.wait)(500); if (this._cli) return import_DriverMode.DriverMode.CLI; if (this._bootloader) return import_DriverMode.DriverMode.Bootloader; return import_DriverMode.DriverMode.SerialAPI; } _controllerInterviewed = false; _nodesReady = /* @__PURE__ */ new Set(); _nodesReadyEventEmitted = false; _isOpeningSerialPort = false; async openSerialport() { let lastError; this._isOpeningSerialPort = true; for (let attempt = 1; attempt <= this._options.attempts.openSerialPort; attempt++) { try { this.serial = await this.serialFactory.createStream(); void this.handleSerialData(this.serial); if ((0, import_shared.getenv)("NODE_ENV") !== "test") { await (0, import_async.wait)(250); } if (this.serial.isOpen) { this._isOpeningSerialPort = false; return; } } catch (e) { lastError = e; } if (attempt < this._options.attempts.openSerialPort) { await (0, import_async.wait)(1e3); } } this._isOpeningSerialPort = false; const message = `Failed to open the serial port: ${(0, import_shared.getErrorMessage)(lastError)}`; this.driverLog.print(message, "error"); throw new import_core.ZWaveError(message, import_core.ZWaveErrorCodes.Driver_Failed); } /** Indicates whether all nodes are ready, i.e. the "all nodes ready" event has been emitted */ get allNodesReady() { return this._nodesReadyEventEmitted; } getJsonlDBOptions() { const options = { ignoreReadErrors: true, ...import_ThrottlePresets.throttlePresets[this._options.storage.throttle] }; if (this._options.storage.lockDir) { options.lockfile = { directory: this._options.storage.lockDir }; } return options; } async initNetworkCache(homeId) { const options = this.getJsonlDBOptions(); const networkCacheFile = import_pathe.default.join(this.cacheDir, `${homeId.toString(16)}.jsonl`); this._networkCache = this.bindings.db.createInstance(networkCacheFile, { ...options, serializer: import_NetworkCache.serializeNetworkCacheValue, reviver: import_NetworkCache.deserializeNetworkCacheValue }); await this._networkCache.open(); if ((0, import_shared.getenv)("NO_CACHE") === "true") { this._networkCache.clear(); } } async initValueDBs(homeId) { const options = this.getJsonlDBOptions(); const valueDBFile = import_pathe.default.join(this.cacheDir, `${homeId.toString(16)}.values.jsonl`); this._valueDB = this.bindings.db.createInstance(valueDBFile, { ...options, enableTimestamps: true, reviver: /* @__PURE__ */ __name((_key, value) => (0, import_core.deserializeCacheValue)(value), "reviver"), serializer: /* @__PURE__ */ __name((_key, value) => (0, import_core.serializeCacheValue)(value), "serializer") }); await this._valueDB.open(); const metadataDBFile = import_pathe.default.join(this.cacheDir, `${homeId.toString(16)}.metadata.jsonl`); this._metadataDB = this.bindings.db.createInstance(metadataDBFile, options); await this._metadataDB.open(); if ((0, import_shared.getenv)("NO_CACHE") === "true") { this._valueDB.clear(); this._metadataDB.clear(); } } async performCacheMigration() { if (!this._controller || !this.controller.homeId || !this._networkCache || !this._valueDB) { return; } if (this._networkCache.size === 0) { this._networkCache.set("cacheFormat", 1); try { await (0, import_NetworkCache.migrateLegacyNetworkCache)(this.controller.homeId, this._networkCache, this._valueDB, this._options.storage.driver ? wrapLegacyFSDriverForCacheMigrationOnly(this._options.storage.driver) : this.bindings.fs, this.cacheDir); for (const key of this._valueDB.keys()) { if (-1 === key.indexOf(`,"commandClass":-1,`)) { continue; } this._valueDB.delete(key); } } catch (e) { const message = `Migrating the legacy cache file to jsonl failed: ${(0, import_shared.getErrorMessage)(e, true)}`; this.driverLog.print(message, "error"); } } if (this._networkCache.get("cacheFormat") === 1) { for (const key of this._valueDB.keys()) { if (-1 !== key.indexOf(`,"commandClass":128,`) && -1 !== key.indexOf(`,"property":"isLow"`)) { this._valueDB.delete(key); this._metadataDB?.delete(key); } } this._networkCache.set("cacheFormat", 2); } } /** * Initializes the variables for controller and nodes, * adds event handlers and starts the interview process. */ async initializeControllerAndNodes() { if (this._controller) { throw new import_core.ZWaveError("The controller was already initialized!", import_core.ZWaveErrorCodes.Driver_Failed); } this._controller = new import_Controller.ZWaveController(this); this._controller.on("node found", this.onNodeFound.bind(this)).on("node added", this.onNodeAdded.bind(this)).on("node removed", this.onNodeRemoved.bind(this)).on("status changed", this.onControllerStatusChanged.bind(this)).on("network found", this.onNetworkFound.bind(this)).on("network joined", this.onNetworkJoined.bind(this)).on("network left", this.onNetworkLeft.bind(this)); this.initTransactionQueues(); this.initSerialAPIQueue(); if (!this._options.testingHooks?.skipControllerIdentification) { const { nodeIds } = await this.controller.queryCapabilities(); await this.controller.queryAndConfigureRF(); const maySoftReset = this.maySoftReset(); if (this._options.features.softReset && !maySoftReset) { this.driverLog.print(`Soft reset is enabled through config, but this stick does not support it.`, "warn"); this._options.features.softReset = false; } if (maySoftReset) { await this.softResetInternal(false); } let lrNodeIds; if (this.controller.supportsLongRange) { lrNodeIds = (await this.controller.queryLongRangeCapabilities()).lrNodeIds; } await this.controller.trySetNodeIDType(this.controller.support