UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

103 lines (91 loc) 3.12 kB
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)); }