-
-
Notifications
You must be signed in to change notification settings - Fork 586
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
feat(cc): add value event and notifications for Multilevel Switch CC Set and Start/StopLevelChange #4282
Changes from 4 commits
5e8d03b
185b341
0f9890e
fb7a000
fbf0efd
9ec57af
0c69a32
414244d
0180109
398de14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
|
@@ -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> { | ||
|
@@ -497,6 +506,17 @@ export class MultilevelSwitchCC extends CommandClass { | |
|
||
await this.refreshValues(); | ||
|
||
// create compat event value if necessary | ||
if (node.deviceConfig?.compat?.treatBasicSetAsEvent) { | ||
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; | ||
} | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems wrong. A |
||
|
||
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()); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing the code that sends There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
@@ -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; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,6 +110,13 @@ import { | |
getEndpointDeviceClassValueId, | ||
getEndpointIndizesValueId, | ||
} from "../commandclass/MultiChannelCC"; | ||
import { | ||
getCompatEventValueId as getMultilevelSwitchCCCompatEventValueId, | ||
MultilevelSwitchCC, | ||
MultilevelSwitchCCSet, | ||
MultilevelSwitchCCStartLevelChange, | ||
MultilevelSwitchCCStopLevelChange, | ||
} from "../commandclass/MultilevelSwitchCC"; | ||
import { | ||
getNodeLocationValueId, | ||
getNodeNameValueId, | ||
|
@@ -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) { | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
|
||
/** | ||
* Allows automatically resetting notification values to idle if the node does not do it itself | ||
*/ | ||
|
There was a problem hiding this comment.
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...
)