inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
728 lines (674 loc) • 24.6 kB
text/typescript
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;
}