UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

1,105 lines 273 kB
"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 Controller_exports = {}; __export(Controller_exports, { ZWaveController: () => ZWaveController }); module.exports = __toCommonJS(Controller_exports); var import_tslib = require("tslib"); var import_cc = require("@zwave-js/cc"); var import_core = require("@zwave-js/core"); var import_nvmedit = require("@zwave-js/nvmedit"); var import_serial = require("@zwave-js/serial"); 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_typeguards = require("alcalzone-shared/typeguards"); var import_NetworkCache = require("../driver/NetworkCache.js"); var import_Task = require("../driver/Task.js"); var import_DeviceClass = require("../node/DeviceClass.js"); var import_Node = require("../node/Node.js"); var import_VirtualNode = require("../node/VirtualNode.js"); var import_Types = require("../node/_Types.js"); var import_ControllerStatistics = require("./ControllerStatistics.js"); var import_Features = require("./Features.js"); var import_FirmwareUpdateService = require("./FirmwareUpdateService.js"); var import_Inclusion = require("./Inclusion.js"); var import_NVMIO = require("./NVMIO.js"); var import_NodeInformationFrame = require("./NodeInformationFrame.js"); var import_Proprietary = require("./Proprietary.js"); var import_ProxyInclusionMachine = require("./ProxyInclusionMachine.js"); var import_ZWaveSDKVersions = require("./ZWaveSDKVersions.js"); var import_utils = require("./utils.js"); let ZWaveController = (() => { let _classDecorators = [(0, import_shared.Mixin)([import_ControllerStatistics.ControllerStatisticsHost])]; let _classDescriptor; let _classExtraInitializers = []; let _classThis; let _classSuper = import_shared.TypedEventTarget; var ZWaveController2 = class extends _classSuper { static { __name(this, "ZWaveController"); } static { _classThis = this; } static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; (0, import_tslib.__esDecorate)(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); ZWaveController2 = _classThis = _classDescriptor.value; if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); (0, import_tslib.__runInitializers)(_classThis, _classExtraInitializers); } /** @internal */ constructor(driver) { super(); this.driver = driver; this._nodes = (0, import_shared.createThrowingMap)((nodeId) => { throw new import_core.ZWaveError(`Node ${nodeId} was not found!`, import_core.ZWaveErrorCodes.Controller_NodeNotFound, nodeId); }); driver.registerRequestHandler(import_serial.FunctionType.SetLearnMode, this.handleLearnModeCallback.bind(this)); } driver; _type; get type() { return this._type; } _protocolVersion; get protocolVersion() { return this._protocolVersion; } _sdkVersion; get sdkVersion() { return this._sdkVersion; } _zwaveApiVersion; get zwaveApiVersion() { return this._zwaveApiVersion; } _zwaveChipType; get zwaveChipType() { return this._zwaveChipType; } _homeId; /** A 32bit number identifying the current network */ get homeId() { return this._homeId; } _ownNodeId; /** The ID of the controller in the current network */ get ownNodeId() { return this._ownNodeId; } _dsk; /** * The device specific key (DSK) of the controller in binary format. */ async getDSK() { if (this._dsk == void 0) { const { publicKey } = await this.driver.getLearnModeAuthenticatedKeyPair(); this._dsk = publicKey.subarray(0, 16); } return this._dsk; } /** @deprecated Use {@link role} instead */ get isPrimary() { switch (this.role) { case import_core.NOT_KNOWN: return import_core.NOT_KNOWN; case import_core.ControllerRole.Primary: return true; default: return false; } } // This seems odd, but isPrimary comes from the Serial API init data command, // while isSecondary comes from the GetControllerCapabilities command. They don't really do what their name implies // and sometimes contradict each other... _isPrimary; _isSecondary; _isUsingHomeIdFromOtherNetwork; /** @deprecated Use {@link role} instead */ get isUsingHomeIdFromOtherNetwork() { return this._isUsingHomeIdFromOtherNetwork; } _isSISPresent; get isSISPresent() { return this._isSISPresent; } _wasRealPrimary; /** @deprecated Use {@link role} instead */ get wasRealPrimary() { return this._wasRealPrimary; } _isSIS; get isSIS() { return this._isSIS; } _isSUC; get isSUC() { return this._isSUC; } _noNodesIncluded; _nodeType; get nodeType() { return this._nodeType; } /** Checks if the SDK version is greater than the given one */ sdkVersionGt(version) { return (0, import_core.sdkVersionGt)(this._sdkVersion, version); } /** Checks if the SDK version is greater than or equal to the given one */ sdkVersionGte(version) { return (0, import_core.sdkVersionGte)(this._sdkVersion, version); } /** Checks if the SDK version is lower than the given one */ sdkVersionLt(version) { return (0, import_core.sdkVersionLt)(this._sdkVersion, version); } /** Checks if the SDK version is lower than or equal to the given one */ sdkVersionLte(version) { return (0, import_core.sdkVersionLte)(this._sdkVersion, version); } _manufacturerId; get manufacturerId() { return this._manufacturerId; } _productType; get productType() { return this._productType; } _productId; get productId() { return this._productId; } _firmwareVersion; get firmwareVersion() { return this._firmwareVersion; } _supportedFunctionTypes; get supportedFunctionTypes() { return this._supportedFunctionTypes; } _status = import_core.ControllerStatus.Ready; /** * Which status the controller is believed to be in */ get status() { return this._status; } /** * @internal */ setStatus(newStatus) { if (newStatus === this._status) return; const oldStatus = this._status; this._status = newStatus; if (newStatus === import_core.ControllerStatus.Jammed) { this.driver.controllerLog.print("The controller is jammed", "warn"); } else if (newStatus === import_core.ControllerStatus.Unresponsive) { this.driver.controllerLog.print("The controller is unresponsive", "warn"); } else if (newStatus === import_core.ControllerStatus.Ready) { if (oldStatus === import_core.ControllerStatus.Jammed) { this.driver.controllerLog.print("The controller is no longer jammed", "warn"); } else if (oldStatus === import_core.ControllerStatus.Unresponsive) { this.driver.controllerLog.print("The controller is no longer unresponsive", "warn"); } else { this.driver.controllerLog.print("The controller is ready"); } } this.emit("status changed", newStatus); } /** * Checks if a given Z-Wave function type is supported by this controller. * Returns `NOT_KNOWN`/`undefined` if this information isn't known yet. */ isFunctionSupported(functionType) { if (!this._supportedFunctionTypes) return import_core.NOT_KNOWN; return this._supportedFunctionTypes.includes(functionType); } _supportedSerialAPISetupCommands; get supportedSerialAPISetupCommands() { return this._supportedSerialAPISetupCommands; } /** * Checks if a given Serial API setup command is supported by this controller. * Returns `NOT_KNOWN`/`undefined` if this information isn't known yet. */ isSerialAPISetupCommandSupported(command) { if (!this._supportedSerialAPISetupCommands) return import_core.NOT_KNOWN; return this._supportedSerialAPISetupCommands.includes(command); } /** * Tests if the controller supports a certain feature. * Returns `undefined` if this information isn't known yet. */ supportsFeature(feature) { switch (feature) { case import_Features.ZWaveFeature.SmartStart: return this.sdkVersionGte(import_Features.minFeatureVersions[feature]); } } /** Throws if the controller does not support a certain feature */ assertFeature(feature) { if (!this.supportsFeature(feature)) { throw new import_core.ZWaveError(`The controller does not support the ${(0, import_shared.getEnumMemberName)(import_Features.ZWaveFeature, feature)} feature`, import_core.ZWaveErrorCodes.Controller_NotSupported); } } _sucNodeId; get sucNodeId() { return this._sucNodeId; } _supportsTimers; get supportsTimers() { return this._supportsTimers; } _supportedRegions; /** Which RF regions are supported by the controller, including information about them */ get supportedRegions() { return this._supportedRegions; } _rfRegion; /** Which RF region the controller is currently set to, or `undefined` if it could not be determined (yet). This value is cached and can be changed through {@link setRFRegion}. */ get rfRegion() { return this._rfRegion; } _txPower; /** The transmit power used for Z-Wave mesh, or `undefined` if it could not be determined (yet). This value is cached and can be changed through {@link setPowerlevel}. */ get txPower() { return this._txPower; } _powerlevelCalibration; /** The calibration value for the transmit power, or `undefined` if it could not be determined (yet). This value is cached and can be changed through {@link setPowerlevel}. */ get powerlevelCalibration() { return this._powerlevelCalibration; } _supportsLongRange; /** Whether the controller supports the Z-Wave Long Range protocol */ get supportsLongRange() { return this._supportsLongRange; } _maxLongRangePowerlevel; /** The maximum powerlevel to use for Z-Wave Long Range, or `undefined` if it could not be determined (yet). This value is cached and can be changed through {@link setMaxLongRangePowerlevel}. */ get maxLongRangePowerlevel() { return this._maxLongRangePowerlevel; } _longRangeChannel; /** The channel to use for Z-Wave Long Range, or `undefined` if it could not be determined (yet). This value is cached and can be changed through {@link setLongRangeChannel}. */ get longRangeChannel() { return this._longRangeChannel; } _supportsLongRangeAutoChannelSelection; /** Whether automatic LR channel selection is supported, or `undefined` if it could not be determined (yet). */ get supportsLongRangeAutoChannelSelection() { return this._supportsLongRangeAutoChannelSelection; } _maxPayloadSize; /** The maximum payload size that can be transmitted with a Z-Wave explorer frame */ get maxPayloadSize() { return this._maxPayloadSize; } _maxPayloadSizeLR; /** The maximum payload size that can be transmitted with a Z-Wave Long Range frame */ get maxPayloadSizeLR() { return this._maxPayloadSizeLR; } _nodes; /** A dictionary of the nodes connected to this controller */ get nodes() { return this._nodes; } _nodeIdType = import_core.NodeIDType.Short; /** Whether the controller is configured to use 8 or 16 bit node IDs */ get nodeIdType() { return this._nodeIdType; } /** @internal */ set nodeIdType(value) { this._nodeIdType = value; } /** Returns the node with the given DSK */ getNodeByDSK(dsk) { try { if (typeof dsk === "string") dsk = (0, import_core.dskFromString)(dsk); } catch (e) { if ((0, import_core.isZWaveError)(e) && e.code === import_core.ZWaveErrorCodes.Argument_Invalid) { return void 0; } throw e; } for (const node of this._nodes.values()) { if (node.dsk && import_shared.Bytes.view(node.dsk).equals(dsk)) return node; } } /** Returns the controller node's value DB */ get valueDB() { return this._nodes.get(this._ownNodeId).valueDB; } /** @internal Which associations are currently configured */ get associations() { return this.driver.cacheGet(import_NetworkCache.cacheKeys.controller.associations(1)) ?? []; } /** @internal */ set associations(value) { this.driver.cacheSet(import_NetworkCache.cacheKeys.controller.associations(1), value); } _powerlevel; /** * @internal * Remembers which powerlevel was set by another node. */ get powerlevel() { return this._powerlevel ?? { powerlevel: import_cc.Powerlevel["Normal Power"], until: /* @__PURE__ */ new Date() }; } /** @internal */ set powerlevel(value) { this._powerlevel = value; } /** The role of the controller on the network */ get role() { if (this._wasRealPrimary) return import_core.ControllerRole.Primary; if (this._isPrimary && this._isSIS && this._isSecondary === false) { return import_core.ControllerRole.Primary; } switch (this._isSecondary) { case true: return import_core.ControllerRole.Secondary; case false: return import_core.ControllerRole.Inclusion; default: return import_core.NOT_KNOWN; } } /** Returns whether learn mode may be enabled on this controller */ get isLearnModePermitted() { if (this.role === import_core.ControllerRole.Primary) { return !!this._noNodesIncluded; } else { return this._isSUC === false; } } /** * @internal * Remembers the indicator values set by another node */ indicatorValues = /* @__PURE__ */ new Map(); /** Returns whether the routes are currently being rebuilt for one or more nodes. */ get isRebuildingRoutes() { return !!this.driver.scheduler.findTask(import_utils.isRebuildRoutesTask); } /** * Returns a reference to the (virtual) broadcast node, which allows sending commands to all nodes. * This automatically groups nodes by security class, ignores nodes that cannot be controlled via multicast/broadcast, and will fall back to multicast(s) if necessary. */ getBroadcastNode() { return new import_VirtualNode.VirtualNode(import_core.NODE_ID_BROADCAST, this.driver, this.nodes.values()); } /** * Returns a reference to the (virtual) broadcast node for Z-Wave Long Range, which allows sending commands to all LR nodes. * This automatically groups nodes by security class, ignores nodes that cannot be controlled via multicast/broadcast, and will fall back to multicast(s) if necessary. */ getBroadcastNodeLR() { return new import_VirtualNode.VirtualNode(import_core.NODE_ID_BROADCAST_LR, this.driver, this.nodes.values()); } /** * Creates a virtual node that can be used to send one or more multicast commands to several nodes. * This automatically groups nodes by security class and ignores nodes that cannot be controlled via multicast. */ getMulticastGroup(nodeIDs) { if (nodeIDs.length === 0) { throw new import_core.ZWaveError("Cannot create an empty multicast group", import_core.ZWaveErrorCodes.Argument_Invalid); } const nodes = nodeIDs.map((id) => this._nodes.getOrThrow(id)); return new import_VirtualNode.VirtualNode(void 0, this.driver, nodes); } /** @internal */ get provisioningList() { return this.driver.cacheGet(import_NetworkCache.cacheKeys.controller.provisioningList) ?? []; } set provisioningList(value) { this.driver.cacheSet(import_NetworkCache.cacheKeys.controller.provisioningList, value); } /** @internal Tracks the number of failed SmartStart inclusion attempts per DSK */ _smartStartFailedAttempts = /* @__PURE__ */ new Map(); /** @internal Resets the SmartStart inclusion failure counter for the given DSK */ resetSmartStartFailureCount(dsk) { this._smartStartFailedAttempts.delete(dsk); } /** Adds the given entry (DSK and security classes) to the controller's SmartStart provisioning list or replaces an existing entry */ provisionSmartStartNode(entry) { this.assertFeature(import_Features.ZWaveFeature.SmartStart); (0, import_utils.assertProvisioningEntry)(entry); const provisioningList = [...this.provisioningList]; const index = provisioningList.findIndex((e) => e.dsk === entry.dsk); if (index === -1) { provisioningList.push(entry); } else { provisioningList[index] = entry; } this.provisioningList = provisioningList; this.resetSmartStartFailureCount(entry.dsk); this.autoProvisionSmartStart(); } /** * Removes the given DSK or node ID from the controller's SmartStart provisioning list. * * **Note:** If this entry corresponds to an included node, it will **NOT** be excluded */ unprovisionSmartStartNode(dskOrNodeId) { const provisioningList = [...this.provisioningList]; const entry = this.getProvisioningEntryInternal(dskOrNodeId); if (!entry) return; const index = provisioningList.indexOf(entry); if (index >= 0) { provisioningList.splice(index, 1); this.provisioningList = provisioningList; this.resetSmartStartFailureCount(entry.dsk); this.autoProvisionSmartStart(); } } getProvisioningEntryInternal(dskOrNodeId) { if (typeof dskOrNodeId === "string") { return this.provisioningList.find((e) => e.dsk === dskOrNodeId); } else { let ret = this.provisioningList.find((e) => "nodeId" in e && e.nodeId === dskOrNodeId); if (!ret) { const dsk = this.nodes.get(dskOrNodeId)?.dsk; if (dsk) { ret = this.provisioningList.find((e) => e.dsk === (0, import_core.dskToString)(dsk)); } } return ret; } } /** * Returns the entry for the given DSK or node ID from the controller's SmartStart provisioning list. */ getProvisioningEntry(dskOrNodeId) { const entry = this.getProvisioningEntryInternal(dskOrNodeId); if (entry) { const ret = { ...entry }; const node = typeof dskOrNodeId === "string" ? this.getNodeByDSK(dskOrNodeId) : this.nodes.get(dskOrNodeId); if (node) ret.nodeId = node.id; return ret; } } /** * Returns all entries from the controller's SmartStart provisioning list. */ getProvisioningEntries() { const dskNodeMap = /* @__PURE__ */ new Map(); for (const node of this.nodes.values()) { if (node.dsk) dskNodeMap.set((0, import_core.dskToString)(node.dsk), node.id); } return this.provisioningList.map((e) => { const { dsk, securityClasses, nodeId, ...rest } = e; return { dsk, securityClasses: [...securityClasses], ...dskNodeMap.has(dsk) ? { nodeId: dskNodeMap.get(dsk) } : {}, ...rest }; }); } /** Returns whether the SmartStart provisioning list contains active entries that have not been included yet */ hasPlannedProvisioningEntries() { return this.provisioningList.some((e) => (e.status == void 0 || e.status === import_Inclusion.ProvisioningEntryStatus.Active) && !this.getNodeByDSK(e.dsk)); } /** * @internal * Automatically starts smart start inclusion if there are nodes pending inclusion. */ autoProvisionSmartStart() { if (!this.supportsFeature(import_Features.ZWaveFeature.SmartStart)) return; if (this.hasPlannedProvisioningEntries()) { void this.enableSmartStart().catch(import_shared.noop); } else { void this.disableSmartStart().catch(import_shared.noop); } } /** * @internal * Queries the controller / serial API capabilities. * Returns a list of Z-Wave classic node IDs that are currently in the network. */ async queryCapabilities() { this.driver.controllerLog.print(`querying Serial API capabilities...`); const apiCaps = await this.driver.sendMessage(new import_serialapi.GetSerialApiCapabilitiesRequest(), { supportCheck: false }); this._firmwareVersion = apiCaps.firmwareVersion; this._manufacturerId = apiCaps.manufacturerId; this._productType = apiCaps.productType; this._productId = apiCaps.productId; this._supportedFunctionTypes = apiCaps.supportedFunctionTypes; this.driver.controllerLog.print(`received API capabilities: firmware version: ${this._firmwareVersion} manufacturer ID: ${(0, import_shared.num2hex)(this._manufacturerId)} product type: ${(0, import_shared.num2hex)(this._productType)} product ID: ${(0, import_shared.num2hex)(this._productId)} supported functions: ${this._supportedFunctionTypes.map((fn) => ` \xB7 ${import_serial.FunctionType[fn]} (${(0, import_shared.num2hex)(fn)})`).join("")}`); const initData = await this.getSerialApiInitData(); this.driver.controllerLog.print(`querying version info...`); const version = await this.driver.sendMessage(new import_serialapi.GetControllerVersionRequest(), { supportCheck: false }); this._protocolVersion = version.libraryVersion; this._type = version.controllerType; this.driver.controllerLog.print(`received version info: controller type: ${(0, import_shared.getEnumMemberName)(import_core.ZWaveLibraryTypes, this._type)} library version: ${this._protocolVersion}`); if (this.isFunctionSupported(import_serial.FunctionType.GetProtocolVersion)) { this.driver.controllerLog.print(`querying protocol version info...`); const protocol = await this.driver.sendMessage(new import_serialapi.GetProtocolVersionRequest()); this._protocolVersion = protocol.protocolVersion; let message = `received protocol version info: protocol type: ${(0, import_shared.getEnumMemberName)(import_core.ProtocolType, protocol.protocolType)} protocol version: ${protocol.protocolVersion}`; if (protocol.applicationFrameworkBuildNumber) { message += ` appl. framework build no.: ${protocol.applicationFrameworkBuildNumber}`; } if (protocol.gitCommitHash) { message += ` git commit hash: ${protocol.gitCommitHash}`; } this.driver.controllerLog.print(message); } this._sdkVersion = (0, import_ZWaveSDKVersions.protocolVersionToSDKVersion)(this._protocolVersion); await this.getControllerCapabilities(); if (this.isFunctionSupported(import_serial.FunctionType.SerialAPISetup)) { this.driver.controllerLog.print(`querying serial API setup capabilities...`); const setupCaps = await this.driver.sendMessage(new import_serialapi.SerialAPISetup_GetSupportedCommandsRequest()); this._supportedSerialAPISetupCommands = setupCaps.supportedCommands; this.driver.controllerLog.print(`supported serial API setup commands:${this._supportedSerialAPISetupCommands.map((cmd) => ` \xB7 ${(0, import_shared.getEnumMemberName)(import_serialapi.SerialAPISetupCommand, cmd)}`).join("")}`); } else { this._supportedSerialAPISetupCommands = []; } if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.GetMaximumPayloadSize)) { this.driver.controllerLog.print(`querying max. payload size...`); this._maxPayloadSize = await this.getMaxPayloadSize(); this.driver.controllerLog.print(`maximum payload size: ${this._maxPayloadSize} bytes`); } if (!this.isLongRangeCapable()) { this._supportsLongRange = false; this._supportsLongRangeAutoChannelSelection = false; } this.driver.controllerLog.print(`supported Z-Wave features: ${Object.keys(import_Features.ZWaveFeature).filter((k) => /^\d+$/.test(k)).map((k) => parseInt(k)).filter((feat) => this.supportsFeature(feat)).map((feat) => ` \xB7 ${(0, import_shared.getEnumMemberName)(import_Features.ZWaveFeature, feat)}`).join("")}`); return { nodeIds: initData.nodeIds }; } /** * @internal * Queries the controller's capabilities in regards to Z-Wave Long Range. * Returns the list of Long Range node IDs */ async queryLongRangeCapabilities() { this.driver.controllerLog.print(`querying Z-Wave Long Range capabilities...`); const lrNodeIds = await this.getLongRangeNodes(); if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.GetLongRangeMaximumPayloadSize)) { this._maxPayloadSizeLR = await this.getMaxPayloadSizeLongRange(); } this.driver.controllerLog.print(`received Z-Wave Long Range capabilities: max. payload size: ${this._maxPayloadSizeLR} bytes nodes: ${lrNodeIds.join(", ")}`); return { lrNodeIds }; } isLongRangeCapable() { return this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.SetNodeIDType); } /** * Helper function to determine whether the controller is capable of EU Long Range, * possibly without advertising it */ isEULongRangeCapable() { return this.isLongRangeCapable() && typeof this._zwaveChipType === "string" && (0, import_core.getChipTypeAndVersion)(this._zwaveChipType)?.type === 8 && this.sdkVersionGte("7.22"); } /** Tries to determine the LR capable replacement of the given region. If none is found, the given region is returned. */ tryGetLRCapableRegion(region) { if (this._supportedRegions) { if (this._supportedRegions.get(region)?.supportsLongRange) { return region; } for (const info of this._supportedRegions.values()) { if (info.supportsLongRange && info.includesRegion === region) { return info.region; } } } if (region === import_core.RFRegion.USA && this.isLongRangeCapable()) { return import_core.RFRegion["USA (Long Range)"]; } if (region === import_core.RFRegion.Europe && this.isEULongRangeCapable()) { return import_core.RFRegion["Europe (Long Range)"]; } return region; } /** * @internal * Queries the region and powerlevel settings and configures them if necessary */ async queryAndConfigureRF() { if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.GetSupportedRegions)) { this.driver.controllerLog.print(`Querying supported RF regions and their information...`); const supportedRegions = await this.querySupportedRFRegions().catch(() => []); this._supportedRegions = /* @__PURE__ */ new Map(); for (const region of supportedRegions) { try { const info = await this.queryRFRegionInfo(region); if (info.region === import_core.RFRegion.Unknown) continue; this._supportedRegions.set(region, info); } catch { continue; } } this.driver.controllerLog.print(`supported regions:${[...this._supportedRegions.values()].map((info) => { let ret = ` \xB7 ${(0, import_shared.getEnumMemberName)(import_core.RFRegion, info.region)}`; if (info.includesRegion != void 0) { ret += ` \xB7 superset of ${(0, import_shared.getEnumMemberName)(import_core.RFRegion, info.includesRegion)}`; } if (info.supportsLongRange) { ret += " \xB7 ZWLR"; if (!info.supportsZWave) { ret += " only"; } } return ret; }).join("")}`); } if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.GetRFRegion)) { this.driver.controllerLog.print(`Querying configured RF region...`); const resp = await this.getRFRegion().catch(() => void 0); if (resp != void 0) { this.driver.controllerLog.print(`The controller is using RF region ${(0, import_shared.getEnumMemberName)(import_core.RFRegion, resp)}`); } else { this.driver.controllerLog.print(`Querying the RF region failed!`, "warn"); } } if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.GetPowerlevel)) { this.driver.controllerLog.print(`Querying configured powerlevel...`); const resp = await this.getPowerlevel().catch(() => void 0); if (resp != void 0) { this.driver.controllerLog.print(`The powerlevel is ${resp.powerlevel} dBm (${resp.measured0dBm} dBm calibration)`); } else { this.driver.controllerLog.print(`Querying the powerlevel failed!`, "warn"); } } if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.GetLongRangeMaximumTxPower)) { this.driver.controllerLog.print(`Querying configured max. Long Range powerlevel...`); const resp = await this.getMaxLongRangePowerlevel().catch(() => void 0); if (resp != void 0) { this.driver.controllerLog.print(`The max. LR powerlevel is ${resp.toFixed(1)} dBm`); } else { this.driver.controllerLog.print(`Querying the max. Long Range powerlevel failed!`, "warn"); } } let desiredRFRegion; if (this.driver.options.rf?.region != void 0) { desiredRFRegion = this.driver.options.rf.region; } if (this.driver.options.rf?.preferLRRegion !== false) { desiredRFRegion ??= this.rfRegion; if (desiredRFRegion != void 0) { desiredRFRegion = this.tryGetLRCapableRegion(desiredRFRegion); } } if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.SetRFRegion) && desiredRFRegion != void 0 && this.rfRegion != desiredRFRegion) { this.driver.controllerLog.print(`Current RF region (${(0, import_shared.getEnumMemberName)(import_core.RFRegion, this.rfRegion ?? import_core.RFRegion.Unknown)}) differs from desired region (${(0, import_shared.getEnumMemberName)(import_core.RFRegion, desiredRFRegion)}), configuring it...`); const isRegionActuallyDifferent = this.tryGetLRCapableRegion(this.rfRegion ?? import_core.RFRegion.Unknown) !== this.tryGetLRCapableRegion(desiredRFRegion); const resp = await this.setRFRegionInternal( desiredRFRegion, // Do not soft reset here, we'll do it later false ).catch((e) => e.message); if (resp === true) { this.driver.controllerLog.print(`Changed RF region to ${(0, import_shared.getEnumMemberName)(import_core.RFRegion, desiredRFRegion)}`); } else { this.driver.controllerLog.print(`Changing the RF region failed!${resp ? ` Reason: ${resp}` : ""}`, "warn"); } if (resp === true && isRegionActuallyDifferent) { await this.applyLegalPowerlevelLimits(desiredRFRegion, this.driver.options.rf?.txPower?.powerlevel === "auto", this.driver.options.rf?.maxLongRangePowerlevel === "auto"); } } let desiredTXPowerlevelMesh; let desiredTXPowerlevelLR; if (typeof this.driver.options.rf?.txPower?.powerlevel === "number") { desiredTXPowerlevelMesh = this.driver.options.rf.txPower.powerlevel; } if (typeof this.driver.options.rf?.maxLongRangePowerlevel === "number") { desiredTXPowerlevelLR = this.driver.options.rf.maxLongRangePowerlevel; } if (desiredTXPowerlevelMesh != void 0) { await this.applyDesiredPowerlevelMesh(desiredTXPowerlevelMesh); } if (desiredTXPowerlevelLR != void 0) { await this.applyDesiredPowerlevelLR(desiredTXPowerlevelLR); } if (this.driver.options.rf?.txPower?.powerlevel === "auto" && this._rfRegion != void 0 && this._txPower === 20 && this._maxLongRangePowerlevel === 20) { const legalPowerlevelMesh = (0, import_core.getLegalPowerlevelMesh)(this._rfRegion); const legalPowerlevelLR = (0, import_core.getLegalPowerlevelLR)(this._rfRegion); if (legalPowerlevelMesh != void 0 || legalPowerlevelLR != void 0) { this.driver.controllerLog.print(`The controller is set to incorrect powerlevel defaults, correcting them...`); } if (legalPowerlevelMesh != void 0) { await this.setPowerlevel(legalPowerlevelMesh, this._powerlevelCalibration ?? 0).catch(import_shared.noop); } if (legalPowerlevelLR != void 0) { await this.setMaxLongRangePowerlevel(legalPowerlevelLR).catch(import_shared.noop); } } if (this.isFunctionSupported(import_serial.FunctionType.GetLongRangeChannel)) { this.driver.controllerLog.print(`Querying configured Long Range channel information...`); const resp = await this.getLongRangeChannel().catch(() => void 0); if (resp != void 0) { this.driver.controllerLog.print(`received Z-Wave Long Range channel information: channel: ${resp.channel != void 0 ? (0, import_shared.getEnumMemberName)(import_core.LongRangeChannel, resp.channel) : "(unknown)"} supports auto channel selection: ${resp.supportsAutoChannelSelection} `); } else { this.driver.controllerLog.print(`Querying the Long Range channel information failed!`, "warn"); } } else { this._supportsLongRangeAutoChannelSelection = false; } if (this.isFunctionSupported(import_serial.FunctionType.SetLongRangeChannel) && this.driver.options.rf?.longRangeChannel != void 0 && this.longRangeChannel !== this.driver.options.rf.longRangeChannel) { const desired = this.driver.options.rf.longRangeChannel; if (desired === import_core.LongRangeChannel.Auto && !this._supportsLongRangeAutoChannelSelection) { this.driver.controllerLog.print(`Cannot set desired LR channel to Auto because the controller does not support it!`, "warn"); } else { this.driver.controllerLog.print(`Current LR channel ${this.longRangeChannel != void 0 ? (0, import_shared.getEnumMemberName)(import_core.LongRangeChannel, this.longRangeChannel) : "(unknown)"} differs from desired channel ${(0, import_shared.getEnumMemberName)(import_core.LongRangeChannel, desired)}, configuring it...`); const resp = await this.setLongRangeChannel(desired).catch((e) => e.message); if (resp === true) { this.driver.controllerLog.print(`LR channel updated`); } else { this.driver.controllerLog.print(`Changing the LR channel failed!${resp ? ` Reason: ${resp}` : ""}`, "warn"); } } } } /** * @internal * Queries the home and node id of the controller */ async identify() { this.driver.controllerLog.print(`querying controller IDs...`); const ids = await this.driver.sendMessage(new import_serialapi.GetControllerIdRequest(), { supportCheck: false }); this._homeId = ids.homeId; this._ownNodeId = ids.ownNodeId; this.driver.controllerLog.print(`received controller IDs: home ID: ${(0, import_shared.num2hex)(this._homeId)} own node ID: ${this._ownNodeId}`); } /** * @internal * Performs additional controller configuration */ async configure() { if (this.isSerialAPISetupCommandSupported(import_serialapi.SerialAPISetupCommand.SetTxStatusReport)) { this.driver.controllerLog.print(`Enabling TX status report...`); const resp = await this.driver.sendMessage(new import_serialapi.SerialAPISetup_SetTXStatusReportRequest({ enabled: true })); this.driver.controllerLog.print(`Enabling TX status report ${resp.success ? "successful" : "failed"}...`); } this.driver.controllerLog.print(`finding SUC...`); const suc = await this.driver.sendMessage(new import_serialapi.GetSUCNodeIdRequest(), { supportCheck: false }); this._sucNodeId = suc.sucNodeId; if (this._sucNodeId === 0) { this.driver.controllerLog.print(`No SUC present in the network`); } else if (this._sucNodeId === this._ownNodeId) { this.driver.controllerLog.print(`This is the SUC`); } else { this.driver.controllerLog.print(`SUC has node ID ${this.sucNodeId}`); } if (this.role === import_core.ControllerRole.Primary && this._noNodesIncluded && this._sucNodeId === 0 && !this._isSUC && !this._isSISPresent) { this.driver.controllerLog.print(`There is no SUC/SIS in the network - promoting ourselves...`); try { const result = await this.configureSUC(this._ownNodeId, true, true); if (result) { this._sucNodeId = this._ownNodeId; this._isSUC = true; this._isSISPresent = true; } this.driver.controllerLog.print(`Promotion to SUC/SIS ${result ? "succeeded" : "failed"}.`, result ? void 0 : "warn"); } catch (e) { this.driver.controllerLog.print(`Error while promoting to SUC/SIS: ${(0, import_shared.getErrorMessage)(e)}`, "error"); } } if (this.type !== import_core.ZWaveLibraryTypes["Bridge Controller"] && this.isFunctionSupported(import_serial.FunctionType.SetSerialApiTimeouts)) { const { ack, byte } = this.driver.options.timeouts; this.driver.controllerLog.print(`setting serial API timeouts: ack = ${ack} ms, byte = ${byte} ms`); const resp = await this.driver.sendMessage(new import_serialapi.SetSerialApiTimeoutsRequest({ ackTimeout: ack, byteTimeout: byte })); this.driver.controllerLog.print(`serial API timeouts overwritten. The old values were: ack = ${resp.oldAckTimeout} ms, byte = ${resp.oldByteTimeout} ms`); } } /** @internal */ async interviewProprietary() { for (const impl of Object.values(this.proprietary)) { if (typeof impl.interview === "function") { await impl.interview(); } } } /** @internal */ async handleUnsolictedProprietaryCommand(msg) { for (const impl of Object.values(this.proprietary)) { if (typeof impl.handleUnsolicited === "function") { if (await impl.handleUnsolicited(msg)) { return true; } } } return false; } /** * @internal * Interviews the controller for the necessary information. * @param restoreFromCache Asynchronous callback for the driver to restore the network from cache after nodes are created */ async initNodes(classicNodeIds, lrNodeIds, restoreFromCache) { const valueDBIndexes = (0, import_core.indexDBsByNode)([ this.driver.valueDB, this.driver.metadataDB ]); const nodeIds = [...classicNodeIds]; if (nodeIds.length === 0) { this.driver.controllerLog.print(`Controller reports no nodes in its network. This could be an indication of a corrupted controller memory.`, "warn"); nodeIds.unshift(this._ownNodeId); } nodeIds.push(...lrNodeIds); for (const nodeId of nodeIds) { this._nodes.set(nodeId, new import_Node.ZWaveNode( nodeId, this.driver, void 0, void 0, void 0, // Use the previously created index to avoid doing extra work when creating the value DB this.createValueDBForNode(nodeId, valueDBIndexes.get(nodeId)) )); } await restoreFromCache(); const controllerValueDB = this.valueDB; controllerValueDB.setMetadata(import_cc.ManufacturerSpecificCCValues.manufacturerId.id, import_cc.ManufacturerSpecificCCValues.manufacturerId.meta); controllerValueDB.setMetadata(import_cc.ManufacturerSpecificCCValues.productType.id, import_cc.ManufacturerSpecificCCValues.productType.meta); controllerValueDB.setMetadata(import_cc.ManufacturerSpecificCCValues.productId.id, import_cc.ManufacturerSpecificCCValues.productId.meta); controllerValueDB.setValue(import_cc.ManufacturerSpecificCCValues.manufacturerId.id, this._manufacturerId); controllerValueDB.setValue(import_cc.ManufacturerSpecificCCValues.productType.id, this._productType); controllerValueDB.setValue(import_cc.ManufacturerSpecificCCValues.productId.id, this._productId); controllerValueDB.setMetadata(import_cc.VersionCCValues.firmwareVersions.id, import_cc.VersionCCValues.firmwareVersions.meta); controllerValueDB.setValue(import_cc.VersionCCValues.firmwareVersions.id, [ this._firmwareVersion ]); controllerValueDB.setMetadata(import_cc.VersionCCValues.zWaveProtocolVersion.id, import_cc.VersionCCValues.zWaveProtocolVersion.meta); controllerValueDB.setValue(import_cc.VersionCCValues.zWaveProtocolVersion.id, this._protocolVersion); controllerValueDB.setMetadata(import_cc.VersionCCValues.sdkVersion.id, import_cc.VersionCCValues.sdkVersion.meta); controllerValueDB.setValue(import_cc.VersionCCValues.sdkVersion.id, this._sdkVersion); await this.nodes.get(this._ownNodeId)?.["loadDeviceConfig"](); } createValueDBForNode(nodeId, ownKeys) { return new import_core.ValueDB(nodeId, this.driver.valueDB, this.driver.metadataDB, ownKeys); } /** * Gets the list of long range nodes from the controller. */ async getLongRangeNodes() { const nodeIds = []; if (this.supportsLongRange) { for (let segment = 0; ; segment++) { const nodesResponse = await this.driver.sendMessage(new import_serialapi.GetLongRangeNodesRequest({ segmentNumber: segment })); nodeIds.push(...nodesResponse.nodeIds); if (!nodesResponse.moreNodes) break; } } return nodeIds; } /** * Sets the NIF of the controller to the Gateway device type and to include the CCs supported by Z-Wave JS. * Warning: This only works when followed up by a hard-reset, so don't call this directly * @internal */ async setControllerNIF() { this.driver.controllerLog.print("Updating the controller NIF..."); await this.driver.sendMessage(new import_serialapi.SetApplicationNodeInformationRequest({ isListening: true, ...(0, import_NodeInformationFrame.determineNIF)() })); } /** * Performs a hard reset on the controller. This wipes out all configuration! * Warning: The driver needs to re-interview the controller, so don't call this directly * @internal */ async hardReset() { try { const associations = this.associations; if (associations?.length) { this.driver.controllerLog.print("Notifying associated nodes about reset..."); const nodeIdDestinations = (0, import_arrays.distinct)(associations.map(({ nodeId }) => nodeId)); for (const nodeId of nodeIdDestinations) { const node = this.nodes.get(nodeId); if (!node) continue; await node.sendResetLocallyNotification().catch(import_shared.noop); } } this.driver.controllerLog.print("performing hard reset..."); await this.driver.sendMessage(new import_serialapi.HardResetRequest(), { supportCheck: false }); this.driver.controllerLog.print(`hard reset succeeded`); this._nodes.forEach((node) => node.removeAllListeners()); this._nodes.clear(); } catch (e) { this.driver.controllerLog.print(`hard reset failed: ${(0, import_shared.getErrorMessage)(e)}`, "error"); throw e; } } /** * @internal */ async shutdown() { try { this.driver.controllerLog.print("Shutting down the Z-Wave API..."); const response = await this.driver.sendMessage(new import_serialapi.ShutdownRequest()); if (response.success) { this.driver.controllerLog.print("Z-Wave API was shut down"); } else { this.driver.controllerLog.print("Failed to shut down the Z-Wave API"); } return response.success; } catch (e) { this.driver.controllerLog.print(`shutdown failed: ${(0, import_shared.getErrorMessage)(e)}`, "error"); throw e; } } /** * Starts the hardware watchdog on supporting 700+ series controllers. * Returns whether the operation was successful. */ async startWatchdog() { if (this.sdkVersionGte("7.0") && this.isFunctionSupported(import_serial.FunctionType.StartWatchdog)) { try { this.driver.controllerLog.print("Starting hardware watchdog..."); await this.driver.sendMessage(new import_serialapi.StartWatchdogRequest()); return true; } catch (e) { this.driver.controllerLog.print(`Starting the hardware watchdog failed: ${(0, import_shared.getErrorMessage)(e)}`, "error"); } } return false; } /** * Stops the hardware watchdog on supporting controllers. * Returns whether the operation was successful. */ async stopWatchdog() { if (this.isFunctionSupported(import_serial.FunctionType.StopWatchdog)) { try { this.driver.controllerLog.print("Stopping hardware watchdog..."); await this.driver.sendMessage(new import_serialapi.StopWatchdogRequest()); return true; } catch (e) { this.driver.controllerLog.print(`Stopping the hardware watchdog failed: ${(0, import_shared.getErrorMessage)(e)}`, "error"); } } return false; } _inclusionState = import_Inclusion.InclusionState.Idle; get inclusionState() { return this._inclusionState; } /** @internal */ setInclusionState(state) { if (this._inclusionState === state) return; this._inclusionState = state; this.emit("inclusion state changed", state); if (state === import_Inclusion.InclusionState.Idle && this._smartStartEnabled && this.supportsFeature(import_Features.ZWaveFeature.SmartStart)) { this.enableSmartStart().catch(import_shared.noop); } } _smartStartEnabled = false; /** * Starts the inclusion process of new nodes. * Resolves to true when the process was started, and false if the inclusion was already active. * * @param options Defines the inclusion strategy to use. */ async beginInclusion(options = { strategy: import_Inclusion.InclusionStrategy.Insecure }) { if (this._inclusionState === import_Inclusion.InclusionState.Including || this._inclusionState === import_Inclusion.InclusionState.Excluding || this._inclusionState === import_Inclusion.InclusionState.Busy) { return false; } if (!(options.strategy in import_Inclusion.InclusionStrategy) || options.strategy === import_Inclusion.InclusionStrategy.SmartStart) { throw new import_core.ZWaveError(`Invalid inclusion strategy: ${options.strategy}`, import_core.ZWaveErrorCodes.Argument_Invalid); } const startedPromise = (0, import_deferred_promise.createDeferredPromise)(); void this.driver.scheduler.queueTask(this.getBeginClassicInclusionTask(startedPromise, options)).catch(import_shared.noop); await startedPromise; return true; } /** * Returns the task to handle the complete classic inclusion process */ getBeginClassicInclusionTask(startedPromise, options) { const self = this; const abortWaiting = new AbortController(); return { priority: import_Task.TaskPriority.Normal, tag: { id: "inclusion" }, group: { id: "inclusion-exclusion" }, task: /* @__PURE__ */ __name(async function* classicInclusionTask() { yield* (0, import_waddle.waitFor)(self.pauseSmartStart()); self.setInclusionState(import_Inclusion.InclusionState.Including); self.driver.controllerLog.print(`Starting inclusion process with strategy ${(0, import_shared.getEnumMemberName)(im