iobroker.gree-hvac
Version:
Adapter for Gree and C&H conditioners
253 lines (252 loc) • 8.99 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var connection_exports = {};
__export(connection_exports, {
Connection: () => Connection
});
module.exports = __toCommonJS(connection_exports);
var dgram = __toESM(require("node:dgram"));
var import_node_events = require("node:events");
var import_encryptor = require("./encryptor");
const commandsMap = {
bind: "bindok",
status: "dat",
cmd: "res"
};
class Connection extends import_node_events.EventEmitter {
requestTimeoutMs;
logger;
socket;
socketScan;
devices = {};
deviceEncVer = {};
/**
*
*/
constructor(address, logger, requestTimeoutMs = 1e3) {
super();
this.logger = logger;
this.requestTimeoutMs = requestTimeoutMs;
this.socket = dgram.createSocket("udp4");
this.socketScan = dgram.createSocket("udp4");
this.socketScan.on("message", this.handleResponse.bind(this));
this.socketScan.on("listening", () => {
const socketAddress = this.socketScan.address();
this.logger.info(`Socket server is listening on ${socketAddress.address}:${socketAddress.port}`);
this.scan(address);
});
this.socket.on("error", (error) => {
this.logger.error(error.message);
});
this.socketScan.on("error", (error) => {
this.logger.error(error.message);
});
this.socket.bind();
this.socketScan.bind();
}
/**
*
*/
registerKey(deviceId, key) {
this.logger.debug(`Registering key: ${deviceId} - ${key}`);
this.devices[deviceId] = key;
}
/**
*
*/
getEncryptionKey(deviceId) {
return this.devices[deviceId] || import_encryptor.defaultKey;
}
/**
*
*/
registerEncVersion(deviceId, encVer) {
this.logger.debug(`Registering encVer: ${deviceId} - ${encVer}`);
this.deviceEncVer[deviceId] = encVer;
}
/**
*
*/
getEncVersion(deviceId) {
return this.deviceEncVer[deviceId] || 1;
}
/**
*
*/
scan(ipAddresses) {
const message = Buffer.from(JSON.stringify({ cid: "app", t: "scan", i: 1, uid: 0 }));
ipAddresses.split(";").forEach((deviceAddress) => {
this.logger.debug(`Test address ${deviceAddress} for available device with message: ${message}`);
this.socketScan.send(message, 0, message.length, 7e3, deviceAddress);
});
}
/**
*
*/
async sendRequest(address, port, payload, encVersion) {
return new Promise((resolve, reject) => {
var _a;
let requestTimeout;
try {
const key = this.getEncryptionKey(address);
const effectiveEncVersion = encVersion != null ? encVersion : this.getEncVersion(address);
let pack;
let tag;
if (effectiveEncVersion === 1) {
pack = (0, import_encryptor.encryptV1)(payload, key);
} else {
const { encPack, encTag } = (0, import_encryptor.encryptV2)(payload, key);
pack = encPack;
tag = encTag;
}
this.logger.debug(`Payload: ${JSON.stringify(payload)}`);
this.logger.debug(`key: ${key}`);
this.logger.debug(`pack: ${pack}`);
this.logger.debug(`tag: ${tag}`);
this.logger.debug(`encVersion: ${effectiveEncVersion}`);
const request = {
cid: "app",
i: payload.t === "bind" ? 1 : key === import_encryptor.defaultKey ? 1 : 0,
t: "pack",
uid: 0,
tcid: payload.mac,
pack,
tag
};
const messageHandler = (msg, rinfo) => {
var _a2;
const message = JSON.parse(msg.toString());
this.logger.debug(
`Received message from ${message.cid} (${rinfo.address}:${rinfo.port}) ${msg.toString()} - ${JSON.stringify(rinfo)}`
);
if (rinfo.address !== address || rinfo.port !== port) {
return;
}
const decKey = this.getEncryptionKey(rinfo.address);
const decTag = message.tag;
let resolvedEncVersion = decTag != void 0 ? 2 : 1;
if (resolvedEncVersion === 2) {
this.registerEncVersion(rinfo.address, resolvedEncVersion);
} else {
resolvedEncVersion = this.getEncVersion(rinfo.address);
}
this.logger.debug(`decKey: ${decKey}`);
this.logger.debug(`decTag: ${decTag}`);
this.logger.debug(`encVersion: ${resolvedEncVersion}`);
let response;
try {
if (resolvedEncVersion === 1) {
response = (0, import_encryptor.decryptV1)(message.pack, decKey);
} else {
response = (0, import_encryptor.decryptV2)(message.pack, decKey, decTag);
}
this.logger.debug(`sendRequest - Response data: ${JSON.stringify(response)}`);
} catch (e) {
this.logger.error(
`Can not decrypt message from ${message.cid} (${rinfo.address}:${rinfo.port}) with key ${decKey} and tag ${decTag}`
);
this.logger.error((_a2 = e.stack) != null ? _a2 : String(e));
return;
}
if (response.t !== commandsMap[payload.t]) {
this.logger.debug(`No matching command: ${response.t}`);
return;
}
if (response.mac !== payload.mac) {
this.logger.debug(`No matching mac ${response.mac} - ${payload.mac}`);
return;
}
this.socket.off("message", messageHandler);
clearTimeout(requestTimeout);
resolve(response);
};
this.logger.debug(`Sending request to ${address}:${port}: ${JSON.stringify(request)}`);
this.socket.on("message", messageHandler);
const toSend = Buffer.from(JSON.stringify(request));
this.socket.send(toSend, 0, toSend.length, port, address);
requestTimeout = setTimeout(() => {
this.socket.off("message", messageHandler);
clearTimeout(requestTimeout);
reject(new Error(`Request to ${address}:${port} timed out`));
}, this.requestTimeoutMs);
} catch (e) {
this.logger.error((_a = e.stack) != null ? _a : String(e));
reject(e instanceof Error ? e : new Error(String(e)));
}
});
}
/**
*
*/
close() {
this.socket.close();
this.socketScan.close();
}
/**
*
*/
handleResponse(msg, rinfo) {
var _a;
let message;
try {
message = JSON.parse(msg.toString());
} catch {
this.logger.error(`Device ${rinfo.address}:${rinfo.port} sent invalid JSON that can not be parsed`);
this.logger.debug(String(msg));
return;
}
const tag = message.tag;
const encVersion = tag != void 0 ? 2 : 1;
const key = encVersion === 1 ? import_encryptor.defaultKey : import_encryptor.defaultKeyGCM;
this.logger.silly(`key: ${key}`);
this.logger.silly(`tag: ${tag}`);
this.logger.silly(`encVersion: ${encVersion}`);
let response;
try {
if (encVersion === 1) {
response = (0, import_encryptor.decryptV1)(message.pack, key);
} else {
response = (0, import_encryptor.decryptV2)(message.pack, key, tag);
}
this.logger.debug(`handleResponse - Response data: ${JSON.stringify(response)}`);
} catch (e) {
this.logger.error(
`handleResponse - Can not decrypt message from ${message.cid} (${rinfo.address}:${rinfo.port}) with key ${key} and tag ${tag}`
);
this.logger.error((_a = e.stack) != null ? _a : String(e));
return;
}
this.emit(response.t, response, rinfo, encVersion);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Connection
});
//# sourceMappingURL=connection.js.map