zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
901 lines (792 loc) • 33.9 kB
text/typescript
/* 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;