UNPKG

ember-zli

Version:

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

159 lines (158 loc) 5.91 kB
import EventEmitter from "node:events"; import { Socket } from "node:net"; import { Readable } from "node:stream"; import { SerialPort } from "zigbee-herdsman/dist/adapter/serialPort.js"; import { logger } from "../index.js"; import { CONFIG_HIGHWATER_MARK, TCP_REGEX } from "./consts.js"; const NS = { namespace: "transport" }; class TransportWriter extends Readable { writeBuffer(buffer) { this.emit("data", buffer); } _read() { } } export var TransportEvent; (function (TransportEvent) { TransportEvent["CLOSED"] = "closed"; TransportEvent["DATA"] = "data"; TransportEvent["FAILED"] = "failed"; })(TransportEvent || (TransportEvent = {})); /** * Serial or Socket based transport based on passed conf. */ export class Transport extends EventEmitter { connected; portConf; portWriter; portSerial; portSocket; constructor(portConf) { super(); this.connected = false; this.portConf = portConf; } get isSerial() { return Boolean(this.portSerial); } async close(emitClosed, emitFailed = true) { if (this.portSerial?.isOpen) { logger.info("Closing serial connection...", NS); try { await this.portSerial.asyncFlushAndClose(); } catch (error) { logger.error(`Failed to close port: ${error}.`, NS); this.portSerial.removeAllListeners(); if (emitFailed) { this.emit(TransportEvent.FAILED); } return; } this.portSerial.removeAllListeners(); } else if (this.portSocket !== undefined && !this.portSocket.closed) { logger.info("Closing socket connection...", NS); this.portSocket.destroy(); this.portSocket.removeAllListeners(); } if (emitClosed) { this.emit(TransportEvent.CLOSED); } } async initPort(customPortWriter) { // will do nothing if nothing's open await this.close(false); if (TCP_REGEX.test(this.portConf.path)) { const info = new URL(this.portConf.path); logger.debug(`Opening TCP socket with ${info.hostname}:${info.port}`, NS); this.portSocket = new Socket(); this.portSocket.setNoDelay(true); this.portSocket.setKeepAlive(true, 15000); this.portWriter = customPortWriter ?? new TransportWriter({ highWaterMark: CONFIG_HIGHWATER_MARK }); this.portWriter.pipe(this.portSocket); this.portSocket.on("data", this.emitData.bind(this)); return await new Promise((resolve, reject) => { const openError = async (err) => { reject(err); }; if (this.portSocket === undefined) { reject(new Error("Invalid socket")); return; } this.portSocket.on("connect", () => { logger.debug("Socket connected", NS); }); this.portSocket.on("ready", () => { logger.info("Socket ready", NS); this.portSocket.removeListener("error", openError); this.portSocket.once("close", this.onPortClose.bind(this)); this.portSocket.on("error", this.onPortError.bind(this)); this.connected = true; resolve(); }); this.portSocket.once("error", openError); this.portSocket.connect(Number.parseInt(info.port, 10), info.hostname); }); } const serialOpts = { autoOpen: false, baudRate: this.portConf.baudRate, dataBits: 8, parity: "none", path: this.portConf.path, rtscts: this.portConf.rtscts, stopBits: 1, xoff: this.portConf.xoff, xon: this.portConf.xon, }; logger.debug(`Opening serial port with ${JSON.stringify(serialOpts)}`, NS); this.portSerial = new SerialPort(serialOpts); this.portWriter = customPortWriter ?? new TransportWriter({ highWaterMark: CONFIG_HIGHWATER_MARK }); this.portWriter.pipe(this.portSerial); this.portSerial.on("data", this.emitData.bind(this)); await this.portSerial.asyncOpen(); logger.info("Serial port opened", NS); this.portSerial.once("close", this.onPortClose.bind(this)); this.portSerial.on("error", this.onPortError.bind(this)); this.connected = true; } async serialSet(options, afterDelayMS) { await new Promise((resolve, reject) => { const fn = () => this.portSerial?.set(options, (error) => (error ? reject(error) : resolve())); if (afterDelayMS) { setTimeout(fn, afterDelayMS); } else { fn(); } }); } write(buffer) { if (this.portWriter === undefined) { logger.error("No port available to write.", NS); this.emit(TransportEvent.FAILED); } else { logger.debug(`Sending transport data: ${buffer.toString("hex")}.`, NS); this.portWriter.writeBuffer(buffer); } } emitData(data) { this.emit(TransportEvent.DATA, data); } onPortClose(error) { logger.info("Transport closed.", NS); if (error && this.connected) { logger.info(`Transport close ${error}`, NS); this.emit(TransportEvent.FAILED); } else { this.emit(TransportEvent.CLOSED); } } onPortError(error) { this.connected = false; logger.info(`Transport ${error}`, NS); this.emit(TransportEvent.FAILED); } }