UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

901 lines (792 loc) 33.9 kB
/* v8 ignore start */ import events from "node:events"; import net from "node:net"; import slip from "slip"; import {logger} from "../../../utils/logger"; import {SerialPort} from "../../serialPort"; import SocketPortUtils from "../../socketPortUtils"; import PARAM, {type ApsDataRequest, type ParameterT, type ReceivedDataResponse, type Request} from "./constants"; import {frameParserEvents} from "./frameParser"; import Parser from "./parser"; import Writer from "./writer"; const NS = "zh:deconz:driver"; const queue: Array<Request> = []; export const busyQueue: Array<Request> = []; const apsQueue: Array<Request> = []; export const apsBusyQueue: Array<Request> = []; const apsConfirmIndQueue: Array<Request> = []; export let readyToSend = true; export function enableRTS(): void { if (readyToSend === false) { readyToSend = true; } } export function disableRTS(): void { readyToSend = false; } export let enableRtsTimeout: NodeJS.Timeout | undefined; class Driver extends events.EventEmitter { private path: string; private serialPort?: SerialPort; private initialized: boolean; private writer: Writer; private parser: Parser; private frameParserEvent = frameParserEvents; private seqNumber: number; private timeoutResetTimeout?: NodeJS.Timeout; private apsRequestFreeSlots: number; private apsDataConfirm: number; private apsDataIndication: number; private configChanged: number; private socketPort?: net.Socket; private delay: number; private readyToSendTimeout: number; private handleDeviceStatusDelay: number; private processQueues: number; private timeoutCounter = 0; private currentBaudRate = 0; public constructor(path: string) { super(); this.path = path; this.initialized = false; this.seqNumber = 0; this.timeoutResetTimeout = undefined; this.apsRequestFreeSlots = 1; this.apsDataConfirm = 0; this.apsDataIndication = 0; this.configChanged = 0; this.delay = 0; this.readyToSendTimeout = 1; this.handleDeviceStatusDelay = 5; this.processQueues = 5; this.writer = new Writer(); this.parser = new Parser(); setInterval(() => { this.deviceStateRequest() .then(() => {}) .catch(() => {}); }, 10000); setInterval( () => { this.writeParameterRequest(0x26, 600) // reset watchdog // 10 minutes .then(() => {}) .catch(() => { //try again logger.debug("try again to reset watchdog", NS); this.writeParameterRequest(0x26, 600) .then(() => {}) .catch(() => { logger.debug("warning watchdog was not reset", NS); }); }); }, 1000 * 60 * 8, ); // 8 minutes this.onParsed = this.onParsed.bind(this); this.frameParserEvent.on("receivedDataNotification", (data: number) => { this.checkDeviceStatus(data); }); this.on("close", () => { for (const interval of this.intervals) { clearInterval(interval); } queue.length = 0; busyQueue.length = 0; apsQueue.length = 0; apsBusyQueue.length = 0; apsConfirmIndQueue.length = 0; this.timeoutCounter = 0; }); } protected intervals: NodeJS.Timeout[] = []; protected registerInterval(interval: NodeJS.Timeout): void { this.intervals.push(interval); } protected async catchPromise<T>(val: Promise<T>): Promise<undefined | Awaited<T>> { return (await Promise.resolve(val).catch((err) => logger.debug(`Promise was caught with reason: ${err}`, NS))) as undefined | Awaited<T>; } public setDelay(delay: number): void { logger.debug(`Set delay to ${delay}`, NS); this.delay = delay; this.readyToSendTimeout = delay; this.processQueues = delay; this.handleDeviceStatusDelay = delay; if (this.readyToSendTimeout === 0) { this.readyToSendTimeout = 1; } if (this.processQueues < 5) { this.processQueues = 5; } if (this.handleDeviceStatusDelay < 5) { this.handleDeviceStatusDelay = 5; } if (this.processQueues > 60) { this.processQueues = 60; } if (this.handleDeviceStatusDelay > 60) { this.handleDeviceStatusDelay = 60; } this.registerInterval( setInterval(() => { this.processQueue(); }, this.processQueues), ); // fire non aps requests this.registerInterval( setInterval(async () => { await this.catchPromise(this.processBusyQueue()); }, this.processQueues), ); // check timeouts for non aps requests this.registerInterval( setInterval(async () => { await this.catchPromise(this.processApsQueue()); }, this.processQueues), ); // fire aps request this.registerInterval( setInterval(() => { this.processApsBusyQueue(); }, this.processQueues), ); // check timeouts for all open aps requests this.registerInterval( setInterval(() => { this.processApsConfirmIndQueue(); }, this.processQueues), ); // fire aps indications and confirms this.registerInterval( setInterval(async () => { await this.catchPromise(this.handleDeviceStatus()); }, this.handleDeviceStatusDelay), ); // query confirm and indication requests } private onPortClose(): void { logger.debug("Port closed", NS); this.initialized = false; this.emit("close"); } public async open(baudrate: number): Promise<void> { this.currentBaudRate = baudrate; return await (SocketPortUtils.isTcpPath(this.path) ? this.openSocketPort() : this.openSerialPort(baudrate)); } public openSerialPort(baudrate: number): Promise<void> { logger.debug(`Opening with ${this.path}`, NS); this.serialPort = new SerialPort({path: this.path, baudRate: baudrate, autoOpen: false}); //38400 RaspBee //115200 ConBee3 this.writer.pipe(this.serialPort); this.serialPort.pipe(this.parser); this.parser.on("parsed", this.onParsed); return new Promise((resolve, reject): void => { // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.serialPort!.open((error) => { if (error) { reject(new Error(`Error while opening serialport '${error}'`)); this.initialized = false; // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` if (this.serialPort!.isOpen) { // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.serialPort!.close(); } } else { logger.debug("Serialport opened", NS); this.initialized = true; resolve(); } }); }); } private async openSocketPort(): Promise<void> { const info = SocketPortUtils.parseTcpPath(this.path); logger.debug(`Opening TCP socket with ${info.host}:${info.port}`, NS); this.socketPort = new net.Socket(); this.socketPort.setNoDelay(true); this.socketPort.setKeepAlive(true, 15000); this.writer = new Writer(); this.writer.pipe(this.socketPort); this.parser = new Parser(); this.socketPort.pipe(this.parser); this.parser.on("parsed", this.onParsed); return await new Promise((resolve, reject): void => { // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.socketPort!.on("connect", () => { logger.debug("Socket connected", NS); }); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.socketPort!.on("ready", () => { logger.debug("Socket ready", NS); this.initialized = true; resolve(); }); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.socketPort!.once("close", this.onPortClose); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.socketPort!.on("error", (error) => { logger.error(`Socket error ${error}`, NS); reject(new Error("Error while opening socket")); this.initialized = false; }); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.socketPort!.connect(info.port, info.host); }); } public close(): Promise<void> { return new Promise((resolve, reject): void => { if (this.initialized) { if (this.serialPort) { this.serialPort.flush((): void => { // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.serialPort!.close((error): void => { this.initialized = false; if (error == null) { resolve(); } else { reject(new Error(`Error while closing serialport '${error}'`)); } this.emit("close"); }); }); } else { // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.socketPort!.destroy(); resolve(); } } else { resolve(); this.emit("close"); } }); } public readParameterRequest(parameterId: number): Promise<unknown> { const seqNumber = this.nextSeqNumber(); return new Promise((resolve, reject): void => { //logger.debug(`push read parameter request to queue. seqNr: ${seqNumber} paramId: ${parameterId}`, NS); const ts = 0; const commandId = PARAM.PARAM.FrameType.ReadParameter; const req: Request = {commandId, parameterId, seqNumber, resolve, reject, ts}; queue.push(req); }); } public writeParameterRequest(parameterId: number, parameter: ParameterT): Promise<void> { const seqNumber = this.nextSeqNumber(); return new Promise((resolve, reject): void => { //logger.debug(`push write parameter request to queue. seqNr: ${seqNumber} paramId: ${parameterId} parameter: ${parameter}`, NS); const ts = 0; const commandId = PARAM.PARAM.FrameType.WriteParameter; const req: Request = {commandId, parameterId, parameter, seqNumber, resolve, reject, ts}; queue.push(req); }); } public async writeLinkKey(ieeeAddress: string, hashedKey: Buffer): Promise<void> { await this.writeParameterRequest(PARAM.PARAM.Network.LINK_KEY, [...this.macAddrStringToArray(ieeeAddress), ...hashedKey]); } public readFirmwareVersionRequest(): Promise<number[]> { const seqNumber = this.nextSeqNumber(); return new Promise((resolve, reject): void => { //logger.debug(`push read firmware version request to queue. seqNr: ${seqNumber}`, NS); const ts = 0; const commandId = PARAM.PARAM.FrameType.ReadFirmwareVersion; const req: Request = {commandId, seqNumber, resolve, reject, ts}; queue.push(req); }); } private sendReadParameterRequest(parameterId: number, seqNumber: number): void { /* command id, sequence number, 0, framelength(U16), payloadlength(U16), parameter id */ if (parameterId === PARAM.PARAM.Network.NETWORK_KEY) { this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadParameter, seqNumber, 0x00, 0x09, 0x00, 0x02, 0x00, parameterId, 0x00])); } else { this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadParameter, seqNumber, 0x00, 0x08, 0x00, 0x01, 0x00, parameterId])); } } private sendWriteParameterRequest(parameterId: number, value: ParameterT, seqNumber: number): void { /* command id, sequence number, 0, framelength(U16), payloadlength(U16), parameter id, pameter */ let parameterLength = 0; if (parameterId === PARAM.PARAM.STK.Endpoint) { const arrayParameterValue = value as number[]; parameterLength = arrayParameterValue.length; } else { parameterLength = this.getLengthOfParameter(parameterId); } //logger.debug("SEND WRITE_PARAMETER Request - parameter id: " + parameterId + " value: " + value.toString(16) + " length: " + parameterLength, NS); const payloadLength = 1 + parameterLength; const frameLength = 7 + payloadLength; const fLength1 = frameLength & 0xff; const fLength2 = frameLength >> 8; const pLength1 = payloadLength & 0xff; const pLength2 = payloadLength >> 8; if (parameterId === PARAM.PARAM.Network.NETWORK_KEY) { this.sendRequest( Buffer.from([PARAM.PARAM.FrameType.WriteParameter, seqNumber, 0x00, 0x19, 0x00, 0x12, 0x00, parameterId, 0x00].concat(value)), ); } else { this.sendRequest( Buffer.from([ PARAM.PARAM.FrameType.WriteParameter, seqNumber, 0x00, fLength1, fLength2, pLength1, pLength2, parameterId, ...this.parameterBuffer(value, parameterLength), ]), ); } } private getLengthOfParameter(parameterId: number): number { switch (parameterId) { case 9: case 16: case 21: case 28: case 33: case 36: return 1; case 5: case 7: case 34: return 2; case 10: case 38: return 4; case 1: case 8: case 11: case 14: return 8; case 24: case 25: return 16; default: return 0; } } private parameterBuffer(parameter: ParameterT, parameterLength: number): Buffer { if (typeof parameter === "number") { // for parameter <= 4 Byte if (parameterLength > 4) throw new Error("parameter to big for type number"); const buf = Buffer.alloc(parameterLength); buf.writeUIntLE(parameter, 0, parameterLength); return buf; } return Buffer.from(parameter.reverse()); } private sendReadFirmwareVersionRequest(seqNumber: number): void { /* command id, sequence number, 0, framelength(U16) */ this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadFirmwareVersion, seqNumber, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00])); } private sendReadDeviceStateRequest(seqNumber: number): void { /* command id, sequence number, 0, framelength(U16) */ this.sendRequest(Buffer.from([PARAM.PARAM.FrameType.ReadDeviceState, seqNumber, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00])); } private sendRequest(buffer: Buffer): void { const frame = Buffer.concat([buffer, this.calcCrc(buffer)]); const slipframe = slip.encode(frame); // TODO: write not awaited? if (this.serialPort) { this.serialPort.write(slipframe, (err) => { if (err) { logger.debug(`Error writing serial Port: ${err.message}`, NS); } }); } else { // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.socketPort!.write(slipframe, (err) => { if (err) { logger.debug(`Error writing socket Port: ${err.message}`, NS); } }); } } private processQueue(): void { if (queue.length === 0) { return; } if (busyQueue.length > 0) { return; } const req = queue.shift(); if (req) { req.ts = Date.now(); switch (req.commandId) { case PARAM.PARAM.FrameType.ReadParameter: logger.debug(`send read parameter request from queue. seqNr: ${req.seqNumber} paramId: ${req.parameterId}`, NS); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.sendReadParameterRequest(req.parameterId!, req.seqNumber); break; case PARAM.PARAM.FrameType.WriteParameter: logger.debug( `send write parameter request from queue. seqNr: ${req.seqNumber} paramId: ${req.parameterId} param: ${req.parameter}`, NS, ); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.sendWriteParameterRequest(req.parameterId!, req.parameter!, req.seqNumber); break; case PARAM.PARAM.FrameType.ReadFirmwareVersion: logger.debug(`send read firmware version request from queue. seqNr: ${req.seqNumber}`, NS); this.sendReadFirmwareVersionRequest(req.seqNumber); break; case PARAM.PARAM.FrameType.ReadDeviceState: logger.debug(`send read device state from queue. seqNr: ${req.seqNumber}`, NS); this.sendReadDeviceStateRequest(req.seqNumber); break; case PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE: logger.debug(`send change network state request from queue. seqNr: ${req.seqNumber}`, NS); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.sendChangeNetworkStateRequest(req.seqNumber, req.networkState!); break; default: throw new Error("process queue - unknown command id"); } busyQueue.push(req); } } private async processBusyQueue(): Promise<void> { let i = busyQueue.length; while (i--) { const req: Request = busyQueue[i]; const now = Date.now(); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` if (now - req.ts! > 10000) { logger.debug(`Timeout for request - CMD: 0x${req.commandId.toString(16)} seqNr: ${req.seqNumber}`, NS); //remove from busyQueue busyQueue.splice(i, 1); this.timeoutCounter++; // after a timeout the timeoutcounter will be reset after 1 min. If another timeout happen then the timeoutcounter // will not be reset clearTimeout(this.timeoutResetTimeout); this.timeoutResetTimeout = undefined; this.resetTimeoutCounterAfter1min(); req.reject(new Error("TIMEOUT")); if (this.timeoutCounter >= 2) { this.timeoutCounter = 0; logger.debug("too many timeouts - restart serial connecion", NS); if (this.serialPort?.isOpen) { this.serialPort.close(); } if (this.socketPort) { this.socketPort.destroy(); } await this.open(this.currentBaudRate); } } } } public changeNetworkStateRequest(networkState: number): Promise<void> { const seqNumber = this.nextSeqNumber(); return new Promise((resolve, reject): void => { //logger.debug(`push change network state request to apsQueue. seqNr: ${seqNumber}`, NS); const ts = 0; const commandId = PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE; const req: Request = {commandId, networkState, seqNumber, resolve, reject, ts}; queue.push(req); }); } private sendChangeNetworkStateRequest(seqNumber: number, networkState: number): void { this.sendRequest(Buffer.from([PARAM.PARAM.NetworkState.CHANGE_NETWORK_STATE, seqNumber, 0x00, 0x06, 0x00, networkState])); } private async deviceStateRequest(): Promise<void> { const seqNumber = this.nextSeqNumber(); return await new Promise((resolve, reject): void => { //logger.debug(`DEVICE_STATE Request - seqNr: ${seqNumber}`, NS); const ts = 0; const commandId = PARAM.PARAM.FrameType.ReadDeviceState; const req: Request = {commandId, seqNumber, resolve, reject, ts}; queue.push(req); }); } private checkDeviceStatus(currentDeviceStatus: number): void { const networkState = currentDeviceStatus & 0x03; this.apsDataConfirm = (currentDeviceStatus >> 2) & 0x01; this.apsDataIndication = (currentDeviceStatus >> 3) & 0x01; this.configChanged = (currentDeviceStatus >> 4) & 0x01; this.apsRequestFreeSlots = (currentDeviceStatus >> 5) & 0x01; logger.debug( `networkstate: ${networkState} apsDataConfirm: ${this.apsDataConfirm} apsDataIndication: ${this.apsDataIndication} configChanged: ${this.configChanged} apsRequestFreeSlots: ${this.apsRequestFreeSlots}`, NS, ); } private async handleDeviceStatus(): Promise<void> { if (this.apsDataConfirm === 1) { try { logger.debug("query aps data confirm", NS); this.apsDataConfirm = 0; await this.querySendDataStateRequest(); } catch (error) { // @ts-expect-error TODO: this doesn't look right? if (error.status === 5) { this.apsDataConfirm = 0; } } } if (this.apsDataIndication === 1) { try { logger.debug("query aps data indication", NS); this.apsDataIndication = 0; await this.readReceivedDataRequest(); } catch (error) { // @ts-expect-error TODO: this doesn't look right? if (error.status === 5) { this.apsDataIndication = 0; } } } if (this.configChanged === 1) { // when network settings changed } } // DATA_IND private readReceivedDataRequest(): Promise<void> { const seqNumber = this.nextSeqNumber(); return new Promise((resolve, reject): void => { //logger.debug(`push read received data request to apsQueue. seqNr: ${seqNumber}`, NS); const ts = 0; const commandId = PARAM.PARAM.APS.DATA_INDICATION; const req: Request = {commandId, seqNumber, resolve, reject, ts}; apsConfirmIndQueue.push(req); }); } // DATA_REQ public enqueueSendDataRequest(request: ApsDataRequest): Promise<undefined | ReceivedDataResponse> { const seqNumber = this.nextSeqNumber(); return new Promise((resolve, reject): void => { //logger.debug(`push enqueue send data request to apsQueue. seqNr: ${seqNumber}`, NS); const ts = 0; const commandId = PARAM.PARAM.APS.DATA_REQUEST; const req: Request = {commandId, seqNumber, request, resolve, reject, ts}; apsQueue.push(req); }); } // DATA_CONF private querySendDataStateRequest(): Promise<void> { const seqNumber = this.nextSeqNumber(); return new Promise((resolve, reject): void => { //logger.debug(`push query send data state request to apsQueue. seqNr: ${seqNumber}`, NS); const ts = 0; const commandId = PARAM.PARAM.APS.DATA_CONFIRM; const req: Request = {commandId, seqNumber, resolve, reject, ts}; apsConfirmIndQueue.push(req); }); } private async processApsQueue(): Promise<void> { if (apsQueue.length === 0) { return; } if (this.apsRequestFreeSlots !== 1) { logger.debug("no free slots. Delay sending of APS Request", NS); await this.sleep(1000); return; } const req = apsQueue.shift(); if (req) { req.ts = Date.now(); switch (req.commandId) { case PARAM.PARAM.APS.DATA_REQUEST: if (readyToSend === false) { // wait until last request was confirmed or given time elapsed logger.debug("delay sending of APS Request", NS); apsQueue.unshift(req); break; } disableRTS(); enableRtsTimeout = setTimeout(() => { enableRTS(); }, this.readyToSendTimeout); apsBusyQueue.push(req); // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` this.sendEnqueueSendDataRequest(req.request!, req.seqNumber); break; default: throw new Error("process APS queue - unknown command id"); } } } private processApsConfirmIndQueue(): void { if (apsConfirmIndQueue.length === 0) { return; } const req = apsConfirmIndQueue.shift(); if (req) { req.ts = Date.now(); apsBusyQueue.push(req); switch (req.commandId) { case PARAM.PARAM.APS.DATA_INDICATION: //logger.debug(`read received data request. seqNr: ${req.seqNumber}`, NS); if (this.delay === 0) { this.sendReadReceivedDataRequest(req.seqNumber); } else { this.sendReadReceivedDataRequest(req.seqNumber); } break; case PARAM.PARAM.APS.DATA_CONFIRM: //logger.debug(`query send data state request. seqNr: ${req.seqNumber}`, NS); if (this.delay === 0) { this.sendQueryDataStateRequest(req.seqNumber); } else { this.sendQueryDataStateRequest(req.seqNumber); } break; default: throw new Error("process APS Confirm/Ind queue - unknown command id"); } } } private sendQueryDataStateRequest(seqNumber: number): void { logger.debug(`DATA_CONFIRM - sending data state request - SeqNr. ${seqNumber}`, NS); this.sendRequest(Buffer.from([PARAM.PARAM.APS.DATA_CONFIRM, seqNumber, 0x00, 0x07, 0x00, 0x00, 0x00])); } private sendReadReceivedDataRequest(seqNumber: number): void { logger.debug(`DATA_INDICATION - sending read data request - SeqNr. ${seqNumber}`, NS); // payloadlength = 0, flag = none this.sendRequest(Buffer.from([PARAM.PARAM.APS.DATA_INDICATION, seqNumber, 0x00, 0x08, 0x00, 0x01, 0x00, 0x01])); } private sendEnqueueSendDataRequest(request: ApsDataRequest, seqNumber: number): void { const payloadLength = 12 + (request.destAddrMode === PARAM.PARAM.addressMode.GROUP_ADDR ? 2 : request.destAddrMode === PARAM.PARAM.addressMode.NWK_ADDR ? 3 : 9) + request.asduLength; const frameLength = 7 + payloadLength; const cid1 = request.clusterId & 0xff; const cid2 = (request.clusterId >> 8) & 0xff; const asdul1 = request.asduLength & 0xff; const asdul2 = (request.asduLength >> 8) & 0xff; let destArray: Array<number> = []; let dest = ""; if (request.destAddr16 !== undefined) { destArray[0] = request.destAddr16 & 0xff; destArray[1] = (request.destAddr16 >> 8) & 0xff; dest = request.destAddr16.toString(16); } if (request.destAddr64 !== undefined) { dest = request.destAddr64; destArray = this.macAddrStringToArray(request.destAddr64); } if (request.destEndpoint !== undefined) { destArray.push(request.destEndpoint); dest += " EP:"; dest += request.destEndpoint; } logger.debug(`DATA_REQUEST - destAddr: 0x${dest} SeqNr. ${seqNumber} request id: ${request.requestId}`, NS); this.sendRequest( Buffer.from([ PARAM.PARAM.APS.DATA_REQUEST, seqNumber, 0x00, frameLength & 0xff, (frameLength >> 8) & 0xff, payloadLength & 0xff, (payloadLength >> 8) & 0xff, request.requestId, 0x00, request.destAddrMode, ...destArray, request.profileId & 0xff, (request.profileId >> 8) & 0xff, cid1, cid2, request.srcEndpoint, asdul1, asdul2, ...request.asduPayload, request.txOptions, request.radius, ]), ); } private processApsBusyQueue(): void { let i = apsBusyQueue.length; while (i--) { const req = apsBusyQueue[i]; const now = Date.now(); let timeout = 60000; if (req.request != null && req.request.timeout != null) { timeout = req.request.timeout * 1000; // seconds * 1000 = milliseconds } // biome-ignore lint/style/noNonNullAssertion: ignored using `--suppress` if (now - req.ts! > timeout) { logger.debug(`Timeout for aps request CMD: 0x${req.commandId.toString(16)} seq: ${req.seqNumber}`, NS); //remove from busyQueue apsBusyQueue.splice(i, 1); req.reject(new Error("APS TIMEOUT")); } } } private calcCrc(buffer: Uint8Array): Buffer { let crc = 0; for (let i = 0; i < buffer.length; i++) { crc += buffer[i]; } const crc0 = (~crc + 1) & 0xff; const crc1 = ((~crc + 1) >> 8) & 0xff; return Buffer.from([crc0, crc1]); } public macAddrStringToArray(addr: string): Array<number> { if (addr.indexOf("0x") === 0) { addr = addr.slice(2, addr.length); } if (addr.length < 16) { for (let l = 0; l < 16 - addr.length; l++) { addr = `0${addr}`; } } const result = new Array<number>(); let y = 0; for (let i = 0; i < 8; i++) { result[i] = Number.parseInt(addr.substr(y, 2), 16); y += 2; } const reverse = result.reverse(); return reverse; } public macAddrArrayToString(addr: Array<number>): string { if (addr.length !== 8) { throw new Error(`invalid array length for MAC address: ${addr.length}`); } let result = "0x"; let char = ""; let i = 8; while (i--) { char = addr[i].toString(16); if (char.length < 2) { char = `0${char}`; } result += char; } return result; } /** * generalArrayToString result is not reversed! */ public generalArrayToString(key: Array<number>, length: number): string { let result = "0x"; let char = ""; let i = 0; while (i < length) { char = key[i].toString(16); if (char.length < 2) { char = `0${char}`; } result += char; i++; } return result; } private nextSeqNumber(): number { this.seqNumber++; if (this.seqNumber > 254) { this.seqNumber = 1; } return this.seqNumber; } private onParsed(frame: Uint8Array): void { this.emit("rxFrame", frame); } private sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } private resetTimeoutCounterAfter1min(): void { if (this.timeoutResetTimeout === undefined) { this.timeoutResetTimeout = setTimeout(() => { this.timeoutCounter = 0; this.timeoutResetTimeout = undefined; }, 60000); } } } export default Driver;