diff --git a/packages/testing/src/CCSpecificCapabilities.ts b/packages/testing/src/CCSpecificCapabilities.ts index 789891a26925..0234632eedc9 100644 --- a/packages/testing/src/CCSpecificCapabilities.ts +++ b/packages/testing/src/CCSpecificCapabilities.ts @@ -28,6 +28,28 @@ export interface NotificationCCCapabilities { notificationTypesAndEvents: Record; } +export interface MeterCCCapabilities { + meterType: number; + supportedScales: number[]; + supportedRateTypes: number[]; + supportsReset: boolean; + getValue?: ( + scale: number, + rateType: number, + ) => number | { + value: number; + deltaTime: number; + prevValue?: number; + } | undefined; + onReset?: ( + options?: { + scale: number; + rateType: number; + targetValue: number; + }, + ) => void; +} + export interface MultilevelSensorCCCapabilities { sensors: Record = diff --git a/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts b/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts index 4efa04e44e99..2d00a2098e23 100644 --- a/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts +++ b/packages/zwave-js/src/lib/node/MockNodeBehaviors.ts @@ -31,6 +31,7 @@ import { BasicCCBehaviors } from "./mockCCBehaviors/Basic"; import { ConfigurationCCBehaviors } from "./mockCCBehaviors/Configuration"; import { EnergyProductionCCBehaviors } from "./mockCCBehaviors/EnergyProduction"; import { ManufacturerSpecificCCBehaviors } from "./mockCCBehaviors/ManufacturerSpecific"; +import { MeterCCBehaviors } from "./mockCCBehaviors/Meter"; import { MultilevelSensorCCBehaviors } from "./mockCCBehaviors/MultilevelSensor"; import { NotificationCCBehaviors } from "./mockCCBehaviors/Notification"; import { ScheduleEntryLockCCBehaviors } from "./mockCCBehaviors/ScheduleEntryLock"; @@ -294,6 +295,7 @@ export function createDefaultBehaviors(): MockNodeBehavior[] { ...ConfigurationCCBehaviors, ...EnergyProductionCCBehaviors, ...ManufacturerSpecificCCBehaviors, + ...MeterCCBehaviors, ...MultilevelSensorCCBehaviors, ...NotificationCCBehaviors, ...ScheduleEntryLockCCBehaviors, diff --git a/packages/zwave-js/src/lib/node/mockCCBehaviors/Meter.ts b/packages/zwave-js/src/lib/node/mockCCBehaviors/Meter.ts new file mode 100644 index 000000000000..0528c811e40f --- /dev/null +++ b/packages/zwave-js/src/lib/node/mockCCBehaviors/Meter.ts @@ -0,0 +1,143 @@ +import { + MeterCCGet, + MeterCCReport, + MeterCCReset, + MeterCCSupportedGet, + MeterCCSupportedReport, + RateType, +} from "@zwave-js/cc"; +import { CommandClasses } from "@zwave-js/core"; +import { + type MeterCCCapabilities, + type MockNodeBehavior, + MockZWaveFrameType, + createMockZWaveRequestFrame, +} from "@zwave-js/testing"; + +export const defaultCapabilities: MeterCCCapabilities = { + meterType: 0x01, // Electric + supportedScales: [0x00], // kWh + supportedRateTypes: [RateType.Consumed], + supportsReset: true, +}; + +const respondToMeterSupportedGet: MockNodeBehavior = { + async onControllerFrame(controller, self, frame) { + if ( + frame.type === MockZWaveFrameType.Request + && frame.payload instanceof MeterCCSupportedGet + ) { + const capabilities = { + ...defaultCapabilities, + ...self.getCCCapabilities( + CommandClasses.Meter, + frame.payload.endpointIndex, + ), + }; + const cc = new MeterCCSupportedReport(self.host, { + nodeId: controller.host.ownNodeId, + type: capabilities.meterType, + supportedScales: capabilities.supportedScales, + supportedRateTypes: capabilities.supportedRateTypes, + supportsReset: capabilities.supportsReset, + }); + await self.sendToController( + createMockZWaveRequestFrame(cc, { + ackRequested: false, + }), + ); + return true; + } + return false; + }, +}; + +const respondToMeterGet: MockNodeBehavior = { + async onControllerFrame(controller, self, frame) { + if ( + frame.type === MockZWaveFrameType.Request + && frame.payload instanceof MeterCCGet + ) { + const capabilities = { + ...defaultCapabilities, + ...self.getCCCapabilities( + CommandClasses.Meter, + frame.payload.endpointIndex, + ), + }; + const scale = frame.payload.scale + ?? capabilities.supportedScales[0]; + const rateType = frame.payload.rateType + ?? capabilities.supportedRateTypes[0] + ?? RateType.Consumed; + + const value = capabilities.getValue?.(scale, rateType) ?? { + value: 0, + deltaTime: 0, + }; + const normalizedValue = typeof value === "number" + ? { + value, + deltaTime: 0, + } + : value; + + const cc = new MeterCCReport(self.host, { + nodeId: controller.host.ownNodeId, + type: capabilities.meterType, + scale, + rateType, + ...normalizedValue, + }); + await self.sendToController( + createMockZWaveRequestFrame(cc, { + ackRequested: false, + }), + ); + return true; + } + return false; + }, +}; + +const respondToMeterReset: MockNodeBehavior = { + onControllerFrame(controller, self, frame) { + if ( + frame.type === MockZWaveFrameType.Request + && frame.payload instanceof MeterCCReset + ) { + const capabilities = { + ...defaultCapabilities, + ...self.getCCCapabilities( + CommandClasses.Meter, + frame.payload.endpointIndex, + ), + }; + + const cc = frame.payload; + if ( + cc.type != undefined + && cc.scale != undefined + && cc.rateType != undefined + && cc.targetValue != undefined + ) { + capabilities.onReset?.({ + scale: cc.scale, + rateType: cc.rateType, + targetValue: cc.targetValue, + }); + } else { + capabilities.onReset?.(); + } + + return true; + } + return false; + }, +}; + +export const MeterCCBehaviors = [ + respondToMeterSupportedGet, + respondToMeterGet, + respondToMeterReset, +];