zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
389 lines • 16.7 kB
JavaScript
/* v8 ignore start */
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SerialDriver = void 0;
const node_events_1 = require("node:events");
const node_net_1 = __importDefault(require("node:net"));
const utils_1 = require("../../../utils");
const logger_1 = require("../../../utils/logger");
const serialPort_1 = require("../../serialPort");
const socketPortUtils_1 = __importDefault(require("../../socketPortUtils"));
const frame_1 = require("./frame");
const parser_1 = require("./parser");
const writer_1 = require("./writer");
const NS = "zh:ezsp:uart";
var NcpResetCode;
(function (NcpResetCode) {
NcpResetCode[NcpResetCode["RESET_UNKNOWN_REASON"] = 0] = "RESET_UNKNOWN_REASON";
NcpResetCode[NcpResetCode["RESET_EXTERNAL"] = 1] = "RESET_EXTERNAL";
NcpResetCode[NcpResetCode["RESET_POWER_ON"] = 2] = "RESET_POWER_ON";
NcpResetCode[NcpResetCode["RESET_WATCHDOG"] = 3] = "RESET_WATCHDOG";
NcpResetCode[NcpResetCode["RESET_ASSERT"] = 6] = "RESET_ASSERT";
NcpResetCode[NcpResetCode["RESET_BOOTLOADER"] = 9] = "RESET_BOOTLOADER";
NcpResetCode[NcpResetCode["RESET_SOFTWARE"] = 11] = "RESET_SOFTWARE";
NcpResetCode[NcpResetCode["ERROR_EXCEEDED_MAXIMUM_ACK_TIMEOUT_COUNT"] = 81] = "ERROR_EXCEEDED_MAXIMUM_ACK_TIMEOUT_COUNT";
NcpResetCode[NcpResetCode["ERROR_UNKNOWN_EM3XX_ERROR"] = 128] = "ERROR_UNKNOWN_EM3XX_ERROR";
})(NcpResetCode || (NcpResetCode = {}));
class SerialDriver extends node_events_1.EventEmitter {
serialPort;
socketPort;
writer;
parser;
initialized;
sendSeq = 0; // next frame number to send
recvSeq = 0; // next frame number to receive
ackSeq = 0; // next number after the last accepted frame
rejectCondition = false;
waitress;
queue;
constructor() {
super();
this.initialized = false;
this.queue = new utils_1.Queue(1);
this.waitress = new utils_1.Waitress(this.waitressValidator, this.waitressTimeoutFormatter);
this.writer = new writer_1.Writer();
this.parser = new parser_1.Parser();
}
async connect(options) {
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
if (socketPortUtils_1.default.isTcpPath(options.path)) {
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
await this.openSocketPort(options.path);
}
else {
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
await this.openSerialPort(options.path, options.baudRate, options.rtscts);
}
}
async openSerialPort(path, baudRate, rtscts) {
const options = {
path,
baudRate: typeof baudRate === "number" ? baudRate : 115200,
rtscts: typeof rtscts === "boolean" ? rtscts : false,
autoOpen: false,
parity: "none",
stopBits: 1,
xon: false,
xoff: false,
};
// enable software flow control if RTS/CTS not enabled in config
if (!options.rtscts) {
logger_1.logger.debug("RTS/CTS config is off, enabling software flow control.", NS);
options.xon = true;
options.xoff = true;
}
logger_1.logger.debug(`Opening SerialPort with ${JSON.stringify(options)}`, NS);
// @ts-ignore
this.serialPort = new serialPort_1.SerialPort(options);
this.writer.pipe(this.serialPort);
this.serialPort.pipe(this.parser);
this.parser.on("parsed", this.onParsed.bind(this));
try {
await this.serialPort.asyncOpen();
logger_1.logger.debug("Serialport opened", NS);
this.serialPort.once("close", this.onPortClose.bind(this));
this.serialPort.on("error", this.onPortError.bind(this));
// reset
await this.reset();
this.initialized = true;
}
catch (error) {
this.initialized = false;
if (this.serialPort.isOpen) {
this.serialPort.close();
}
throw error;
}
}
async openSocketPort(path) {
const info = socketPortUtils_1.default.parseTcpPath(path);
logger_1.logger.debug(`Opening TCP socket with ${info.host}:${info.port}`, NS);
this.socketPort = new node_net_1.default.Socket();
this.socketPort.setNoDelay(true);
this.socketPort.setKeepAlive(true, 15000);
this.writer.pipe(this.socketPort);
this.socketPort.pipe(this.parser);
this.parser.on("parsed", this.onParsed.bind(this));
return await new Promise((resolve, reject) => {
const openError = (err) => {
this.initialized = false;
reject(err);
};
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.on("connect", () => {
logger_1.logger.debug("Socket connected", NS);
});
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.on("ready", async () => {
logger_1.logger.debug("Socket ready", NS);
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.removeListener("error", openError);
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.once("close", this.onPortClose.bind(this));
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.on("error", this.onPortError.bind(this));
// reset
await this.reset();
this.initialized = true;
resolve();
});
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.once("error", openError);
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.connect(info.port, info.host);
});
}
async onParsed(frame) {
const rejectCondition = this.rejectCondition;
try {
frame.checkCRC();
/* Frame receive handler */
switch (frame.type) {
case frame_1.FrameType.DATA:
this.handleDATA(frame);
break;
case frame_1.FrameType.ACK:
this.handleACK(frame);
break;
case frame_1.FrameType.NAK:
this.handleNAK(frame);
break;
case frame_1.FrameType.RST:
this.handleRST(frame);
break;
case frame_1.FrameType.RSTACK:
this.handleRSTACK(frame);
break;
case frame_1.FrameType.ERROR:
await this.handleError(frame);
break;
default:
this.rejectCondition = true;
logger_1.logger.debug(`UNKNOWN FRAME RECEIVED: ${frame}`, NS);
}
}
catch (error) {
this.rejectCondition = true;
logger_1.logger.error(`Error while parsing to NpiFrame '${error}'`, NS);
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
logger_1.logger.debug(error.stack, NS);
}
// We send NAK only if the rejectCondition was set in the current processing
if (!rejectCondition && this.rejectCondition) {
// send NAK
this.writer.sendNAK(this.recvSeq);
}
}
handleDATA(frame) {
/* Data frame receive handler */
const frmNum = (frame.control & 0x70) >> 4;
const reTx = (frame.control & 0x08) >> 3;
logger_1.logger.debug(`<-- DATA (${frmNum},${frame.control & 0x07},${reTx}): ${frame}`, NS);
// Expected package {recvSeq}, but received {frmNum}
// This happens when the chip sends us a reTx packet, but we are waiting for the next one
if (this.recvSeq !== frmNum) {
if (reTx) {
// if the reTx flag is set, then this is a packet replay
logger_1.logger.debug(`Unexpected DATA packet sequence ${frmNum} | ${this.recvSeq}: packet replay`, NS);
}
else {
// otherwise, the sequence of packets is out of order - skip or send NAK is needed
logger_1.logger.debug(`Unexpected DATA packet sequence ${frmNum} | ${this.recvSeq}: reject condition`, NS);
this.rejectCondition = true;
return;
}
}
this.rejectCondition = false;
this.recvSeq = (frmNum + 1) & 7; // next
logger_1.logger.debug(`--> ACK (${this.recvSeq})`, NS);
this.writer.sendACK(this.recvSeq);
const handled = this.handleACK(frame);
if (reTx && !handled) {
// if the package is resent and did not expect it,
// then will skip it - already processed it earlier
logger_1.logger.debug(`Skipping the packet as repeated (${this.recvSeq})`, NS);
return;
}
const data = frame.buffer.subarray(1, -3);
this.emit("received", frame_1.Frame.makeRandomizedBuffer(data));
}
handleACK(frame) {
/* Handle an acknowledgement frame */
// next number after the last accepted frame
this.ackSeq = frame.control & 0x07;
logger_1.logger.debug(`<-- ACK (${this.ackSeq}): ${frame}`, NS);
const handled = this.waitress.resolve({ sequence: this.ackSeq });
if (!handled && this.sendSeq !== this.ackSeq) {
// Packet confirmation received for {ackSeq}, but was expected {sendSeq}
// This happens when the chip has not yet received of the packet {sendSeq} from us,
// but has already sent us the next one.
logger_1.logger.debug(`Unexpected packet sequence ${this.ackSeq} | ${this.sendSeq}`, NS);
}
return handled;
}
handleNAK(frame) {
/* Handle negative acknowledgment frame */
const nakNum = frame.control & 0x07;
logger_1.logger.debug(`<-- NAK (${nakNum}): ${frame}`, NS);
const handled = this.waitress.reject({ sequence: nakNum }, "Recv NAK frame");
if (!handled) {
// send NAK
logger_1.logger.debug(`NAK Unexpected packet sequence ${nakNum}`, NS);
}
else {
logger_1.logger.debug(`NAK Expected packet sequence ${nakNum}`, NS);
}
}
handleRST(frame) {
logger_1.logger.debug(`<-- RST: ${frame}`, NS);
}
handleRSTACK(frame) {
/* Reset acknowledgement frame receive handler */
let code;
this.rejectCondition = false;
logger_1.logger.debug(`<-- RSTACK ${frame}`, NS);
try {
code = NcpResetCode[frame.buffer[2]];
}
catch {
code = NcpResetCode.ERROR_UNKNOWN_EM3XX_ERROR;
}
logger_1.logger.debug(`RSTACK Version: ${frame.buffer[1]} Reason: ${code.toString()} frame: ${frame}`, NS);
if (NcpResetCode[code].toString() !== NcpResetCode.RESET_SOFTWARE.toString()) {
return;
}
this.waitress.resolve({ sequence: -1 });
}
async handleError(frame) {
logger_1.logger.debug(`<-- Error ${frame}`, NS);
try {
// send reset
await this.reset();
}
catch (error) {
logger_1.logger.error(`Failed to reset on Error Frame: ${error}`, NS);
}
}
async reset() {
logger_1.logger.debug("Uart reseting", NS);
this.parser.reset();
this.queue.clear();
this.sendSeq = 0;
this.recvSeq = 0;
return await this.queue.execute(async () => {
try {
logger_1.logger.debug("--> Write reset", NS);
const waiter = this.waitFor(-1, 10000);
this.rejectCondition = false;
this.writer.sendReset();
logger_1.logger.debug("-?- waiting reset", NS);
await waiter.start().promise;
logger_1.logger.debug("-+- waiting reset success", NS);
await (0, utils_1.wait)(2000);
}
catch (e) {
logger_1.logger.error(`--> Error: ${e}`, NS);
this.emit("reset");
throw new Error(`Reset error: ${e}`);
}
});
}
async close(emitClose) {
logger_1.logger.debug("Closing UART", NS);
this.queue.clear();
if (this.initialized) {
this.initialized = false;
if (this.serialPort) {
try {
await this.serialPort.asyncFlushAndClose();
}
catch (error) {
if (emitClose) {
this.emit("close");
}
throw error;
}
}
else {
// biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress`
this.socketPort.destroy();
}
}
if (emitClose) {
this.emit("close");
}
}
onPortError(error) {
logger_1.logger.error(`Port error: ${error}`, NS);
}
onPortClose(err) {
logger_1.logger.debug(`Port closed. Error? ${err}`, NS);
// on error: serialport passes an Error object (in case of disconnect)
// net.Socket passes a boolean (in case of a transmission error)
// try to reset instead of failing immediately
if (err != null && err !== false) {
this.emit("reset");
}
else {
this.initialized = false;
this.emit("close");
}
}
isInitialized() {
return this.initialized;
}
async sendDATA(data) {
const seq = this.sendSeq;
this.sendSeq = (seq + 1) % 8; // next
const nextSeq = this.sendSeq;
const ackSeq = this.recvSeq;
return await this.queue.execute(async () => {
const randData = frame_1.Frame.makeRandomizedBuffer(data);
try {
const waiter = this.waitFor(nextSeq);
logger_1.logger.debug(`--> DATA (${seq},${ackSeq},0): ${data.toString("hex")}`, NS);
this.writer.sendData(randData, seq, 0, ackSeq);
logger_1.logger.debug(`-?- waiting (${nextSeq})`, NS);
await waiter.start().promise;
logger_1.logger.debug(`-+- waiting (${nextSeq}) success`, NS);
}
catch (e1) {
logger_1.logger.error(`--> Error: ${e1}`, NS);
logger_1.logger.error(`-!- break waiting (${nextSeq})`, NS);
logger_1.logger.error(`Can't send DATA frame (${seq},${ackSeq},0): ${data.toString("hex")}`, NS);
try {
await (0, utils_1.wait)(500);
const waiter = this.waitFor(nextSeq);
logger_1.logger.debug(`->> DATA (${seq},${ackSeq},1): ${data.toString("hex")}`, NS);
this.writer.sendData(randData, seq, 1, ackSeq);
logger_1.logger.debug(`-?- rewaiting (${nextSeq})`, NS);
await waiter.start().promise;
logger_1.logger.debug(`-+- rewaiting (${nextSeq}) success`, NS);
}
catch (e2) {
logger_1.logger.error(`--> Error: ${e2}`, NS);
logger_1.logger.error(`-!- break rewaiting (${nextSeq})`, NS);
logger_1.logger.error(`Can't resend DATA frame (${seq},${ackSeq},1): ${data.toString("hex")}`, NS);
if (this.initialized) {
this.emit("reset");
}
throw new Error(`sendDATA error: try 1: ${e1}, try 2: ${e2}`);
}
}
});
}
waitFor(sequence, timeout = 4000) {
return this.waitress.waitFor({ sequence }, timeout);
}
waitressTimeoutFormatter(matcher, timeout) {
return `${JSON.stringify(matcher)} after ${timeout}ms`;
}
waitressValidator(payload, matcher) {
return payload.sequence === matcher.sequence;
}
}
exports.SerialDriver = SerialDriver;
//# sourceMappingURL=uart.js.map
;