UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

276 lines (275 loc) 11.1 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 VirtualNode_exports = {}; __export(VirtualNode_exports, { CommunicationProfile: () => CommunicationProfile, VirtualNode: () => VirtualNode, getCommunicationProfile: () => getCommunicationProfile, getSecurityClassFromCommunicationProfile: () => getSecurityClassFromCommunicationProfile }); module.exports = __toCommonJS(VirtualNode_exports); var import_cc = require("@zwave-js/cc"); var import_core = require("@zwave-js/core"); var import_arrays = require("alcalzone-shared/arrays"); var import_VirtualEndpoint = require("./VirtualEndpoint.js"); var CommunicationProfile; (function(CommunicationProfile2) { CommunicationProfile2[CommunicationProfile2["Mesh_S2_Unauthenticated"] = 0] = "Mesh_S2_Unauthenticated"; CommunicationProfile2[CommunicationProfile2["Mesh_S2_Authenticated"] = 1] = "Mesh_S2_Authenticated"; CommunicationProfile2[CommunicationProfile2["Mesh_S2_AccessControl"] = 2] = "Mesh_S2_AccessControl"; CommunicationProfile2[CommunicationProfile2["Mesh_S0_Legacy"] = 7] = "Mesh_S0_Legacy"; CommunicationProfile2[CommunicationProfile2["LR_S2_Authenticated"] = 17] = "LR_S2_Authenticated"; CommunicationProfile2[CommunicationProfile2["LR_S2_AccessControl"] = 18] = "LR_S2_AccessControl"; })(CommunicationProfile || (CommunicationProfile = {})); function getCommunicationProfile(protocol, securityClass) { return protocol << 4 | securityClass & 15; } __name(getCommunicationProfile, "getCommunicationProfile"); function getSecurityClassFromCommunicationProfile(profile) { return profile & 15; } __name(getSecurityClassFromCommunicationProfile, "getSecurityClassFromCommunicationProfile"); function groupNodesByCommunicationProfile(nodes) { const ret = /* @__PURE__ */ new Map(); for (const node of nodes) { const secClass = node.getHighestSecurityClass(); if (secClass === import_core.SecurityClass.Temporary || secClass == void 0) { continue; } const profile = getCommunicationProfile(node.protocol, secClass); if (!ret.has(profile)) { ret.set(profile, []); } ret.get(profile).push(node); } return ret; } __name(groupNodesByCommunicationProfile, "groupNodesByCommunicationProfile"); class VirtualNode extends import_VirtualEndpoint.VirtualEndpoint { static { __name(this, "VirtualNode"); } id; constructor(id, driver, physicalNodes) { super(void 0, driver, 0); this.id = id; super.setNode(this); this.physicalNodes = [...physicalNodes].filter((n) => ( // And avoid including the controller node in the support checks n.id !== driver.controller.ownNodeId && n.getHighestSecurityClass() !== import_core.SecurityClass.S0_Legacy )); this.nodesByCommunicationProfile = groupNodesByCommunicationProfile(this.physicalNodes); if (this.hasMixedCommunicationProfiles) this.id = void 0; } physicalNodes; nodesByCommunicationProfile; get hasMixedCommunicationProfiles() { return this.nodesByCommunicationProfile.size > 1; } /** * Updates a value for a given property of a given CommandClass. * This will communicate with the physical node(s) this virtual node represents! */ async setValue(valueId, value, options) { valueId = (0, import_core.normalizeValueID)(valueId); try { const endpointInstance = this.getEndpoint(valueId.endpoint || 0); if (!endpointInstance) { return { status: import_cc.SetValueStatus.EndpointNotFound, message: `Endpoint ${valueId.endpoint} does not exist on virtual node ${this.id ?? "??"}` }; } let api = endpointInstance.commandClasses[valueId.commandClass]; if (!api.setValue) { return { status: import_cc.SetValueStatus.NotImplemented, message: `The ${(0, import_core.getCCName)(valueId.commandClass)} CC does not support setting values` }; } const valueIdProps = { property: valueId.property, propertyKey: valueId.propertyKey }; const hooks = api.setValueHooks?.(valueIdProps, value, options); if (hooks?.supervisionDelayedUpdates) { api = api.withOptions({ requestStatusUpdates: true, onUpdate: /* @__PURE__ */ __name(async (update) => { try { if (update.status === import_core.SupervisionStatus.Success) { await hooks.supervisionOnSuccess(); } else if (update.status === import_core.SupervisionStatus.Fail) { await hooks.supervisionOnFailure(); } } catch { } }, "onUpdate") }); } if (typeof options?.onProgress === "function") { api = api.withOptions({ onProgress: options.onProgress }); } const result = await api.setValue.call(api, valueIdProps, value, options); if (api.isSetValueOptimistic(valueId)) { const affectedNodes = this.physicalNodes.filter((node) => node.getEndpoint(endpointInstance.index)?.supportsCC(valueId.commandClass)); for (const node of affectedNodes) { node.valueDB.setValue(valueId, value); } } if (hooks) { const supervisedAndSuccessful = (0, import_core.isSupervisionResult)(result) && result.status === import_core.SupervisionStatus.Success; const shouldUpdateOptimistically = api.isSetValueOptimistic(valueId) && (supervisedAndSuccessful || !this.driver.options.disableOptimisticValueUpdate && result == void 0); if (shouldUpdateOptimistically) { hooks.optimisticallyUpdateRelatedValues?.(supervisedAndSuccessful); } if (!(0, import_core.supervisedCommandSucceeded)(result) || hooks.forceVerifyChanges?.()) { await hooks.verifyChanges?.(result); } } return (0, import_cc.supervisionResultToSetValueResult)(result); } catch (e) { if ((0, import_core.isZWaveError)(e)) { let result; switch (e.code) { // This CC or API is not implemented case import_core.ZWaveErrorCodes.CC_NotImplemented: case import_core.ZWaveErrorCodes.CC_NoAPI: result = { status: import_cc.SetValueStatus.NotImplemented, message: e.message }; break; // A user tried to set an invalid value case import_core.ZWaveErrorCodes.Argument_Invalid: result = { status: import_cc.SetValueStatus.InvalidValue, message: e.message }; break; } if (result) return result; } throw e; } } /** * Returns a list of all value IDs and their metadata that can be used to * control the physical node(s) this virtual node represents. */ getDefinedValueIDs() { const ret = /* @__PURE__ */ new Map(); for (const pNode of this.physicalNodes) { const valueIDs = pNode.getDefinedValueIDs().filter((v) => import_core.actuatorCCs.includes(v.commandClass)); for (const valueId of valueIDs) { const mapKey = (0, import_core.valueIdToString)(valueId); const ccVersion = pNode.getCCVersion(valueId.commandClass); const metadata = pNode.getValueMetadata(valueId); if (!metadata.writeable) continue; const needsUpdate = !ret.has(mapKey) || ret.get(mapKey).ccVersion < ccVersion; if (needsUpdate) { ret.set(mapKey, { ...valueId, ccVersion, metadata: { ...metadata, // Metadata of virtual nodes is only writable readable: false } }); } } } const exposedEndpoints = (0, import_arrays.distinct)([...ret.values()].map((v) => v.endpoint).filter((e) => e !== void 0)); for (const endpoint of exposedEndpoints) { const valueId = { ...import_cc.BasicCCValues.targetValue.endpoint(endpoint), commandClassName: "Basic", propertyName: "Target value" }; const ccVersion = 1; const metadata = { ...import_cc.BasicCCValues.targetValue.meta, readable: false }; ret.set((0, import_core.valueIdToString)(valueId), { ...valueId, ccVersion, metadata }); } return [...ret.values()]; } /** Cache for this node's endpoint instances */ _endpointInstances = /* @__PURE__ */ new Map(); getEndpoint(index) { if (index < 0) { throw new import_core.ZWaveError("The endpoint index must be positive!", import_core.ZWaveErrorCodes.Argument_Invalid); } if (!index) return this; if (!this.isMultiChannelInterviewComplete) { this.driver.driverLog.print(`Virtual node ${this.id ?? "??"}, Endpoint ${index}: Trying to access endpoint instance before the Multi Channel interview of all nodes was completed!`, "error"); return void 0; } if (index > this.getEndpointCount()) return void 0; if (!this._endpointInstances.has(index)) { this._endpointInstances.set(index, new import_VirtualEndpoint.VirtualEndpoint(this, this.driver, index)); } return this._endpointInstances.get(index); } getEndpointOrThrow(index) { const ret = this.getEndpoint(index); if (!ret) { throw new import_core.ZWaveError(`Endpoint ${index} does not exist on virtual node ${this.id ?? "??"}`, import_core.ZWaveErrorCodes.Controller_EndpointNotFound); } return ret; } /** Returns the current endpoint count of this virtual node (the maximum in the list of physical nodes) */ getEndpointCount() { let ret = 0; for (const node of this.physicalNodes) { const count = node.getEndpointCount(); ret = Math.max(ret, count); } return ret; } get isMultiChannelInterviewComplete() { for (const node of this.physicalNodes) { if (!node["isMultiChannelInterviewComplete"]) return false; } return true; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { CommunicationProfile, VirtualNode, getCommunicationProfile, getSecurityClassFromCommunicationProfile }); //# sourceMappingURL=VirtualNode.js.map