zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
357 lines (356 loc) • 13.7 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 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