Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cc): add value event and notifications for Multilevel Switch CC Set and Start/StopLevelChange #4282

Merged
merged 10 commits into from
Mar 14, 2022
80 changes: 65 additions & 15 deletions packages/zwave-js/src/lib/commandclass/MultilevelSwitchCC.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { MessageRecord, ValueID } from "@zwave-js/core";
import {
CommandClasses,
Duration,
Maybe,
MessageOrCCLogEntry,
MessageRecord,
parseMaybeNumber,
parseNumber,
validatePayload,
ValueID,
ValueMetadata,
ZWaveError,
ZWaveErrorCodes,
Expand Down Expand Up @@ -109,6 +110,14 @@ function getSuperviseStartStopLevelChangeValueId(): ValueID {
};
}

export function getCompatEventValueId(endpoint?: number): ValueID {
return {
commandClass: CommandClasses["Multilevel Switch"],
endpoint,
property: "event",
};
}

@API(CommandClasses["Multilevel Switch"])
export class MultilevelSwitchCCAPI extends CCAPI {
public supportsCommand(cmd: MultilevelSwitchCommand): Maybe<boolean> {
Expand Down Expand Up @@ -497,6 +506,17 @@ export class MultilevelSwitchCC extends CommandClass {

await this.refreshValues();

// create compat event value if necessary
if (node.deviceConfig?.compat?.treatBasicSetAsEvent) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MultilevelSwitchCC should have a separate compat flag (not named ...Basic...)

const valueId = getCompatEventValueId(this.endpointIndex);
if (!node.valueDB.hasMetadata(valueId)) {
node.valueDB.setMetadata(valueId, {
...ValueMetadata.ReadOnlyUInt8,
label: "Event value",
});
}
}

// Remember that the interview is complete
this.interviewComplete = true;
}
Expand Down Expand Up @@ -581,24 +601,49 @@ export class MultilevelSwitchCCSet extends MultilevelSwitchCC {
) {
super(driver, 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.currentValue = parseMaybeNumber(
this.payload[0],
driver.options.preserveUnknownValues,
);

if (this.version >= 2 && this.payload.length >= 2) {
this.targetValue = parseNumber(this.payload[0]);
reubenbijl marked this conversation as resolved.
Show resolved Hide resolved
this.duration = Duration.parseReport(this.payload[1]);
}
} else {
this.targetValue = options.targetValue;
this.duration = options.duration;
}
}

public targetValue: number;
public duration: Duration | undefined;
@ccValue()
@ccValueMetadata({
...ValueMetadata.ReadOnlyLevel,
label: "Current value",
})
public readonly currentValue: Maybe<number> | undefined;

@ccValue({ forceCreation: true })
@ccValueMetadata({
...ValueMetadata.Level,
label: "Target value",
})
public readonly targetValue: number | undefined;

@ccValue({ minVersion: 2 })
@ccValueMetadata({
...ValueMetadata.ReadOnlyDuration,
label: "Remaining duration until target value",
})
public readonly duration: Duration | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong. A MultilevelSwitchCCSet doesn't have a currentValue property and its properties shouldn't be readonly.


public serialize(): Buffer {
const payload = [this.targetValue];
if (this.version >= 2 && this.duration) {
payload.push(this.duration.serializeSet());
const payload: number[] = [
typeof this.currentValue !== "number" ? 0xfe : this.currentValue,
];
if (this.version >= 2 && this.targetValue && this.duration) {
payload.push(this.targetValue, this.duration.serializeReport());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the code that sends MultilevelSwitchCCSet isn't necessary to understand them when they are received.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably why tests are failing.

this.payload = Buffer.from(payload);
return super.serialize();
Expand Down Expand Up @@ -705,11 +750,16 @@ export class MultilevelSwitchCCStartLevelChange extends MultilevelSwitchCC {
) {
super(driver, options);
if (gotDeserializationOptions(options)) {
// TODO: Deserialize payload
throw new ZWaveError(
`${this.constructor.name}: deserialization not implemented`,
ZWaveErrorCodes.Deserialization_NotImplemented,
);
validatePayload(this.payload.length >= 3);
const direction = (this.payload[0] & 0b0_1_0_00000) >>> 6;
const ignoreStartLevel = (this.payload[0] & 0b0_0_1_00000) >>> 5;
const startLevel = this.payload[1];
const duration = this.payload[2];

this.duration = Duration.parseReport(duration);
this.ignoreStartLevel = !!ignoreStartLevel;
this.startLevel = startLevel;
this.direction = direction ? "down" : "up";
} else {
this.duration = options.duration;
this.ignoreStartLevel = options.ignoreStartLevel;
Expand Down
60 changes: 60 additions & 0 deletions packages/zwave-js/src/lib/node/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ import {
getEndpointDeviceClassValueId,
getEndpointIndizesValueId,
} from "../commandclass/MultiChannelCC";
import {
getCompatEventValueId as getMultilevelSwitchCCCompatEventValueId,
MultilevelSwitchCC,
MultilevelSwitchCCSet,
MultilevelSwitchCCStartLevelChange,
MultilevelSwitchCCStopLevelChange,
} from "../commandclass/MultilevelSwitchCC";
import {
getNodeLocationValueId,
getNodeNameValueId,
Expand Down Expand Up @@ -2239,6 +2246,8 @@ protocol version: ${this._protocolVersion}`;

if (command instanceof BasicCC) {
return this.handleBasicCommand(command);
} else if (command instanceof MultilevelSwitchCC) {
return this.handleMultilevelSwitchCommand(command);
reubenbijl marked this conversation as resolved.
Show resolved Hide resolved
} else if (command instanceof CentralSceneCCNotification) {
return this.handleCentralSceneNotification(command);
} else if (command instanceof WakeUpCCWakeUpNotification) {
Expand Down Expand Up @@ -2811,6 +2820,57 @@ protocol version: ${this._protocolVersion}`;
}
}

/** Handles the receipt of a MultilevelCC Set or Report */
private handleMultilevelSwitchCommand(command: MultilevelSwitchCC): void {
// Retrieve the endpoint the command is coming from
const sourceEndpoint =
this.getEndpoint(command.endpointIndex ?? 0) ?? this;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't used

if (command instanceof MultilevelSwitchCCSet) {
this.driver.controllerLog.logNode(this.id, {
endpoint: command.endpointIndex,
message: "treating MultiLevelSwitchCCSet::Set as a value event",
});
this._valueDB.setValue(
getMultilevelSwitchCCCompatEventValueId(command.endpointIndex),
command.targetValue,
{
stateful: false,
},
);
return;
}
if (command instanceof MultilevelSwitchCCStartLevelChange) {
this.driver.controllerLog.logNode(this.id, {
endpoint: command.endpointIndex,
message:
"treating MultilevelSwitchCCStartLevelChange::Set as a value event",
});
this._valueDB.setValue(
getMultilevelSwitchCCCompatEventValueId(command.endpointIndex),
command.direction,
{
stateful: false,
},
);
return;
}
if (command instanceof MultilevelSwitchCCStopLevelChange) {
this.driver.controllerLog.logNode(this.id, {
endpoint: command.endpointIndex,
message:
"treating MultilevelSwitchCCStopLevelChange::Set as a value event",
});
this._valueDB.setValue(
getMultilevelSwitchCCCompatEventValueId(command.endpointIndex),
"stop",
{
stateful: false,
},
);
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not water down the meaning of the event value too much, it should be limited to the value received via a Set command.
I'd prefer having start/stopLevelChange as a new notification type, like for Powerlevel CC and Entry Control CC - see point 3. in my comment in the issue.

}

/**
* Allows automatically resetting notification values to idle if the node does not do it itself
*/
Expand Down