Skip to content

Commit

Permalink
feat: add mocks for Multilevel Sensor CC
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Nov 23, 2023
1 parent a489007 commit 89031d4
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 19 deletions.
75 changes: 56 additions & 19 deletions packages/cc/src/cc/MultilevelSensorCC.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Scale, getDefaultScale } from "@zwave-js/config";
import { timespan } from "@zwave-js/core";
import { encodeBitMask, timespan } from "@zwave-js/core";
import type {
IZWaveEndpoint,
MessageOrCCLogEntry,
Expand All @@ -13,8 +13,6 @@ import {
type MaybeNotKnown,
MessagePriority,
ValueMetadata,
ZWaveError,
ZWaveErrorCodes,
encodeFloatWithScale,
parseBitMask,
parseFloatWithScale,
Expand Down Expand Up @@ -829,22 +827,41 @@ export class MultilevelSensorCCGet extends MultilevelSensorCC {
}
}

// @publicAPI
export interface MultilevelSensorCCSupportedSensorReportOptions
extends CCCommandOptions
{
supportedSensorTypes: readonly number[];
}

@CCCommand(MultilevelSensorCommand.SupportedSensorReport)
export class MultilevelSensorCCSupportedSensorReport
extends MultilevelSensorCC
{
public constructor(
host: ZWaveHost,
options: CommandClassDeserializationOptions,
options:
| CommandClassDeserializationOptions
| MultilevelSensorCCSupportedSensorReportOptions,
) {
super(host, options);
validatePayload(this.payload.length >= 1);
this.supportedSensorTypes = parseBitMask(this.payload);

if (gotDeserializationOptions(options)) {
validatePayload(this.payload.length >= 1);
this.supportedSensorTypes = parseBitMask(this.payload);
} else {
this.supportedSensorTypes = options.supportedSensorTypes;
}
}

// TODO: Use this during interview to precreate values
@ccValue(MultilevelSensorCCValues.supportedSensorTypes)
public readonly supportedSensorTypes: readonly number[];
public supportedSensorTypes: readonly number[];

public serialize(): Buffer {
this.payload = encodeBitMask(this.supportedSensorTypes);
return super.serialize();
}

public toLogEntry(applHost: ZWaveApplicationHost): MessageOrCCLogEntry {
return {
Expand All @@ -869,20 +886,35 @@ export class MultilevelSensorCCSupportedSensorReport
@expectedCCResponse(MultilevelSensorCCSupportedSensorReport)
export class MultilevelSensorCCGetSupportedSensor extends MultilevelSensorCC {}

// @publicAPI
export interface MultilevelSensorCCSupportedScaleReportOptions
extends CCCommandOptions
{
sensorType: number;
supportedScales: readonly number[];
}

@CCCommand(MultilevelSensorCommand.SupportedScaleReport)
export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC {
public constructor(
host: ZWaveHost,
options: CommandClassDeserializationOptions,
options:
| CommandClassDeserializationOptions
| MultilevelSensorCCSupportedScaleReportOptions,
) {
super(host, options);

validatePayload(this.payload.length >= 2);
this.sensorType = this.payload[0];
this.supportedScales = parseBitMask(
Buffer.from([this.payload[1] & 0b1111]),
0,
);
if (gotDeserializationOptions(options)) {
validatePayload(this.payload.length >= 2);
this.sensorType = this.payload[0];
this.supportedScales = parseBitMask(
Buffer.from([this.payload[1] & 0b1111]),
0,
);
} else {
this.sensorType = options.sensorType;
this.supportedScales = options.supportedScales;
}
}

public readonly sensorType: number;
Expand All @@ -894,6 +926,14 @@ export class MultilevelSensorCCSupportedScaleReport extends MultilevelSensorCC {
)
public readonly supportedScales: readonly number[];

public serialize(): Buffer {
this.payload = Buffer.concat([
Buffer.from([this.sensorType]),
encodeBitMask(this.supportedScales, 4, 0),
]);
return super.serialize();
}

public toLogEntry(applHost: ZWaveApplicationHost): MessageOrCCLogEntry {
return {
...super.toLogEntry(applHost),
Expand Down Expand Up @@ -935,11 +975,8 @@ export class MultilevelSensorCCGetSupportedScale extends MultilevelSensorCC {
) {
super(host, options);
if (gotDeserializationOptions(options)) {
// TODO: Deserialize payload
throw new ZWaveError(
`${this.constructor.name}: deserialization not implemented`,
ZWaveErrorCodes.Deserialization_NotImplemented,
);
validatePayload(this.payload.length >= 1);
this.sensorType = this.payload[0];
} else {
this.sensorType = options.sensorType;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/testing/src/CCSpecificCapabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export interface NotificationCCCapabilities {
notificationTypesAndEvents: Record<number, number[]>;
}

export interface MultilevelSensorCCCapabilities {
sensors: Record<number, {
supportedScales: number[];
}>;
getValue?: (
sensorType: number | undefined,
scale: number | undefined,
) => number | undefined;
}

export interface SoundSwitchCCCapabilities {
defaultToneId: number;
defaultVolume: number;
Expand Down Expand Up @@ -105,6 +115,7 @@ export interface ScheduleEntryLockCCCapabilities {
export type CCSpecificCapabilities = {
[CommandClasses.Configuration]: ConfigurationCCCapabilities;
[CommandClasses.Notification]: NotificationCCCapabilities;
[49 /* Multilevel Sensor */]: MultilevelSensorCCCapabilities;
[121 /* Sound Switch */]: SoundSwitchCCCapabilities;
[106 /* Window Covering */]: WindowCoveringCCCapabilities;
[144 /* Energy Production */]: EnergyProductionCCCapabilities;
Expand Down
2 changes: 2 additions & 0 deletions packages/zwave-js/src/lib/node/MockNodeBehaviors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { MultilevelSensorCCBehaviors } from "./mockCCBehaviors/MultilevelSensor";
import { NotificationCCBehaviors } from "./mockCCBehaviors/Notification";
import { ScheduleEntryLockCCBehaviors } from "./mockCCBehaviors/ScheduleEntryLock";
import { SoundSwitchCCBehaviors } from "./mockCCBehaviors/SoundSwitch";
Expand Down Expand Up @@ -293,6 +294,7 @@ export function createDefaultBehaviors(): MockNodeBehavior[] {
...ConfigurationCCBehaviors,
...EnergyProductionCCBehaviors,
...ManufacturerSpecificCCBehaviors,
...MultilevelSensorCCBehaviors,
...NotificationCCBehaviors,
...ScheduleEntryLockCCBehaviors,
...SoundSwitchCCBehaviors,
Expand Down
128 changes: 128 additions & 0 deletions packages/zwave-js/src/lib/node/mockCCBehaviors/MultilevelSensor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {
MultilevelSensorCCGet,
MultilevelSensorCCGetSupportedScale,
MultilevelSensorCCGetSupportedSensor,
MultilevelSensorCCReport,
MultilevelSensorCCSupportedScaleReport,
MultilevelSensorCCSupportedSensorReport,
} from "@zwave-js/cc";
import { CommandClasses } from "@zwave-js/core";
import type { MultilevelSensorCCCapabilities } from "@zwave-js/testing";
import {
type MockNodeBehavior,
MockZWaveFrameType,
createMockZWaveRequestFrame,
} from "@zwave-js/testing";

const defaultCapabilities: MultilevelSensorCCCapabilities = {
sensors: {}, // none
};

const respondToMultilevelSensorGetSupportedSensor: MockNodeBehavior = {
async onControllerFrame(controller, self, frame) {
if (
frame.type === MockZWaveFrameType.Request
&& frame.payload instanceof MultilevelSensorCCGetSupportedSensor
) {
const capabilities = {
...defaultCapabilities,
...self.getCCCapabilities(
CommandClasses["Multilevel Sensor"],
frame.payload.endpointIndex,
),
};
const cc = new MultilevelSensorCCSupportedSensorReport(self.host, {
nodeId: controller.host.ownNodeId,
supportedSensorTypes: Object.keys(
capabilities.sensors,
).map((t) => parseInt(t)),
});
await self.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
return true;
}
return false;
},
};

const respondToMultilevelSensorGetSupportedScale: MockNodeBehavior = {
async onControllerFrame(controller, self, frame) {
if (
frame.type === MockZWaveFrameType.Request
&& frame.payload instanceof MultilevelSensorCCGetSupportedScale
) {
const capabilities = {
...defaultCapabilities,
...self.getCCCapabilities(
CommandClasses["Multilevel Sensor"],
frame.payload.endpointIndex,
),
};
const sensorType = frame.payload.sensorType;
const supportedScales =
capabilities.sensors[sensorType]?.supportedScales ?? [];
const cc = new MultilevelSensorCCSupportedScaleReport(self.host, {
nodeId: controller.host.ownNodeId,
sensorType,
supportedScales,
});
await self.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
return true;
}
return false;
},
};

const respondToMultilevelSensorGet: MockNodeBehavior = {
async onControllerFrame(controller, self, frame) {
if (
frame.type === MockZWaveFrameType.Request
&& frame.payload instanceof MultilevelSensorCCGet
) {
const capabilities = {
...defaultCapabilities,
...self.getCCCapabilities(
CommandClasses["Multilevel Sensor"],
frame.payload.endpointIndex,
),
};
const firstSupportedSensorType =
Object.keys(capabilities.sensors).length > 0
? parseInt(Object.keys(capabilities.sensors)[0])
: undefined;
const sensorType = frame.payload.sensorType
?? firstSupportedSensorType
?? 1;
const scale = frame.payload.scale
?? capabilities.sensors[sensorType].supportedScales[0]
?? 0;
const value = capabilities.getValue?.(sensorType, scale) ?? 0;
const cc = new MultilevelSensorCCReport(self.host, {
nodeId: controller.host.ownNodeId,
type: sensorType,
scale,
value,
});
await self.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
return true;
}
return false;
},
};

export const MultilevelSensorCCBehaviors = [
respondToMultilevelSensorGetSupportedSensor,
respondToMultilevelSensorGetSupportedScale,
respondToMultilevelSensorGet,
];

0 comments on commit 89031d4

Please sign in to comment.