UNPKG

ember-zli

Version:

Interact with EmberZNet-based adapters using zigbee-herdsman 'ember' driver

156 lines (155 loc) 6.59 kB
import EventEmitter from "node:events"; import { logger } from "../index.js"; import { computeCRC16 } from "./utils.js"; const NS = { namespace: "xmodemcrc" }; const FILLER = 0xff; /** First block number. */ const XMODEM_START_BLOCK = 1; /** Bytes in each block (header and checksum not included) */ const BLOCK_SIZE = 128; /** Maximum retries to send block before giving up */ const MAX_RETRIES = 10; export var XSignal; (function (XSignal) { /** Start of Header */ XSignal[XSignal["SOH"] = 1] = "SOH"; /** End of Transmission */ XSignal[XSignal["EOT"] = 4] = "EOT"; /** Acknowledge */ XSignal[XSignal["ACK"] = 6] = "ACK"; /** Not Acknowledge */ XSignal[XSignal["NAK"] = 21] = "NAK"; /** End of Transmission Block / File done */ XSignal[XSignal["ETB"] = 23] = "ETB"; /** Cancel */ XSignal[XSignal["CAN"] = 24] = "CAN"; /** Block OK */ XSignal[XSignal["BOK"] = 25] = "BOK"; /** 'C' */ XSignal[XSignal["CRC"] = 67] = "CRC"; })(XSignal || (XSignal = {})); export var XExitStatus; (function (XExitStatus) { XExitStatus[XExitStatus["SUCCESS"] = 0] = "SUCCESS"; XExitStatus[XExitStatus["FAIL"] = 1] = "FAIL"; XExitStatus[XExitStatus["CANCEL"] = 2] = "CANCEL"; })(XExitStatus || (XExitStatus = {})); export var XEvent; (function (XEvent) { /** C byte received */ XEvent["START"] = "start"; XEvent["STOP"] = "stop"; /** Data to write */ XEvent["DATA"] = "data"; })(XEvent || (XEvent = {})); export class XModemCRC extends EventEmitter { blockNum = XMODEM_START_BLOCK; blocks = []; retries = MAX_RETRIES; sentEOF = false; waitForBlock = XMODEM_START_BLOCK; init(buffer) { this.blockNum = XMODEM_START_BLOCK; this.blocks = [Buffer.from([])]; // filler for start block offset this.retries = MAX_RETRIES; this.sentEOF = false; this.waitForBlock = XMODEM_START_BLOCK; let currentBlock = Buffer.alloc(BLOCK_SIZE); while (buffer.length > 0) { for (let i = 0; i < BLOCK_SIZE; i++) { currentBlock[i] = buffer[i] === undefined ? FILLER : buffer[i]; } buffer = buffer.subarray(BLOCK_SIZE); this.blocks.push(currentBlock); currentBlock = Buffer.alloc(BLOCK_SIZE); } const blocksCount = this.blocks.length - XMODEM_START_BLOCK; logger.debug(`Outgoing blocks count=${blocksCount}, size=${blocksCount * BLOCK_SIZE}.`, NS); } process(recdData) { if (this.waitForBlock !== this.blockNum) { logger.warning(`Received out of sequence data: ${recdData.toString("hex")} (blockNum=${this.blockNum}, expected=${this.waitForBlock}).`, NS); this.retries--; if (this.retries === 0) { logger.error(`Maximum retries ${MAX_RETRIES} reached. Giving up.`, NS); this.emit(XEvent.STOP, XExitStatus.FAIL); } return; } logger.debug(`Current block ${this.blockNum}. Received data: ${recdData.toString("hex")}.`, NS); switch (recdData[0]) { case XSignal.CRC: { if (this.blockNum === XMODEM_START_BLOCK) { logger.debug("Received C byte, starting transfer...", NS); if (this.blocks.length > this.blockNum) { this.emit(XEvent.START); this.emitBlock(this.blockNum, this.blocks[this.blockNum]); this.blockNum++; } } break; } case XSignal.ACK: { if (this.blockNum > XMODEM_START_BLOCK) { this.retries = MAX_RETRIES; logger.debug("ACK received.", NS); if (this.blocks.length > this.blockNum) { this.emitBlock(this.blockNum, this.blocks[this.blockNum]); this.blockNum++; } else if (this.blocks.length === this.blockNum) { if (this.sentEOF === false) { this.sentEOF = true; logger.debug("Sending End of Transmission.", NS); this.emit(XEvent.DATA, Buffer.from([XSignal.EOT]), 100); } else { logger.debug("Done.", NS); this.emit(XEvent.STOP, XExitStatus.SUCCESS); } } } break; } case XSignal.NAK: { if (this.blockNum > XMODEM_START_BLOCK) { this.retries--; logger.debug("NAK received.", NS); if (this.retries === 0) { logger.error(`Maximum retries ${MAX_RETRIES} reached. Giving up.`, NS); this.emit(XEvent.STOP, XExitStatus.FAIL); } else if (this.blockNum === this.blocks.length && this.sentEOF) { logger.warning("Received NAK, resending EOT.", NS); this.emit(XEvent.DATA, Buffer.from([XSignal.EOT]), 0); } else { logger.warning("Packet corrupted, resending previous block.", NS); this.blockNum--; if (this.blocks.length > this.blockNum) { this.emitBlock(this.blockNum, this.blocks[this.blockNum]); this.blockNum++; } } } break; } case XSignal.CAN: { logger.error("Received cancel.", NS); this.emit(XEvent.STOP, XExitStatus.CANCEL); break; } default: { logger.debug(`Unrecognized data received for block ${this.blockNum}. Ignoring.`, NS); break; } } } emitBlock(blockNum, blockData) { const progressPc = Math.round((blockNum / (this.blocks.length - XMODEM_START_BLOCK)) * 100); this.waitForBlock = blockNum + 1; blockNum &= 0xff; // starts at 1, goes to 255, then wraps back to 0 (XModem spec) logger.debug(`Sending block ${blockNum}.`, NS); this.emit(XEvent.DATA, Buffer.concat([Buffer.from([XSignal.SOH, blockNum, 0xff - blockNum]), blockData, computeCRC16(blockData)]), progressPc); } }