UNPKG

zwave-js

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

236 lines 10.5 kB
import { CommandClass, EntryControlCCValues, NotificationCCValues, entryControlEventTypeLabels, } from "@zwave-js/cc"; import { MultiChannelCCValues } from "@zwave-js/cc/MultiChannelCC"; import { CommandClasses, ZWaveError, ZWaveErrorCodes, allCCs, applicationCCs, getCCName, getNotification, } from "@zwave-js/core"; function getValue(ctx, nodeId, valueId) { return ctx.getValueDB(nodeId).getValue(valueId); } function setValue(ctx, nodeId, valueId, value, options) { return ctx.getValueDB(nodeId).setValue(valueId, value, options); } export function endpointCountIsDynamic(ctx, nodeId) { return getValue(ctx, nodeId, MultiChannelCCValues.endpointCountIsDynamic.id); } export function endpointsHaveIdenticalCapabilities(ctx, nodeId) { return getValue(ctx, nodeId, MultiChannelCCValues.endpointsHaveIdenticalCapabilities.id); } export function getIndividualEndpointCount(ctx, nodeId) { return getValue(ctx, nodeId, MultiChannelCCValues.individualEndpointCount.id); } export function getAggregatedEndpointCount(ctx, nodeId) { return getValue(ctx, nodeId, MultiChannelCCValues.aggregatedEndpointCount.id); } export function getEndpointCount(ctx, nodeId) { return ((getIndividualEndpointCount(ctx, nodeId) || 0) + (getAggregatedEndpointCount(ctx, nodeId) || 0)); } export function setIndividualEndpointCount(ctx, nodeId, count) { setValue(ctx, nodeId, MultiChannelCCValues.individualEndpointCount.id, count); } export function setAggregatedEndpointCount(ctx, nodeId, count) { setValue(ctx, nodeId, MultiChannelCCValues.aggregatedEndpointCount.id, count); } export function getEndpointIndizes(ctx, nodeId) { let ret = getValue(ctx, nodeId, MultiChannelCCValues.endpointIndizes.id); if (!ret) { // Endpoint indizes not stored, assume sequential endpoints ret = []; for (let i = 1; i <= getEndpointCount(ctx, nodeId); i++) { ret.push(i); } } return ret; } export function setEndpointIndizes(ctx, nodeId, indizes) { setValue(ctx, nodeId, MultiChannelCCValues.endpointIndizes.id, indizes); } export function isMultiChannelInterviewComplete(ctx, nodeId) { return !!getValue(ctx, nodeId, { commandClass: CommandClasses["Multi Channel"], endpoint: 0, property: "interviewComplete", }); } export function setMultiChannelInterviewComplete(ctx, nodeId, complete) { setValue(ctx, nodeId, { commandClass: CommandClasses["Multi Channel"], endpoint: 0, property: "interviewComplete", }, complete); } export function getAllEndpoints(ctx, node) { const ret = [node]; // Check if the Multi Channel CC interview for this node is completed, // because we don't have all the endpoint information before that if (isMultiChannelInterviewComplete(ctx, node.nodeId)) { for (const i of getEndpointIndizes(ctx, node.nodeId)) { const endpoint = node.getEndpoint(i); if (endpoint) ret.push(endpoint); } } return ret; } /** Determines whether the root application CC values should be hidden in favor of endpoint values */ export function shouldHideRootApplicationCCValues(ctx, nodeId) { // This is not the case when the root values should explicitly be preserved const compatConfig = ctx.getDeviceConfig?.(nodeId)?.compat; if (compatConfig?.preserveRootApplicationCCValueIDs) return false; // This is not the case when there are no endpoints const endpointIndizes = getEndpointIndizes(ctx, nodeId); if (endpointIndizes.length === 0) return false; // This is not the case when only individual endpoints should be preserved in addition to the root const preserveEndpoints = compatConfig?.preserveEndpoints; if (preserveEndpoints != undefined && preserveEndpoints !== "*" && preserveEndpoints.length !== endpointIndizes.length) { return false; } // Otherwise they should be hidden return true; } /** * Enhances a value id so it can be consumed better by applications */ export function translateValueID(ctx, endpoint, valueId) { // Try to retrieve the speaking CC name const commandClassName = getCCName(valueId.commandClass); const ret = { commandClassName, ...valueId, }; const ccInstance = CommandClass.createInstanceUnchecked(endpoint, valueId.commandClass); if (!ccInstance) { throw new ZWaveError(`Cannot translate a value ID for the non-implemented CC ${getCCName(valueId.commandClass)}`, ZWaveErrorCodes.CC_NotImplemented); } // Retrieve the speaking property name ret.propertyName = ccInstance.translateProperty(ctx, valueId.property, valueId.propertyKey); // Try to retrieve the speaking property key if (valueId.propertyKey != undefined) { const propertyKey = ccInstance.translatePropertyKey(ctx, valueId.property, valueId.propertyKey); ret.propertyKeyName = propertyKey; } return ret; } /** * Removes all Value IDs from an array that belong to a root endpoint and have a corresponding * Value ID on a non-root endpoint */ export function filterRootApplicationCCValueIDs(allValueIds) { const shouldHideRootValueID = (valueId, allValueIds) => { // Non-root endpoint values don't need to be filtered if (!!valueId.endpoint) return false; // Non-application CCs don't need to be filtered if (!applicationCCs.includes(valueId.commandClass)) return false; // Filter out root values if an identical value ID exists for another endpoint const valueExistsOnAnotherEndpoint = allValueIds.some((other) => // same CC other.commandClass === valueId.commandClass // non-root endpoint && !!other.endpoint // same property and key && other.property === valueId.property && other.propertyKey === valueId.propertyKey); return valueExistsOnAnotherEndpoint; }; return allValueIds.filter((vid) => !shouldHideRootValueID(vid, allValueIds)); } /** Returns a list of all value names that are defined on all endpoints of this node */ export function getDefinedValueIDs(ctx, node) { return getDefinedValueIDsInternal(ctx, node, false); } /** * @internal * Returns a list of all value names that are defined on all endpoints of this node */ export function getDefinedValueIDsInternal(ctx, node, includeInternal = false) { // The controller has no values. Even if some ended up in the cache somehow, do not return any. if (node.id === ctx.ownNodeId) return []; let ret = []; const allowControlled = new Set([ CommandClasses["Scene Activation"], ]); for (const endpoint of getAllEndpoints(ctx, node)) { for (const cc of allCCs) { if ( // Create values only for supported CCs endpoint.supportsCC(cc) // ...and some controlled CCs || (endpoint.controlsCC(cc) && allowControlled.has(cc)) // ...and possibly Basic CC, which has some extra checks to know // whether values should be exposed || cc === CommandClasses.Basic) { const ccInstance = CommandClass.createInstanceUnchecked(endpoint, cc); if (ccInstance) { ret.push(...ccInstance.getDefinedValueIDs(ctx, includeInternal)); } } } } // Application command classes of the Root Device capabilities that are also advertised by at // least one End Point SHOULD be filtered out by controlling nodes before presenting the functionalities // via service discovery mechanisms like mDNS or to users in a GUI. // We do this when there are endpoints that were explicitly preserved if (shouldHideRootApplicationCCValues(ctx, node.id)) { ret = filterRootApplicationCCValueIDs(ret); } // Translate the remaining value IDs before exposing them to applications return ret.map((id) => translateValueID(ctx, node, id)); } export function getSupportedNotificationEvents(ctx, node) { const ret = []; const valueDB = ctx.getValueDB(node.id); for (const endpoint of getAllEndpoints(ctx, node)) { // This list is hardcoded since there is just a small list of CCs // that can send notifications if (endpoint.supportsCC(CommandClasses["Entry Control"])) { const eventTypes = valueDB.getValue(EntryControlCCValues.supportedEventTypes.endpoint(endpoint.index)); if (eventTypes) { const capability = { commandClass: CommandClasses["Entry Control"], endpoint: endpoint.index, supportedEventTypes: Object.fromEntries(eventTypes.map((et) => [ et, entryControlEventTypeLabels[et], ])), }; ret.push(capability); } } if (endpoint.supportsCC(CommandClasses.Notification)) { const notificationTypes = valueDB.getValue(NotificationCCValues.supportedNotificationTypes.endpoint(endpoint.index)) ?? []; const capability = { commandClass: CommandClasses.Notification, endpoint: endpoint.index, supportedNotificationTypes: {}, }; let hasEvent = false; for (const notificationType of notificationTypes) { const notification = getNotification(notificationType); if (!notification) continue; const notificationEvents = valueDB.getValue(NotificationCCValues .supportedNotificationEvents(notificationType) .endpoint(endpoint.index)) ?.map((e) => notification.events.get(e)) .filter((e) => e != undefined); if (!notificationEvents || notificationEvents.length === 0) { continue; } capability.supportedNotificationTypes[notificationType] = { label: notification.name, supportedEvents: Object.fromEntries(notificationEvents.map((e) => [e.value, e.label])), }; hasEvent = true; } if (hasEvent) ret.push(capability); } } return ret; } //# sourceMappingURL=utils.js.map