zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
1,105 lines • 273 kB
JavaScript
"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