UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

333 lines (306 loc) 8.38 kB
import { CommandClass } from "@zwave-js/cc"; import { MultiChannelCCValues } from "@zwave-js/cc/MultiChannelCC"; import { allCCs, applicationCCs, CommandClasses, getCCName, IZWaveEndpoint, IZWaveNode, SetValueOptions, TranslatedValueID, ValueID, ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; import type { ZWaveApplicationHost } from "@zwave-js/host"; function getValue<T>( applHost: ZWaveApplicationHost, node: IZWaveNode, valueId: ValueID, ): T | undefined { return applHost.getValueDB(node.id).getValue(valueId); } function setValue( applHost: ZWaveApplicationHost, node: IZWaveNode, valueId: ValueID, value: unknown, options?: SetValueOptions, ): void { return applHost.getValueDB(node.id).setValue(valueId, value, options); } export function endpointCountIsDynamic( applHost: ZWaveApplicationHost, node: IZWaveNode, ): boolean | undefined { return getValue( applHost, node, MultiChannelCCValues.endpointCountIsDynamic.id, ); } export function endpointsHaveIdenticalCapabilities( applHost: ZWaveApplicationHost, node: IZWaveNode, ): boolean | undefined { return getValue( applHost, node, MultiChannelCCValues.endpointsHaveIdenticalCapabilities.id, ); } export function getIndividualEndpointCount( applHost: ZWaveApplicationHost, node: IZWaveNode, ): number | undefined { return getValue( applHost, node, MultiChannelCCValues.individualEndpointCount.id, ); } export function getAggregatedEndpointCount( applHost: ZWaveApplicationHost, node: IZWaveNode, ): number | undefined { return getValue( applHost, node, MultiChannelCCValues.aggregatedEndpointCount.id, ); } export function getEndpointCount( applHost: ZWaveApplicationHost, node: IZWaveNode, ): number { return ( (getIndividualEndpointCount(applHost, node) || 0) + (getAggregatedEndpointCount(applHost, node) || 0) ); } export function setIndividualEndpointCount( applHost: ZWaveApplicationHost, node: IZWaveNode, count: number, ): void { setValue( applHost, node, MultiChannelCCValues.individualEndpointCount.id, count, ); } export function setAggregatedEndpointCount( applHost: ZWaveApplicationHost, node: IZWaveNode, count: number, ): void { setValue( applHost, node, MultiChannelCCValues.aggregatedEndpointCount.id, count, ); } export function getEndpointIndizes( applHost: ZWaveApplicationHost, node: IZWaveNode, ): number[] { let ret = getValue<number[]>( applHost, node, MultiChannelCCValues.endpointIndizes.id, ); if (!ret) { // Endpoint indizes not stored, assume sequential endpoints ret = []; for (let i = 1; i <= getEndpointCount(applHost, node); i++) { ret.push(i); } } return ret; } export function setEndpointIndizes( applHost: ZWaveApplicationHost, node: IZWaveNode, indizes: number[], ): void { setValue(applHost, node, MultiChannelCCValues.endpointIndizes.id, indizes); } export function isMultiChannelInterviewComplete( applHost: ZWaveApplicationHost, node: IZWaveNode, ): boolean { return !!getValue(applHost, node, { commandClass: CommandClasses["Multi Channel"], endpoint: 0, property: "interviewComplete", }); } export function setMultiChannelInterviewComplete( applHost: ZWaveApplicationHost, node: IZWaveNode, complete: boolean, ): void { setValue( applHost, node, { commandClass: CommandClasses["Multi Channel"], endpoint: 0, property: "interviewComplete", }, complete, ); } export function getAllEndpoints( applHost: ZWaveApplicationHost, node: IZWaveNode, ): IZWaveEndpoint[] { const ret: IZWaveEndpoint[] = [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(applHost, node)) { for (const i of getEndpointIndizes(applHost, node)) { 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( applHost: ZWaveApplicationHost, node: IZWaveNode, ): boolean { // This is not the case when the root values should explicitly be preserved const compatConfig = applHost.getDeviceConfig?.(node.id)?.compat; if (compatConfig?.preserveRootApplicationCCValueIDs) return false; // This is not the case when there are no endpoints const endpointIndizes = getEndpointIndizes(applHost, node); 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<T extends ValueID>( applHost: ZWaveApplicationHost, node: IZWaveNode, valueId: T, ): T & TranslatedValueID { // Try to retrieve the speaking CC name const commandClassName = getCCName(valueId.commandClass); const ret: T & TranslatedValueID = { commandClassName, ...valueId, }; const ccInstance = CommandClass.createInstanceUnchecked( applHost, node, 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( applHost, valueId.property, valueId.propertyKey, ); // Try to retrieve the speaking property key if (valueId.propertyKey != undefined) { const propertyKey = ccInstance.translatePropertyKey( applHost, 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: ValueID[], ): ValueID[] { const shouldHideRootValueID = ( valueId: ValueID, allValueIds: ValueID[], ): boolean => { // 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( applHost: ZWaveApplicationHost, node: IZWaveNode, ): TranslatedValueID[] { let ret: ValueID[] = []; const allowControlled: CommandClasses[] = [ CommandClasses["Scene Activation"], ]; for (const endpoint of getAllEndpoints(applHost, node)) { for (const cc of allCCs) { if ( endpoint.supportsCC(cc) || (endpoint.controlsCC(cc) && allowControlled.includes(cc)) ) { const ccInstance = CommandClass.createInstanceUnchecked( applHost, endpoint, cc, ); if (ccInstance) { ret.push(...ccInstance.getDefinedValueIDs(applHost)); } } } } // 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(applHost, node)) { ret = filterRootApplicationCCValueIDs(ret); } // Translate the remaining value IDs before exposing them to applications return ret.map((id) => translateValueID(applHost, node, id)); }