inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
159 lines (143 loc) • 4.63 kB
text/typescript
import { CommandClass, ICommandClassContainer } from "@zwave-js/cc";
import {
MessageOrCCLogEntry,
MessagePriority,
MessageRecord,
SinglecastCC,
ZWaveError,
ZWaveErrorCodes,
} from "@zwave-js/core";
import type { ZWaveHost } from "@zwave-js/host";
import {
FunctionType,
gotDeserializationOptions,
Message,
MessageBaseOptions,
MessageDeserializationOptions,
MessageType,
messageTypes,
priority,
} from "@zwave-js/serial";
export enum ApplicationCommandStatusFlags {
RoutedBusy = 0b1, // A response route is locked by the application
LowPower = 0b10, // Received at low output power level
TypeSingle = 0b0000, // Received a single cast frame
TypeBroad = 0b0100, // Received a broad cast frame
TypeMulti = 0b1000, // Received a multi cast frame
TypeMask = TypeSingle | TypeBroad | TypeMulti,
Explore = 0b10000, // Received an explore frame
ForeignFrame = 0b0100_0000, // Received a foreign frame (only promiscuous mode)
ForeignHomeId = 0b1000_0000, // The received frame is received from a foreign HomeID. Only Controllers in Smart Start AddNode mode can receive this status.
}
interface ApplicationCommandRequestOptions extends MessageBaseOptions {
command: CommandClass;
frameType?: ApplicationCommandRequest["frameType"];
routedBusy?: boolean;
}
// This does not expect a response. The controller sends us this when a node sends a command
export class ApplicationCommandRequest
extends Message
implements ICommandClassContainer
{
public constructor(
host: ZWaveHost,
options:
| MessageDeserializationOptions
| ApplicationCommandRequestOptions,
) {
super(host, options);
if (gotDeserializationOptions(options)) {
// first byte is a status flag
const status = this.payload[0];
this.routedBusy = !!(
status & ApplicationCommandStatusFlags.RoutedBusy
);
switch (status & ApplicationCommandStatusFlags.TypeMask) {
case ApplicationCommandStatusFlags.TypeMulti:
this.frameType = "multicast";
break;
case ApplicationCommandStatusFlags.TypeBroad:
this.frameType = "broadcast";
break;
default:
this.frameType = "singlecast";
}
this.isExploreFrame =
this.frameType === "broadcast" &&
!!(status & ApplicationCommandStatusFlags.Explore);
this.isForeignFrame = !!(
status & ApplicationCommandStatusFlags.ForeignFrame
);
this.fromForeignHomeId = !!(
status & ApplicationCommandStatusFlags.ForeignHomeId
);
// followed by a node ID
const nodeId = this.payload[1];
// and a command class
const commandLength = this.payload[2];
this.command = CommandClass.from(this.host, {
data: this.payload.slice(3, 3 + commandLength),
nodeId,
origin: options.origin,
}) as SinglecastCC<CommandClass>;
} else {
// TODO: This logic is unsound
if (!options.command.isSinglecast()) {
throw new ZWaveError(
`ApplicationCommandRequest can only be used for singlecast CCs`,
ZWaveErrorCodes.Argument_Invalid,
);
}
this.frameType = options.frameType ?? "singlecast";
this.routedBusy = !!options.routedBusy;
this.command = options.command;
this.isExploreFrame = false;
this.isForeignFrame = false;
this.fromForeignHomeId = false;
}
}
public readonly routedBusy: boolean;
public readonly frameType: "singlecast" | "broadcast" | "multicast";
public readonly isExploreFrame: boolean;
public readonly isForeignFrame: boolean;
public readonly fromForeignHomeId: boolean;
// This needs to be writable or unwrapping MultiChannelCCs crashes
public command: SinglecastCC<CommandClass>; // TODO: why is this a SinglecastCC?
public override getNodeId(): number | undefined {
if (this.command.isSinglecast()) {
return this.command.nodeId;
}
return super.getNodeId();
}
public serialize(): Buffer {
const statusByte =
(this.frameType === "broadcast"
? ApplicationCommandStatusFlags.TypeBroad
: this.frameType === "multicast"
? ApplicationCommandStatusFlags.TypeMulti
: 0) |
(this.routedBusy ? ApplicationCommandStatusFlags.RoutedBusy : 0);
const serializedCC = this.command.serialize();
this.payload = Buffer.concat([
Buffer.from([
statusByte,
this.getNodeId() ?? this.host.ownNodeId,
serializedCC.length,
]),
serializedCC,
]);
return super.serialize();
}
public toLogEntry(): MessageOrCCLogEntry {
const message: MessageRecord = {};
if (this.frameType !== "singlecast") {
message.type = this.frameType;
}
return {
...super.toLogEntry(),
message,
};
}
}