inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
103 lines (91 loc) • 3.12 kB
text/typescript
import { Transform, TransformCallback } from "stream";
import type { SerialLogger } from "./Logger";
import { MessageHeaders } from "./MessageHeaders";
/**
* Checks if there's enough data in the buffer to deserialize a complete message
*/
function containsCompleteMessage(data?: Buffer): boolean {
return !!data && data.length >= 5 && data.length >= getMessageLength(data);
}
/** Given a buffer that starts with SOF, this method returns the number of bytes the first message occupies in the buffer */
function getMessageLength(data: Buffer): number {
const remainingLength = data[1];
return remainingLength + 2;
}
export class SerialAPIParser extends Transform {
constructor(private logger?: SerialLogger) {
// We read byte streams but emit messages
super({ readableObjectMode: true });
}
private receiveBuffer = Buffer.allocUnsafe(0);
_transform(
chunk: any,
encoding: string,
callback: TransformCallback,
): void {
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
while (this.receiveBuffer.length > 0) {
if (this.receiveBuffer[0] !== MessageHeaders.SOF) {
let skip = 1;
switch (this.receiveBuffer[0]) {
// Emit the single-byte messages directly
case MessageHeaders.ACK: {
this.logger?.ACK("inbound");
this.push(MessageHeaders.ACK);
break;
}
case MessageHeaders.NAK: {
this.logger?.NAK("inbound");
this.push(MessageHeaders.NAK);
break;
}
case MessageHeaders.CAN: {
this.logger?.CAN("inbound");
this.push(MessageHeaders.CAN);
break;
}
default: {
// INS12350: A host or a Z-Wave chip waiting for new traffic MUST ignore all other
// byte values than 0x06 (ACK), 0x15 (NAK), 0x18 (CAN) or 0x01 (Data frame).
// Scan ahead until the next valid byte and log the invalid bytes
while (skip < this.receiveBuffer.length) {
const byte = this.receiveBuffer[skip];
if (
byte === MessageHeaders.SOF ||
byte === MessageHeaders.ACK ||
byte === MessageHeaders.NAK ||
byte === MessageHeaders.CAN
) {
// Next byte is valid, keep it
break;
}
skip++;
}
const discarded = this.receiveBuffer.slice(0, skip);
this.logger?.discarded(discarded);
}
}
// Continue with the next valid byte
this.receiveBuffer = skipBytes(this.receiveBuffer, skip);
continue;
}
if (!containsCompleteMessage(this.receiveBuffer)) {
// The buffer contains no complete message, we're done here for now
break;
} else {
// We have at least one complete message
const msgLength = getMessageLength(this.receiveBuffer);
// emit it and slice the read bytes from the buffer
const msg = this.receiveBuffer.slice(0, msgLength);
this.receiveBuffer = skipBytes(this.receiveBuffer, msgLength);
this.logger?.data("inbound", msg);
this.push(msg);
}
}
callback();
}
}
/** Skips the first n bytes of a buffer and returns the rest */
export function skipBytes(buf: Buffer, n: number): Buffer {
return Buffer.from(buf.slice(n));
}