UNPKG

js-broadlink

Version:
343 lines 12.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Device = exports.Discover = exports.Setup = exports.SecurityMode = void 0; const dgram = __importStar(require("dgram")); const crypto = __importStar(require("crypto")); const tricklejs_1 = require("tricklejs"); const os_1 = require("os"); var SecurityMode; (function (SecurityMode) { SecurityMode[SecurityMode["None"] = 0] = "None"; SecurityMode[SecurityMode["WEP"] = 1] = "WEP"; SecurityMode[SecurityMode["WPA1"] = 2] = "WPA1"; SecurityMode[SecurityMode["WPA2"] = 3] = "WPA2"; SecurityMode[SecurityMode["WPA1or2"] = 4] = "WPA1or2"; })(SecurityMode = exports.SecurityMode || (exports.SecurityMode = {})); function Setup(ssid, password, securityMode) { return new Promise(function (resolve, reject) { const socket = dgram.createSocket({ type: "udp4", reuseAddr: true, }); socket.on("listening", function () { socket.setBroadcast(true); const packet = Buffer.alloc(136, 0); packet.writeUInt8(0x14, 0x26); for (let i = 0; i < ssid.length; i++) { packet.writeUInt8(ssid[i].charCodeAt(0), 0x44 + i); } for (let i = 0; i < password.length; i++) { packet.writeUInt8(password[i].charCodeAt(0), 0x64 + i); } packet.writeUInt8(ssid.length, 0x84); packet.writeUInt8(password.length, 0x85); packet.writeUInt8(securityMode, 0x86); packet.writeUInt16LE(checksum(packet), 0x20); socket.send(packet, 80, "255.255.255.255", function (err) { if (err) { socket.close(); return reject(err); } socket.close(); resolve(); }); }); socket.bind(); }); } exports.Setup = Setup; function Discover(timeout = 0, localIP) { const sc = new tricklejs_1.StreamController(); const socket = dgram.createSocket({ type: "udp4", reuseAddr: true }); sc.onCancel = () => socket.close(); socket.on("listening", function () { socket.setBroadcast(true); const now = new Date(); const packet = Buffer.alloc(48, 0); const port = socket.address().port; packet.writeUInt32LE((now.getTimezoneOffset() / -60) & 0xffff, 0x8); packet.writeUInt16LE(now.getFullYear(), 0xc); packet.writeUInt8(now.getMinutes(), 0x0e); packet.writeUInt8(now.getHours(), 0x0f); packet.writeUInt8(now.getFullYear() % 100, 0x10); packet.writeUInt8(now.getDay(), 0x11); packet.writeUInt8(now.getDate(), 0x12); packet.writeUInt8(now.getMonth() + 1, 0x13); if (!localIP) { const ni = os_1.networkInterfaces(); const ips = Object.keys(ni).reduce((ips, name) => { const net = ni[name]; return ips.concat(net.filter((n) => n.family === "IPv4" && !n.internal).map((n) => n.address)); }, []); localIP = ips[0]; } localIP .split(".") .map((part) => parseInt(part, 10)) .forEach((part, idx) => packet.writeUInt8(part, 0x18 + idx)); packet.writeUInt16LE(port, 0x1c); packet.writeUInt8(6, 0x26); packet.writeUInt16LE(checksum(packet), 0x20); socket.send(packet, 0, packet.length, 80, "255.255.255.255", function (err) { if (err) { sc.addError(err); } }); }); socket.on("message", function (msg, rinfo) { const deviceType = msg[0x34] | (msg[0x35] << 8); const mac = msg.slice(0x3a, 0x40); const device = new Device(deviceType, mac, rinfo.address, rinfo.port); sc.add(device); }); Promise.resolve().then(() => socket.bind()); if (timeout) setTimeout(sc.close.bind(sc), timeout); return sc.stream; } exports.Discover = Discover; class Device { constructor(_deviceType, _mac, _host, _port) { this._deviceType = _deviceType; this._host = _host; this._port = _port; this._authed = false; this._count = crypto.randomBytes(2).readUInt16BE(); this._id = Buffer.alloc(4, 0); this._key = Buffer.from([ 0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02, ]); this._iv = Buffer.from([ 0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, ]); if (typeof _mac === "string") { this._mac = Buffer.from(_mac .split(":") .map((part) => parseInt(part, 16)) .reverse()); } else { this._mac = _mac; } } _auth() { if (this._authed) return Promise.resolve(); return new Promise(async (resolve, reject) => { // prepare packet const payload = Buffer.alloc(80, 0); payload.writeUInt8(1, 0x1e); payload.writeUInt8(1, 0x2d); const mac = this.macAddress; Buffer.from(`===${mac.split(":").join("")}`, "utf8").copy(payload, 4, 0); Buffer.from(mac).copy(payload, 0x30, 0); const packet = this._getPacket(0x65, payload); // get auth keys const data = await this._sendPacket(packet); try { this._checkError(data); const decipher = crypto.createDecipheriv("aes-128-cbc", this._key, this._iv); decipher.setAutoPadding(false); const deciphered = Buffer.concat([decipher.update(data.slice(0x38)), decipher.final()]); this._id = deciphered.slice(0, 4); this._key = deciphered.slice(4, 0x14); this._authed = true; resolve(); } catch (e) { reject(e); } }); } _getPacket(command, payload) { const header = Buffer.alloc(56, 0); const staticHeader = Buffer.from([0x5a, 0xa5, 0xaa, 0x55, 0x5a, 0xa5, 0xaa, 0x55]); this._count = (this._count + 1) & 0xffff; staticHeader.copy(header, 0, 0); header.writeUInt16LE(this._deviceType, 0x24); header.writeUInt16LE(command, 0x26); header.writeUInt16LE(this._count, 0x28); this._mac.copy(header, 0x2a, 0); this._id.copy(header, 0x30, 0); header.writeUInt16LE(checksum(payload), 0x34); // checksum const packet = Buffer.concat([header, this.encrypt(payload)]); packet.writeUInt16LE(checksum(packet), 0x20); return packet; } _sendPacket(packet) { return new Promise((resolve, reject) => { const socket = dgram.createSocket({ type: "udp4", reuseAddr: true, }); socket.on("message", (data) => { socket.close(); resolve(data); }); socket.on("listening", () => { socket.send(packet, this._port, this._host, function (err) { if (err) { socket.close(); reject(err); } }); }); socket.bind(); }); } _checkError(data) { if (data.length < 48) { throw new Error("invalid length"); } const cs = data.readUInt16LE(0x20); data.writeUInt16LE(0, 0x20); if (checksum(data) !== cs) { throw new Error("checksum error"); } const ecode = data.readUInt16LE(0x22); if (ecode) { throw new Error(`error: code= ${ecode}`); } } encrypt(data) { const encryptor = crypto.createCipheriv("aes-128-cbc", this._key, this._iv); encryptor.setAutoPadding(false); data = Buffer.concat([data, Buffer.alloc(16 - (data.length % 16), 0)]); return Buffer.concat([encryptor.update(data), encryptor.final()]); } decrypt(encrypted) { const decryptor = crypto.createDecipheriv("aes-128-cbc", this._key, this._iv); decryptor.setAutoPadding(false); return Buffer.concat([decryptor.update(encrypted), decryptor.final()]); } enterLearning() { return new Promise(async (resolve) => { await this._auth(); const payload = Buffer.alloc(16, 0); payload[0] = 3; const packet = this._getPacket(0x6a, payload); await this._sendPacket(packet); resolve(); }); } exitLearning() { return new Promise(async (resolve) => { await this._auth(); const payload = Buffer.alloc(16, 0); payload[0] = 30; const packet = this._getPacket(0x6a, payload); await this._sendPacket(packet); resolve(); }); } checkData() { return new Promise(async (resolve, reject) => { try { await this._auth(); const payload = Buffer.alloc(16, 0); payload[0] = 4; const packet = this._getPacket(0x6a, payload); const response = await this._sendPacket(packet); this._checkError(response); const data = this.decrypt(response.slice(0x38)).slice(4); resolve(data); } catch (e) { reject(e); } }); } sendData(d) { return new Promise(async (resolve) => { await this._auth(); const header = Buffer.from([2, 0, 0, 0]); const payload = Buffer.concat([header, d]); const packet = this._getPacket(0x6a, payload); await this._sendPacket(packet); resolve(); }); } checkTemperature() { return new Promise(async (resolve, reject) => { try { await this._auth(); const payload = Buffer.from([1]); const packet = this._getPacket(0x6a, payload); const response = await this._sendPacket(packet); this._checkError(response); const data = this.decrypt(response.slice(0x38)).slice(4); resolve(data[0] + data[1] / 10.0); } catch (e) { reject(e); } }); } get deviceType() { return this._deviceType; } get macAddress() { const pad = (s) => (s.length < 2 ? `0${s}` : s); return [this._mac[5], this._mac[4], this._mac[3], this._mac[2], this._mac[1], this._mac[0]] .map((b) => pad(b.toString(16))) .join(":"); } get hostport() { return `${this._host}:${this._port}`; } } exports.Device = Device; function checksum(b) { return b.reduce((cs, byte) => (cs + byte) & 0xffff, 0xbeaf); } //# sourceMappingURL=broadlink.js.map