UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

728 lines (674 loc) 24.6 kB
import { CommandClasses, Duration, isZWaveError, IVirtualEndpoint, IZWaveEndpoint, IZWaveNode, Maybe, NODE_ID_BROADCAST, SendCommandOptions, stripUndefined, SupervisionResult, TXReport, unknownBoolean, ValueChangeOptions, ValueDB, ValueID, ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; import type { ZWaveApplicationHost } from "@zwave-js/host"; import { getEnumMemberName, num2hex, OnlyMethods } from "@zwave-js/shared"; import { isArray } from "alcalzone-shared/typeguards"; import { getAPI, getCommandClass } from "./CommandClassDecorators"; export type ValueIDProperties = Pick<ValueID, "property" | "propertyKey">; /** Used to identify the method on the CC API class that handles setting values on nodes directly */ export const SET_VALUE: unique symbol = Symbol.for("CCAPI_SET_VALUE"); export type SetValueImplementation = ( property: ValueIDProperties, value: unknown, options?: SetValueAPIOptions, ) => Promise<SupervisionResult | undefined>; /** * A generic options bag for the `setValue` API. * Each implementation will choose the options that are relevant for it, so you can use the same options everywhere. * @publicAPI */ export type SetValueAPIOptions = Partial<ValueChangeOptions>; /** Used to identify the method on the CC API class that handles polling values from nodes */ export const POLL_VALUE: unique symbol = Symbol.for("CCAPI_POLL_VALUE"); export type PollValueImplementation<T = unknown> = ( property: ValueIDProperties, ) => Promise<T | undefined>; // Since the setValue API is called from a point with very generic parameters, // we must do narrowing inside the API calls. These three methods are for convenience export function throwUnsupportedProperty( cc: CommandClasses, property: string | number, ): never { throw new ZWaveError( `${CommandClasses[cc]}: "${property}" is not a supported property`, ZWaveErrorCodes.Argument_Invalid, ); } export function throwUnsupportedPropertyKey( cc: CommandClasses, property: string | number, propertyKey: string | number, ): never { throw new ZWaveError( `${CommandClasses[cc]}: "${propertyKey}" is not a supported property key for property "${property}"`, ZWaveErrorCodes.Argument_Invalid, ); } export function throwMissingPropertyKey( cc: CommandClasses, property: string | number, ): never { throw new ZWaveError( `${CommandClasses[cc]}: property "${property}" requires a property key, but none was given`, ZWaveErrorCodes.Argument_Invalid, ); } export function throwWrongValueType( cc: CommandClasses, property: string | number, expectedType: string, receivedType: string, ): never { throw new ZWaveError( `${CommandClasses[cc]}: "${property}" must be of type "${expectedType}", received "${receivedType}"`, ZWaveErrorCodes.Argument_Invalid, ); } export interface SchedulePollOptions { duration?: Duration; transition?: "fast" | "slow"; } /** * The base class for all CC APIs exposed via `Node.commandClasses.<CCName>` * @publicAPI */ export class CCAPI { public constructor( protected readonly applHost: ZWaveApplicationHost, protected readonly endpoint: IZWaveEndpoint | IVirtualEndpoint, ) { this.ccId = getCommandClass(this); } public static create<T extends CommandClasses>( ccId: T, applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint | IVirtualEndpoint, requireSupport?: boolean, ): CommandClasses extends T ? CCAPI : CCToAPI<T> { const APIConstructor = getAPI(ccId); const ccName = CommandClasses[ccId]; if (APIConstructor == undefined) { throw new ZWaveError( `Command Class ${ccName} (${num2hex( ccId, )}) has no associated API!`, ZWaveErrorCodes.CC_NoAPI, ); } const apiInstance = new APIConstructor(applHost, endpoint); // Only require support for physical endpoints by default requireSupport ??= !endpoint.virtual; if (requireSupport) { // @ts-expect-error TS doesn't like assigning to conditional types return new Proxy(apiInstance, { get: (target, property) => { // Forbid access to the API if it is not supported by the node if ( property !== "ccId" && property !== "endpoint" && property !== "isSupported" && property !== "withOptions" && property !== "commandOptions" && !target.isSupported() ) { let messageStart: string; if (endpoint.virtual) { const hasNodeId = typeof endpoint.nodeId === "number"; messageStart = `${ hasNodeId ? "The" : "This" } virtual node${ hasNodeId ? ` ${endpoint.nodeId}` : "" }`; } else { messageStart = `Node ${endpoint.nodeId}`; } throw new ZWaveError( `${messageStart}${ endpoint.index === 0 ? "" : ` (endpoint ${endpoint.index})` } does not support the Command Class ${ccName}!`, ZWaveErrorCodes.CC_NotSupported, ); } return target[property as keyof CCAPI]; }, }); } else { // @ts-expect-error TS doesn't like assigning to conditional types return apiInstance; } } /** * The identifier of the Command Class this API is for */ public readonly ccId: CommandClasses; protected [SET_VALUE]: SetValueImplementation | undefined; /** * Can be used on supported CC APIs to set a CC value by property name (and optionally the property key) */ public get setValue(): SetValueImplementation | undefined { return this[SET_VALUE]; } /** Whether a successful setValue call should imply that the value was successfully updated */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public isSetValueOptimistic(valueId: ValueID): boolean { return true; } protected [POLL_VALUE]: PollValueImplementation | undefined; /** * Can be used on supported CC APIs to poll a CC value by property name (and optionally the property key) */ public get pollValue(): PollValueImplementation | undefined { return this[POLL_VALUE]?.bind(this); } /** * Schedules a value to be polled after a given time. Schedules are deduplicated on a per-property basis. * @returns `true` if the poll was scheduled, `false` otherwise */ protected schedulePoll( property: ValueIDProperties, expectedValue: unknown, { duration, transition = "slow" }: SchedulePollOptions = {}, ): boolean { // Figure out the delay. If a non-zero duration was given or this is a "fast" transition, // use/add the short delay. Otherwise, default to the long delay. const durationMs = duration?.toMilliseconds() ?? 0; const additionalDelay = !!durationMs || transition === "fast" ? this.applHost.options.timeouts.refreshValueAfterTransition : this.applHost.options.timeouts.refreshValue; const timeoutMs = durationMs + additionalDelay; if (this.isSinglecast()) { const node = this.endpoint.getNodeUnsafe(); if (!node) return false; return this.applHost.schedulePoll( node.id, { commandClass: this.ccId, endpoint: this.endpoint.index, ...property, }, { timeoutMs, expectedValue }, ); } else if (this.isMulticast()) { // Only poll supporting nodes in multicast const supportingNodes = this.endpoint.node.physicalNodes.filter( (node) => node .getEndpoint(this.endpoint.index) ?.supportsCC(this.ccId), ); let ret = false; for (const node of supportingNodes) { ret ||= this.applHost.schedulePoll( node.id, { commandClass: this.ccId, endpoint: this.endpoint.index, ...property, }, { timeoutMs, expectedValue }, ); } return ret; } else { // Don't poll the broadcast node return false; } } /** * Retrieves the version of the given CommandClass this endpoint implements */ public get version(): number { return this.endpoint.getCCVersion(this.ccId); } /** Determines if this simplified API instance may be used. */ public isSupported(): boolean { return ( // NoOperation is always supported this.ccId === CommandClasses["No Operation"] || // Basic should always be supported. Since we are trying to hide it from library consumers // we cannot trust supportsCC to test it this.ccId === CommandClasses.Basic || this.endpoint.supportsCC(this.ccId) ); } /** * Determine whether the linked node supports a specific command of this command class. * "unknown" means that the information has not been received yet */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public supportsCommand(command: number): Maybe<boolean> { // This needs to be overwritten per command class. In the default implementation, we don't know anything! return unknownBoolean; } protected assertSupportsCommand( commandEnum: unknown, command: number, ): void { if (this.supportsCommand(command) !== true) { const hasNodeId = typeof this.endpoint.nodeId === "number"; throw new ZWaveError( `${ hasNodeId ? `Node #${this.endpoint.nodeId as number}` : "This virtual node" }${ this.endpoint.index > 0 ? ` (Endpoint ${this.endpoint.index})` : "" } does not support the command ${getEnumMemberName( commandEnum, command, )}!`, ZWaveErrorCodes.CC_NotSupported, ); } } protected assertPhysicalEndpoint( endpoint: IZWaveEndpoint | IVirtualEndpoint, ): asserts endpoint is IZWaveEndpoint { if (endpoint.virtual) { throw new ZWaveError( `This method is not supported for virtual nodes!`, ZWaveErrorCodes.CC_NotSupported, ); } } /** Returns the command options to use for sendCommand calls */ protected get commandOptions(): SendCommandOptions { // No default options return {}; } /** Creates an instance of this API, scoped to use the given options */ public withOptions(options: SendCommandOptions): this { const mergedOptions = { ...this.commandOptions, ...options, }; return new Proxy(this, { get: (target, property) => { if (property === "commandOptions") { return mergedOptions; } else { return (target as any)[property]; } }, }); } /** Creates an instance of this API which (if supported) will return TX reports along with the result. */ public withTXReport<T extends this>(): WithTXReport<T> { if (this.constructor === CCAPI) { throw new ZWaveError( "The withTXReport method may only be called on specific CC API implementations.", ZWaveErrorCodes.Driver_NotSupported, ); } // Remember which properties need to be proxied const ownProps = new Set( Object.getOwnPropertyNames(this.constructor.prototype), ); ownProps.delete("constructor"); function wrapResult<T>(result: T, txReport: TXReport): any { // Both the result and the TX report may be undefined (no response, no support) return stripUndefined({ result, txReport, }); } return new Proxy(this, { get: (target, prop) => { if (prop === "withTXReport") return undefined; let original: any = (target as any)[prop]; if ( ownProps.has(prop as string) && typeof original === "function" ) { // This is a method that only exists in the specific implementation // Wrap each call with its own API proxy, so we don't mix up TX reports let txReport: TXReport; const api = target.withOptions({ onTXReport: (report) => { // Remember the last status report txReport = report; }, }); original = (api as any)[prop].bind(api); // Return a wrapper function that will add the status report after the call is complete return (...args: any) => { let result = original(...args); if (result instanceof Promise) { result = result.then((res) => wrapResult(res, txReport), ); } else { result = wrapResult(result, txReport); } return result; }; } else { return original; } }, }) as any; } protected isSinglecast(): this is this & { endpoint: IZWaveEndpoint } { return ( !this.endpoint.virtual && typeof this.endpoint.nodeId === "number" && this.endpoint.nodeId !== NODE_ID_BROADCAST ); } protected isMulticast(): this is this & { endpoint: IVirtualEndpoint & { nodeId: number[]; }; } { return this.endpoint.virtual && isArray(this.endpoint.nodeId); } protected isBroadcast(): this is this & { endpoint: IVirtualEndpoint & { nodeId: typeof NODE_ID_BROADCAST; }; } { return ( this.endpoint.virtual && this.endpoint.nodeId === NODE_ID_BROADCAST ); } /** * Returns the node this CC API is linked to. Throws if the controller is not yet ready. */ public getNode(): IZWaveNode | undefined { if (this.isSinglecast()) { return this.applHost.nodes.get(this.endpoint.nodeId); } } /** * @internal * Returns the node this CC API is linked to (or undefined if the node doesn't exist) */ public getNodeUnsafe(): IZWaveNode | undefined { try { return this.getNode(); } catch (e) { // This was expected if (isZWaveError(e) && e.code === ZWaveErrorCodes.Driver_NotReady) { return undefined; } // Something else happened throw e; } } /** Returns the value DB for this CC API's node (if it can be safely accessed) */ protected tryGetValueDB(): ValueDB | undefined { if (!this.isSinglecast()) return; try { return this.applHost.getValueDB(this.endpoint.nodeId); } catch { return; } } /** Returns the value DB for this CC's node (or throws if it cannot be accessed) */ protected getValueDB(): ValueDB { if (this.isSinglecast()) { try { return this.applHost.getValueDB(this.endpoint.nodeId); } catch { throw new ZWaveError( "The node for this CC does not exist or the driver is not ready yet", ZWaveErrorCodes.Driver_NotReady, ); } } throw new ZWaveError( "Cannot retrieve the value DB for non-singlecast CCs", ZWaveErrorCodes.CC_NoNodeID, ); } } /** A CC API that is only available for physical endpoints */ export class PhysicalCCAPI extends CCAPI { public constructor( applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint | IVirtualEndpoint, ) { super(applHost, endpoint); this.assertPhysicalEndpoint(endpoint); } protected declare readonly endpoint: IZWaveEndpoint; } export type APIConstructor<T extends CCAPI = CCAPI> = new ( applHost: ZWaveApplicationHost, endpoint: IZWaveEndpoint | IVirtualEndpoint, ) => T; // This type is auto-generated by maintenance/generateCCAPIInterface.ts // Do not edit it by hand or your changes will be lost export type CCToName<CC extends CommandClasses> = [CC] extends [ typeof CommandClasses["Alarm Sensor"], ] ? "Alarm Sensor" : [CC] extends [typeof CommandClasses["Association"]] ? "Association" : [CC] extends [typeof CommandClasses["Association Group Information"]] ? "Association Group Information" : [CC] extends [typeof CommandClasses["Barrier Operator"]] ? "Barrier Operator" : [CC] extends [typeof CommandClasses["Basic"]] ? "Basic" : [CC] extends [typeof CommandClasses["Battery"]] ? "Battery" : [CC] extends [typeof CommandClasses["Binary Sensor"]] ? "Binary Sensor" : [CC] extends [typeof CommandClasses["Binary Switch"]] ? "Binary Switch" : [CC] extends [typeof CommandClasses["CRC-16 Encapsulation"]] ? "CRC-16 Encapsulation" : [CC] extends [typeof CommandClasses["Central Scene"]] ? "Central Scene" : [CC] extends [typeof CommandClasses["Climate Control Schedule"]] ? "Climate Control Schedule" : [CC] extends [typeof CommandClasses["Clock"]] ? "Clock" : [CC] extends [typeof CommandClasses["Color Switch"]] ? "Color Switch" : [CC] extends [typeof CommandClasses["Configuration"]] ? "Configuration" : [CC] extends [typeof CommandClasses["Door Lock"]] ? "Door Lock" : [CC] extends [typeof CommandClasses["Door Lock Logging"]] ? "Door Lock Logging" : [CC] extends [typeof CommandClasses["Entry Control"]] ? "Entry Control" : [CC] extends [typeof CommandClasses["Firmware Update Meta Data"]] ? "Firmware Update Meta Data" : [CC] extends [typeof CommandClasses["Humidity Control Mode"]] ? "Humidity Control Mode" : [CC] extends [typeof CommandClasses["Humidity Control Operating State"]] ? "Humidity Control Operating State" : [CC] extends [typeof CommandClasses["Humidity Control Setpoint"]] ? "Humidity Control Setpoint" : [CC] extends [typeof CommandClasses["Indicator"]] ? "Indicator" : [CC] extends [typeof CommandClasses["Irrigation"]] ? "Irrigation" : [CC] extends [typeof CommandClasses["Language"]] ? "Language" : [CC] extends [typeof CommandClasses["Lock"]] ? "Lock" : [CC] extends [typeof CommandClasses["Manufacturer Proprietary"]] ? "Manufacturer Proprietary" : [CC] extends [typeof CommandClasses["Manufacturer Specific"]] ? "Manufacturer Specific" : [CC] extends [typeof CommandClasses["Meter"]] ? "Meter" : [CC] extends [typeof CommandClasses["Multi Channel Association"]] ? "Multi Channel Association" : [CC] extends [typeof CommandClasses["Multi Channel"]] ? "Multi Channel" : [CC] extends [typeof CommandClasses["Multi Command"]] ? "Multi Command" : [CC] extends [typeof CommandClasses["Multilevel Sensor"]] ? "Multilevel Sensor" : [CC] extends [typeof CommandClasses["Multilevel Switch"]] ? "Multilevel Switch" : [CC] extends [typeof CommandClasses["No Operation"]] ? "No Operation" : [CC] extends [typeof CommandClasses["Node Naming and Location"]] ? "Node Naming and Location" : [CC] extends [typeof CommandClasses["Notification"]] ? "Notification" : [CC] extends [typeof CommandClasses["Powerlevel"]] ? "Powerlevel" : [CC] extends [typeof CommandClasses["Protection"]] ? "Protection" : [CC] extends [typeof CommandClasses["Scene Activation"]] ? "Scene Activation" : [CC] extends [typeof CommandClasses["Scene Actuator Configuration"]] ? "Scene Actuator Configuration" : [CC] extends [typeof CommandClasses["Scene Controller Configuration"]] ? "Scene Controller Configuration" : [CC] extends [typeof CommandClasses["Security 2"]] ? "Security 2" : [CC] extends [typeof CommandClasses["Security"]] ? "Security" : [CC] extends [typeof CommandClasses["Sound Switch"]] ? "Sound Switch" : [CC] extends [typeof CommandClasses["Supervision"]] ? "Supervision" : [CC] extends [typeof CommandClasses["Thermostat Fan Mode"]] ? "Thermostat Fan Mode" : [CC] extends [typeof CommandClasses["Thermostat Fan State"]] ? "Thermostat Fan State" : [CC] extends [typeof CommandClasses["Thermostat Mode"]] ? "Thermostat Mode" : [CC] extends [typeof CommandClasses["Thermostat Operating State"]] ? "Thermostat Operating State" : [CC] extends [typeof CommandClasses["Thermostat Setback"]] ? "Thermostat Setback" : [CC] extends [typeof CommandClasses["Thermostat Setpoint"]] ? "Thermostat Setpoint" : [CC] extends [typeof CommandClasses["Time"]] ? "Time" : [CC] extends [typeof CommandClasses["Time Parameters"]] ? "Time Parameters" : [CC] extends [typeof CommandClasses["User Code"]] ? "User Code" : [CC] extends [typeof CommandClasses["Version"]] ? "Version" : [CC] extends [typeof CommandClasses["Wake Up"]] ? "Wake Up" : [CC] extends [typeof CommandClasses["Z-Wave Plus Info"]] ? "Z-Wave Plus Info" : never; export type CCToAPI<CC extends CommandClasses> = CCToName<CC> extends keyof CCAPIs ? CCAPIs[CCToName<CC>] : never; export type APIMethodsOf<CC extends CommandClasses> = Omit< OnlyMethods<CCToAPI<CC>>, | "isSetValueOptimistic" | "isSupported" | "supportsCommand" | "withOptions" | "withTXReport" >; export type OwnMethodsOf<API extends CCAPI> = Omit< OnlyMethods<API>, keyof OnlyMethods<CCAPI> >; // Wraps the given type in an object that contains a TX report export type WrapWithTXReport<T> = [T] extends [Promise<infer U>] ? Promise<WrapWithTXReport<U>> : [T] extends [void] ? { txReport: TXReport | undefined } : { result: T; txReport: TXReport | undefined }; // Converts the type of the given API implementation so the API methods return an object including the TX report export type WithTXReport<API extends CCAPI> = Omit< API, keyof OwnMethodsOf<API> | "withOptions" | "withTXReport" > & { [K in keyof OwnMethodsOf<API>]: API[K] extends (...args: any[]) => any ? (...args: Parameters<API[K]>) => WrapWithTXReport<ReturnType<API[K]>> : never; }; // This interface is auto-generated by maintenance/generateCCAPIInterface.ts // Do not edit it by hand or your changes will be lost export interface CCAPIs { [Symbol.iterator](): Iterator<CCAPI>; // AUTO GENERATION BELOW "Alarm Sensor": import("../cc/AlarmSensorCC").AlarmSensorCCAPI; Association: import("../cc/AssociationCC").AssociationCCAPI; "Association Group Information": import("../cc/AssociationGroupInfoCC").AssociationGroupInfoCCAPI; "Barrier Operator": import("../cc/BarrierOperatorCC").BarrierOperatorCCAPI; Basic: import("../cc/BasicCC").BasicCCAPI; Battery: import("../cc/BatteryCC").BatteryCCAPI; "Binary Sensor": import("../cc/BinarySensorCC").BinarySensorCCAPI; "Binary Switch": import("../cc/BinarySwitchCC").BinarySwitchCCAPI; "CRC-16 Encapsulation": import("../cc/CRC16CC").CRC16CCAPI; "Central Scene": import("../cc/CentralSceneCC").CentralSceneCCAPI; "Climate Control Schedule": import("../cc/ClimateControlScheduleCC").ClimateControlScheduleCCAPI; Clock: import("../cc/ClockCC").ClockCCAPI; "Color Switch": import("../cc/ColorSwitchCC").ColorSwitchCCAPI; Configuration: import("../cc/ConfigurationCC").ConfigurationCCAPI; "Door Lock": import("../cc/DoorLockCC").DoorLockCCAPI; "Door Lock Logging": import("../cc/DoorLockLoggingCC").DoorLockLoggingCCAPI; "Entry Control": import("../cc/EntryControlCC").EntryControlCCAPI; "Firmware Update Meta Data": import("../cc/FirmwareUpdateMetaDataCC").FirmwareUpdateMetaDataCCAPI; "Humidity Control Mode": import("../cc/HumidityControlModeCC").HumidityControlModeCCAPI; "Humidity Control Operating State": import("../cc/HumidityControlOperatingStateCC").HumidityControlOperatingStateCCAPI; "Humidity Control Setpoint": import("../cc/HumidityControlSetpointCC").HumidityControlSetpointCCAPI; Indicator: import("../cc/IndicatorCC").IndicatorCCAPI; Irrigation: import("../cc/IrrigationCC").IrrigationCCAPI; Language: import("../cc/LanguageCC").LanguageCCAPI; Lock: import("../cc/LockCC").LockCCAPI; "Manufacturer Proprietary": import("../cc/ManufacturerProprietaryCC").ManufacturerProprietaryCCAPI; "Manufacturer Specific": import("../cc/ManufacturerSpecificCC").ManufacturerSpecificCCAPI; Meter: import("../cc/MeterCC").MeterCCAPI; "Multi Channel Association": import("../cc/MultiChannelAssociationCC").MultiChannelAssociationCCAPI; "Multi Channel": import("../cc/MultiChannelCC").MultiChannelCCAPI; "Multi Command": import("../cc/MultiCommandCC").MultiCommandCCAPI; "Multilevel Sensor": import("../cc/MultilevelSensorCC").MultilevelSensorCCAPI; "Multilevel Switch": import("../cc/MultilevelSwitchCC").MultilevelSwitchCCAPI; "No Operation": import("../cc/NoOperationCC").NoOperationCCAPI; "Node Naming and Location": import("../cc/NodeNamingCC").NodeNamingAndLocationCCAPI; Notification: import("../cc/NotificationCC").NotificationCCAPI; Powerlevel: import("../cc/PowerlevelCC").PowerlevelCCAPI; Protection: import("../cc/ProtectionCC").ProtectionCCAPI; "Scene Activation": import("../cc/SceneActivationCC").SceneActivationCCAPI; "Scene Actuator Configuration": import("../cc/SceneActuatorConfigurationCC").SceneActuatorConfigurationCCAPI; "Scene Controller Configuration": import("../cc/SceneControllerConfigurationCC").SceneControllerConfigurationCCAPI; "Security 2": import("../cc/Security2CC").Security2CCAPI; Security: import("../cc/SecurityCC").SecurityCCAPI; "Sound Switch": import("../cc/SoundSwitchCC").SoundSwitchCCAPI; Supervision: import("../cc/SupervisionCC").SupervisionCCAPI; "Thermostat Fan Mode": import("../cc/ThermostatFanModeCC").ThermostatFanModeCCAPI; "Thermostat Fan State": import("../cc/ThermostatFanStateCC").ThermostatFanStateCCAPI; "Thermostat Mode": import("../cc/ThermostatModeCC").ThermostatModeCCAPI; "Thermostat Operating State": import("../cc/ThermostatOperatingStateCC").ThermostatOperatingStateCCAPI; "Thermostat Setback": import("../cc/ThermostatSetbackCC").ThermostatSetbackCCAPI; "Thermostat Setpoint": import("../cc/ThermostatSetpointCC").ThermostatSetpointCCAPI; Time: import("../cc/TimeCC").TimeCCAPI; "Time Parameters": import("../cc/TimeParametersCC").TimeParametersCCAPI; "User Code": import("../cc/UserCodeCC").UserCodeCCAPI; Version: import("../cc/VersionCC").VersionCCAPI; "Wake Up": import("../cc/WakeUpCC").WakeUpCCAPI; "Z-Wave Plus Info": import("../cc/ZWavePlusCC").ZWavePlusCCAPI; }