zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
276 lines (275 loc) • 11.1 kB
JavaScript
;
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