neramirez-broadlink-ts
Version:
A TypeScript-enhanced Node.JS fork of broadlinkjs, designed for interacting with RM devices in homebridge-broadlink-rm. Now includes a feature for handling multiple requests to the same device, with a specific focus on supporting homebridge-broadlink-wind
129 lines • 6.77 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketHandler = void 0;
const dgram_1 = __importDefault(require("dgram"));
const crypto_1 = __importDefault(require("crypto"));
const device_types_1 = require("./device.types");
const payload_handler_1 = require("./types/payload.handler");
class SocketHandler {
constructor(logger, host, macAddress, deviceType, packetHandler) {
this.handleMessage = (response) => {
if (response.length < 0x39)
return;
const requestId = response.readUInt16LE(0x28);
const encryptedPayload = Buffer.alloc(response.length - 0x38, 0);
response.copy(encryptedPayload, 0, 0x38);
const err = response[0x22] | (response[0x23] << 8);
if (err != 0)
return;
const decipher = crypto_1.default.createDecipheriv("aes-128-cbc", this.packetHandler.getKey(), this.packetHandler.getIv());
decipher.setAutoPadding(false);
let payload = decipher.update(encryptedPayload);
const { resolve, reject, timeout, startTime, queueItem } = this.promises[requestId];
clearTimeout(timeout);
delete this.promises[requestId];
// Calculate the time difference
const endTime = process.hrtime.bigint();
const timeDiff = Number(endTime - startTime) / 1e6; // convert to milliseconds
this.logger.info(`Response received: ${requestId}, Time taken: ${timeDiff} ms`);
const p2 = decipher.final();
if (p2)
payload = Buffer.concat([payload, p2]);
if (!payload) {
reject("No Payload");
queueItem === null || queueItem === void 0 ? void 0 : queueItem.promiseExecutor.reject("No Payload");
}
this.logger.info(`Response received: ${requestId}`);
const command = response[0x26];
if (command == 0xe9) {
this.packetHandler.updateKey(Buffer.alloc(0x10, 0));
payload.copy(this.packetHandler.getKey(), 0, 0x04, 0x14);
const id = Buffer.alloc(0x04, 0);
payload.copy(id, 0, 0x00, 0x04);
this.packetHandler.setId(id);
resolve(payload);
queueItem === null || queueItem === void 0 ? void 0 : queueItem.promiseExecutor.resolve(payload);
//this.emit("deviceReady");
}
else if (command == 0xee || command == 0xef) {
const payloadHex = payload.toString("hex");
const requestHeaderHex = this.request_header.toString("hex");
const indexOfHeader = payloadHex.indexOf(requestHeaderHex);
if (indexOfHeader > -1) {
payload = payload.subarray(indexOfHeader + this.request_header.length, payload.length);
}
this.onPayloadReceived(err, payload);
resolve(payload);
queueItem === null || queueItem === void 0 ? void 0 : queueItem.promiseExecutor.resolve(payload);
}
else if (command == 0x72) {
this.logger.info("Command Acknowledged");
}
else {
this.logger.info("Unhandled Command: ", command);
}
};
this.sendPacket = async (command, packet, requestId, queueItem) => {
return await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`Timeout: handleMessage for ${requestId} was not called within the specified time.`));
delete this.promises[requestId]; // remove the promise as it's no longer needed
}, 5000); // 5000 milliseconds = 5 seconds
const startTime = process.hrtime.bigint();
this.promises[requestId] = { startTime, resolve, reject, timeout, queueItem };
this.socket.send(packet, 0, packet.length, this.host.port, this.host.address, (err, _bytes) => {
if (err) {
this.logger.debug("send packet error", err);
}
else {
this.logger.debug(`Packet sent to ${this.host.address}:${this.host.port} with command 0x${command.toString(16)}`);
this.logger.debug(`MAC Address: ${this.macAddress.toString("hex")}`);
this.logger.debug(`Payload: ${packet.toString("hex")}`);
}
});
});
};
this.setupSocket = () => {
const socket = dgram_1.default.createSocket({ type: "udp4", reuseAddr: true });
socket.on("listening", () => {
socket.setBroadcast(true);
const address = socket.address();
this.logger.debug(`Socket is listening on ${address.address}:${address.port}`);
});
socket.on("error", (err) => {
this.logger.error(`Error in UDP socket: ${err}`);
});
return socket.bind();
};
this.onPayloadReceived = (_err, payload) => {
this.logger.debug(`(${this.macAddress.toString("hex")}) Payload received:${payload.toString("hex")}`);
const param = payload[0];
const PayloadHandlerClass = payload_handler_1.payloadHandlers[param];
if (PayloadHandlerClass) {
const handlerInstance = new PayloadHandlerClass(this.rm4Type);
handlerInstance.handle(payload);
}
};
this.logger = logger;
this.host = host;
this.socket = this.setupSocket();
this.macAddress = macAddress;
// this.requestCounter = 0;
this.promises = {};
this.packetHandler = packetHandler;
this.socket.on("message", this.handleMessage.bind(this));
this.rm4Type = (device_types_1.rm4DeviceTypes[(deviceType)] || device_types_1.rm4PlusDeviceTypes[deviceType]);
this.request_header = this.rm4Type ? Buffer.from([0x04, 0x00]) : Buffer.from([]);
this.code_sending_header = this.rm4Type ? Buffer.from([0xda, 0x00]) : Buffer.from([]);
//except 5f36 and 6508 ¯\_(ツ)_/¯
if (deviceType === parseInt(`0x5f36`) || deviceType === parseInt(`0x6508`)) {
this.code_sending_header = Buffer.from([0xd0, 0x00]);
this.request_header = Buffer.from([0x04, 0x00]);
}
}
}
exports.SocketHandler = SocketHandler;
//# sourceMappingURL=socket.handler.js.map