UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

357 lines (356 loc) 13.7 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 Endpoint_exports = {}; __export(Endpoint_exports, { Endpoint: () => Endpoint }); module.exports = __toCommonJS(Endpoint_exports); var import_cc = require("@zwave-js/cc"); var import_ZWavePlusCC = require("@zwave-js/cc/ZWavePlusCC"); var import_core = require("@zwave-js/core"); var import_shared = require("@zwave-js/shared"); var import_NetworkCache = require("../driver/NetworkCache.js"); class Endpoint { static { __name(this, "Endpoint"); } nodeId; driver; index; constructor(nodeId, driver, index, deviceClass, supportedCCs) { this.nodeId = nodeId; this.driver = driver; this.index = index; this._implementedCommandClasses = new import_core.CacheBackedMap(this.driver.networkCache, { prefix: import_NetworkCache.cacheKeys.node(this.nodeId).endpoint(this.index)._ccBaseKey, suffixSerializer: /* @__PURE__ */ __name((cc) => (0, import_shared.num2hex)(cc), "suffixSerializer"), suffixDeserializer: /* @__PURE__ */ __name((key) => { const ccId = parseInt(key, 16); if (ccId in import_core.CommandClasses) return ccId; }, "suffixDeserializer") }); if (deviceClass) this.deviceClass = deviceClass; if (supportedCCs != void 0) { for (const cc of supportedCCs) { if (cc === import_core.CommandClasses.Basic) { continue; } this.addCC(cc, { isSupported: true }); } } } /** Required by {@link IZWaveEndpoint} */ virtual = false; /** * Only used for endpoints which store their device class differently than nodes. * DO NOT ACCESS directly! */ _deviceClass; get deviceClass() { if (this.index > 0) { return this._deviceClass; } else { return this.driver.cacheGet(import_NetworkCache.cacheKeys.node(this.nodeId).deviceClass); } } set deviceClass(deviceClass) { if (this.index > 0) { this._deviceClass = deviceClass; } else { this.driver.cacheSet(import_NetworkCache.cacheKeys.node(this.nodeId).deviceClass, deviceClass); } } /** Can be used to distinguish multiple endpoints of a node */ get endpointLabel() { return this.tryGetNode()?.deviceConfig?.endpoints?.get(this.index)?.label; } /** Resets all stored information of this endpoint */ reset() { this._implementedCommandClasses.clear(); this._commandClassAPIs.clear(); } _implementedCommandClasses; /** * @internal * Information about the implemented Command Classes of this endpoint. */ get implementedCommandClasses() { return this._implementedCommandClasses; } getCCs() { return this._implementedCommandClasses.entries(); } /** * Adds a CC to the list of command classes implemented by the endpoint or updates the information. * You shouldn't need to call this yourself. * @param info The information about the command class. This is merged with existing information. */ addCC(cc, info) { if (this.index > 0 && cc === import_core.CommandClasses["Multi Channel"]) return; const original = this._implementedCommandClasses.get(cc); const updated = Object.assign({}, original ?? { isSupported: false, isControlled: false, secure: false, version: 0 }, info); if (original == void 0 || !(0, import_core.isCCInfoEqual)(original, updated)) { this._implementedCommandClasses.set(cc, updated); } } /** Removes a CC from the list of command classes implemented by the endpoint */ removeCC(cc) { this._implementedCommandClasses.delete(cc); } /** Tests if this endpoint supports the given CommandClass */ supportsCC(cc) { return !!this._implementedCommandClasses.get(cc)?.isSupported; } /** Tests if this endpoint supports or controls the given CC only securely */ isCCSecure(cc) { return !!this._implementedCommandClasses.get(cc)?.secure; } /** Tests if this endpoint controls the given CommandClass */ controlsCC(cc) { return !!this._implementedCommandClasses.get(cc)?.isControlled; } /** * Checks if this endpoint is allowed to support Basic CC per the specification. * This depends on the device type and the other supported CCs */ maySupportBasicCC() { if (import_core.actuatorCCs.some((cc) => this.supportsCC(cc))) { return false; } return this.deviceClass?.specific.maySupportBasicCC ?? this.deviceClass?.generic.maySupportBasicCC ?? true; } /** Determines if support for a CC was force-removed via config file */ wasCCRemovedViaConfig(cc) { if (this.supportsCC(cc)) return false; const compatConfig = this.tryGetNode()?.deviceConfig?.compat; if (!compatConfig) return false; const removedEndpoints = compatConfig.removeCCs?.get(cc); if (!removedEndpoints) return false; return removedEndpoints == "*" || removedEndpoints.includes(this.index); } /** * Determines if support for a CC was force-added via config file. */ wasCCSupportAddedViaConfig(cc) { const compatConfig = this.tryGetNode()?.deviceConfig?.compat; if (!compatConfig) return false; const addedCC = compatConfig.addCCs?.get(cc); if (!addedCC) return false; const endpointInfo = addedCC.endpoints.get(this.index); if (!endpointInfo) return false; return endpointInfo.isSupported === true; } /** * Retrieves the version of the given CommandClass this endpoint implements. * Returns 0 if the CC is not supported. */ getCCVersion(cc) { const ccInfo = this._implementedCommandClasses.get(cc); const ret = ccInfo?.version ?? 0; if (ret === 0 && this.index > 0) { return this.tryGetNode().getCCVersion(cc); } return ret; } /** * Creates an instance of the given CC and links it to this endpoint. * Throws if the CC is neither supported nor controlled by the endpoint. */ createCCInstance(cc) { const ccId = typeof cc === "number" ? cc : (0, import_cc.getCommandClassStatic)(cc); if (!this.supportsCC(ccId) && !this.controlsCC(ccId)) { throw new import_core.ZWaveError(`Cannot create an instance of the unsupported CC ${import_core.CommandClasses[ccId]} (${(0, import_shared.num2hex)(ccId)})`, import_core.ZWaveErrorCodes.CC_NotSupported); } return import_cc.CommandClass.createInstanceUnchecked(this, cc); } /** * Creates an instance of the given CC and links it to this endpoint. * Returns `undefined` if the CC is neither supported nor controlled by the endpoint. */ createCCInstanceUnsafe(cc) { const ccId = typeof cc === "number" ? cc : (0, import_cc.getCommandClassStatic)(cc); if (this.supportsCC(ccId) || this.controlsCC(ccId)) { return import_cc.CommandClass.createInstanceUnchecked(this, cc); } } /** Returns instances for all CCs this endpoint supports, that should be interviewed, and that are implemented in this library */ getSupportedCCInstances() { let supportedCCInstances = [...this.implementedCommandClasses.keys()].filter((cc) => this.supportsCC(cc)).map((cc) => this.createCCInstance(cc)).filter((instance) => !!instance); if (this.index > 0) { supportedCCInstances = supportedCCInstances.filter((instance) => !instance.skipEndpointInterview()); } return supportedCCInstances; } /** Builds the dependency graph used to automatically determine the order of CC interviews */ buildCCInterviewGraph(skipCCs) { const supportedCCs = this.getSupportedCCInstances().map((instance) => instance.ccId).filter((ccId) => !skipCCs.includes(ccId)); const ret = supportedCCs.map((cc) => new import_core.GraphNode(cc)); for (const node of ret) { const instance = this.createCCInstance(node.value); for (const requiredCCId of instance.determineRequiredCCInterviews()) { const requiredCC = ret.find((instance2) => instance2.value === requiredCCId); if (requiredCC) node.edges.add(requiredCC); } } return ret; } /** * @internal * Creates an API instance for a given command class. Throws if no API is defined. * @param ccId The command class to create an API instance for * @param requireSupport Whether accessing the API should throw if it is not supported by the node. */ createAPI(ccId, requireSupport = true) { return import_cc.CCAPI.create(ccId, this.driver, this, requireSupport); } _commandClassAPIs = /* @__PURE__ */ new Map(); _commandClassAPIsProxy = new Proxy(this._commandClassAPIs, { get: /* @__PURE__ */ __name((target, ccNameOrId) => { if (process.env.NODE_ENV === "test" && typeof ccNameOrId === "string" && (ccNameOrId === "$$typeof" || ccNameOrId === "constructor" || ccNameOrId.includes("@@__IMMUTABLE"))) { return void 0; } if (typeof ccNameOrId === "symbol") { if (ccNameOrId === Symbol.iterator) { return this.commandClassesIterator; } else if (ccNameOrId === Symbol.toStringTag) { return "[object Object]"; } return void 0; } else { const ccId = (0, import_cc.normalizeCCNameOrId)(ccNameOrId); if (ccId == void 0) { throw new import_core.ZWaveError(`Command Class ${ccNameOrId} is not implemented!`, import_core.ZWaveErrorCodes.CC_NotImplemented); } if (!target.has(ccId)) { const api = import_cc.CCAPI.create(ccId, this.driver, this); target.set(ccId, api); } return target.get(ccId); } }, "get") }); /** * Used to iterate over the commandClasses API without throwing errors by accessing unsupported CCs */ commandClassesIterator = function* () { for (const cc of this.implementedCommandClasses.keys()) { if (this.supportsCC(cc)) yield this.commandClasses[cc]; } }.bind(this); /** * Provides access to simplified APIs that are tailored to specific CCs. * Make sure to check support of each API using `API.isSupported()` since * all other API calls will throw if the API is not supported */ get commandClasses() { return this._commandClassAPIsProxy; } /** Allows checking whether a CC API is supported before calling it with {@link Endpoint.invokeCCAPI} */ supportsCCAPI(cc) { return this.commandClasses[cc].isSupported(); } /** * Allows dynamically calling any CC API method on this endpoint by CC ID and method name. * Use {@link Endpoint.supportsCCAPI} to check support first. */ invokeCCAPI(cc, method, ...args) { const CCAPI2 = this.commandClasses[cc]; const ccId = (0, import_cc.normalizeCCNameOrId)(cc); const ccName = (0, import_core.getCCName)(ccId); if (!CCAPI2) { throw new import_core.ZWaveError(`The API for the ${ccName} CC does not exist or is not implemented!`, import_core.ZWaveErrorCodes.CC_NoAPI); } const apiMethod = CCAPI2[method]; if (typeof apiMethod !== "function") { throw new import_core.ZWaveError(`Method "${method}" does not exist on the API for the ${ccName} CC!`, import_core.ZWaveErrorCodes.CC_NotImplemented); } return apiMethod.apply(CCAPI2, args); } /** * Returns the node this endpoint belongs to (or undefined if the node doesn't exist) */ tryGetNode() { return this.driver.controller.nodes.get(this.nodeId); } /** Z-Wave+ Icon (for management) */ get installerIcon() { return this.tryGetNode()?.getValue(import_ZWavePlusCC.ZWavePlusCCValues.installerIcon.endpoint(this.index)); } /** Z-Wave+ Icon (for end users) */ get userIcon() { return this.tryGetNode()?.getValue(import_ZWavePlusCC.ZWavePlusCCValues.userIcon.endpoint(this.index)); } /** * @internal * Returns a dump of this endpoint's information for debugging purposes */ createEndpointDump() { const ret = { index: this.index, deviceClass: "unknown", commandClasses: {}, maySupportBasicCC: this.maySupportBasicCC() }; if (this.deviceClass) { ret.deviceClass = { basic: { key: this.deviceClass.basic, label: (0, import_shared.getEnumMemberName)(import_core.BasicDeviceClass, this.deviceClass.basic) }, generic: { key: this.deviceClass.generic.key, label: this.deviceClass.generic.label }, specific: { key: this.deviceClass.specific.key, label: this.deviceClass.specific.label } }; } for (const [ccId, info] of this._implementedCommandClasses) { ret.commandClasses[(0, import_core.getCCName)(ccId)] = { ...info, values: [] }; } for (const [prop, value] of Object.entries(ret)) { if (value === void 0) delete ret[prop]; } return ret; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Endpoint }); //# sourceMappingURL=Endpoint.js.map