UNPKG

iobroker.gree-hvac

Version:
596 lines (595 loc) 23.8 kB
"use strict"; 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 __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 utils = __toESM(require("@iobroker/adapter-core")); var import_device_manager = require("./lib/device_manager"); var import_properties_map = __toESM(require("./lib/properties_map")); var import_device_state = require("./lib/device-state"); var import_adapter_utils = __toESM(require("./lib/adapter-utils")); const MinPollInterval = 1e3; const MaxPollInterval = 6e4; const CheckDevicesTimeoutMs = 1e3; const CheckDevicesTimeout = "CheckDevices"; const MinCelsiusTemperature = 16; const MaxCelsiusTemperature = 30; const MinFahrenheitTemperature = 60; const MaxFahrenheitTemperature = 86; class GreeHvac extends utils.Adapter { deviceManager; timeouts = {}; activeDevices = []; constructor(options) { super({ ...options, name: "gree-hvac" }); try { this.on("ready", this.onReady.bind(this)); this.on("stateChange", this.onStateChange.bind(this)); this.on("message", this.onMessage.bind(this)); this.on("unload", this.onUnload.bind(this)); } catch (error) { this.log.error(`Error in constructor: ${error}`); this.sendError(error, "Error in constructor"); } } async onReady() { var _a, _b; try { this.log.info(`Device list: ${JSON.stringify(this.config.devicelist)}`); this.log.info(`Poll interval: ${this.config.pollInterval}`); if (!this.config.devicelist || this.config.devicelist.length === 0 || !import_adapter_utils.default.validateIPList(this.config.devicelist)) { this.log.error(`Invalid device list: ${JSON.stringify(this.config.devicelist)}`); void ((_a = this.stop) == null ? void 0 : _a.call(this)); return; } if (this.config.pollInterval < MinPollInterval || isNaN(this.config.pollInterval) || this.config.pollInterval > MaxPollInterval) { this.log.error(`Invalid poll interval: ${this.config.pollInterval}`); void ((_b = this.stop) == null ? void 0 : _b.call(this)); return; } await this.setStateAsync("info.connection", { val: false, ack: true }); const adapterObjects = await this.getAdapterObjectsAsync(); for (const key in adapterObjects) { if (Object.prototype.hasOwnProperty.call(adapterObjects, key) && key.endsWith(".alive") === true) { await this.setStateAsync(key, { val: false, ack: true }); } } const devicesArray = this.config.devicelist.map((item) => item.deviceIp); const devices = devicesArray.join(";"); this.subscribeStates("*"); this.deviceManager = new import_device_manager.DeviceManager(devices, this.log, this.config.requestTimeoutMs); this.deviceManager.on("device_bound", async (deviceId, device) => { try { await this.processDevice(deviceId, device); await this.pollDevices(deviceId, true); } catch (error) { this.log.error(`Error in device_bound event for device ${deviceId}: ${error}`); } }); this.checkDevices(); } catch (error) { this.log.error(`Error in onReady: ${error}`); this.sendError(error, "Error in onReady"); } } async pollDevices(deviceId, isFirst) { if (isFirst) { try { await this.getDeviceStatus(deviceId); } catch { } } this.timeouts[deviceId] = this.setTimeout(async () => { try { await this.getDeviceStatus(deviceId); this.clearTimeout(this.timeouts[deviceId]); } catch { } await this.pollDevices(deviceId, false); }, this.config.pollInterval); } checkDevices() { this.timeouts[CheckDevicesTimeout] = this.setTimeout(async () => { const allActive = this.activeDevices.length > 0 && this.activeDevices.every((device) => device.isActive === true); await this.setStateAsync("info.connection", { val: allActive, ack: true }); this.clearTimeout(this.timeouts[CheckDevicesTimeout]); this.checkDevices(); }, CheckDevicesTimeoutMs); } async getDeviceStatus(deviceId) { const deviceItem = this.activeDevices.find((device) => device.id === deviceId); try { const deviceStatus = await this.deviceManager.getDeviceStatus(deviceId); if (deviceItem && deviceItem.isActive === false) { this.log.info(`Device ${deviceId} is responding again`); deviceItem.isActive = true; } if (deviceItem) { deviceItem.lastSeen = /* @__PURE__ */ new Date(); } void this.processDeviceStatus(deviceId, deviceStatus); } catch (error) { await this.setStateAsync(`${deviceId}.alive`, { val: false, ack: true }); if (deviceItem && deviceItem.isActive === true) { this.log.error(`Error in getDeviceStatus for device ${deviceId}: ${error}`); deviceItem.isActive = false; } } } async processDeviceStatus(deviceId, deviceStatus) { try { deviceId = this.nameToId(deviceId); await this.setStateAsync(`${deviceId}.alive`, { val: true, ack: true }); for (const key in deviceStatus) { if (Object.prototype.hasOwnProperty.call(deviceStatus, key)) { let value = deviceStatus[key]; const mapItem = import_properties_map.default.find((item) => item.hvacName === key); if (!mapItem) { this.log.warn(`Property ${key} not found in the map`); continue; } value = import_adapter_utils.default.mapValue(value, mapItem); value = import_adapter_utils.default.convertValue(deviceStatus, value, mapItem); await this.setStateAsync(`${deviceId}.${mapItem.name}`, { val: value, ack: true }); } } } catch (error) { this.log.error(`Error in processDeviceStatus for device ${deviceId}: ${error}`); this.sendError(error, `Error in processDeviceStatus for device ${deviceId}`); } } async processDevice(deviceId, device) { try { this.activeDevices.push(new import_device_state.DeviceState(deviceId)); deviceId = this.nameToId(deviceId); this.log.info(`Device ${deviceId} bound`); await this.setObjectNotExistsAsync(deviceId, { type: "device", common: { name: deviceId }, native: {} }); await this.setObjectNotExistsAsync(`${deviceId}.deviceInfo`, { type: "state", common: { name: "Device Info", type: "string", role: "info", read: true, write: false }, native: {} }); await this.setStateAsync(`${deviceId}.deviceInfo`, { val: JSON.stringify(device), ack: true }); await this.setObjectNotExistsAsync(`${deviceId}.alive`, { type: "state", common: { name: "Is alive", type: "boolean", read: true, write: false, role: "indicator.state" }, native: {} }); await this.setStateAsync(`${deviceId}.alive`, { val: true, ack: true }); for (const property of import_properties_map.default) { try { const propertyObjectName = `${deviceId}.${property.name}`; if (await this.objectExists(propertyObjectName) === true) { const propertyObject = await this.getObjectAsync(propertyObjectName); if (propertyObject && import_adapter_utils.default.areObjectsTheSame(propertyObject, JSON.parse(property.definition)) === false) { await this.delObjectAsync(propertyObjectName); await this.setObjectNotExistsAsync(propertyObjectName, JSON.parse(property.definition)); } } else { await this.setObjectNotExistsAsync(propertyObjectName, JSON.parse(property.definition)); } } catch (error) { this.log.error(`Error in processDevice for device ${deviceId}: ${error}`); this.log.error(`Property ${property.name}, definition '${property.definition}'`); this.sendError(error, `Property ${property.name}, definition '${property.definition}'`); } } } catch (error) { this.log.error(`Error in processDevice for device ${deviceId}: ${error}`); this.sendError(error, `Error in processDevice for device ${deviceId}`); } } nameToId(pName) { return (pName || "").replace(this.FORBIDDEN_CHARS, "_"); } onUnload(callback) { try { for (const deviceId in this.timeouts) { this.clearTimeout(this.timeouts[deviceId]); } if (this.deviceManager) { this.deviceManager.close(); } callback(); } catch (error) { this.log.error(`Error in onUnload: ${error}`); this.sendError(error, "Error in onUnload"); callback(); } } async onStateChange(id, state) { try { if (state && state.ack === false) { const deviceInfo = this.getDeviceInfo(id); if (!deviceInfo) { return; } const { deviceId, devicePath, property } = deviceInfo; if (property === "temperature-unit") { await this.onTemperatureUnitChange(devicePath, state.val); } const mapItem = import_properties_map.default.find((item) => item.name === property); if (mapItem) { const payload = await this.createPayload(devicePath); const cmdResult = await this.deviceManager.setDeviceState(deviceId, payload); if (cmdResult) { await this.processDeviceStatus(deviceId, cmdResult); } } } } catch (error) { this.log.error(`Error in onStateChange for state ${id}: ${error}`); this.sendError(error, `Error in onStateChange for state ${id}`); } } async onTemperatureUnitChange(devicePath, temperatureUnit) { var _a, _b, _c, _d; const temperature = Number((_b = (_a = await this.getStateAsync(`${devicePath}.target-temperature`)) == null ? void 0 : _a.val) != null ? _b : 0); const roomTemperature = Number((_d = (_c = await this.getStateAsync(`${devicePath}.room-temperature`)) == null ? void 0 : _c.val) != null ? _d : 0); if (temperatureUnit === 0) { let celsius = import_adapter_utils.default.fahrenheitToCelsius(temperature); await this.setStateAsync(`${devicePath}.target-temperature`, celsius, true); celsius = import_adapter_utils.default.fahrenheitToCelsius(roomTemperature); await this.setStateAsync(`${devicePath}.room-temperature`, celsius, true); } else { let fahrenheit = import_adapter_utils.default.celsiusToFahrenheit(temperature); await this.setStateAsync(`${devicePath}.target-temperature`, fahrenheit, true); fahrenheit = import_adapter_utils.default.celsiusToFahrenheit(roomTemperature); await this.setStateAsync(`${devicePath}.room-temperature`, fahrenheit, true); } } async createPayload(deviceId) { var _a; try { let payload = {}; for (const property of import_properties_map.default.filter((e) => !e.isReadOnly())) { if (await this.objectExists(`${deviceId}.${property.name}`) === true) { const state = await this.getStateAsync(`${deviceId}.${property.name}`); if (state && state.val !== null) { const definition = JSON.parse(property.definition); if ((_a = definition.native) == null ? void 0 : _a.valuesMap) { const valuesMap = definition.native.valuesMap; const valueMap = valuesMap.find((item) => item.value === state.val); if (valueMap) { payload[property.hvacName] = valueMap.targetValue; } else { payload[property.hvacName] = state.val; } } else { payload[property.hvacName] = state.val; } } } } for (const property of import_properties_map.default.filter((e) => e.toConverter !== null)) { if (property.toConverter) { payload = property.toConverter(payload); } } return payload; } catch (error) { this.log.error(`Error in createPayload: ${error}`); this.sendError(error, "Error in createPayload"); return {}; } } getDeviceInfo(id) { try { const parts = id.split("."); const deviceId = parts[2]; const devicePath = parts.slice(0, -1).join("."); const property = parts[parts.length - 1]; return { deviceId, devicePath, property }; } catch (error) { this.log.error(`Error in getDeviceInfo: ${error}`); this.sendError(error, `Error in getDeviceInfo, id: ${id}`); return null; } } async onMessage(obj) { this.log.debug(`Received message ${JSON.stringify(obj)}`); if (typeof obj === "object" && obj.message) { switch (obj.command) { case "getDevices": await this.processGetDevicesCommand(obj); break; case "remoteCommand": await this.processRemoteCommand(obj); break; case "renameDevice": await this.processRenameDevice(obj); break; default: { this.log.warn(`Unknown command ${obj.command}`); const result = { error: `Unknown command ${obj.command}` }; if (obj.callback) { this.sendTo(obj.from, obj.command, result, obj.callback); } break; } } } } async processRenameDevice(obj) { const result = {}; try { const msg = obj.message; const deviceId = msg.deviceId; const deviceName = msg.name; const deviceObject = await this.getObjectAsync(deviceId); if (!deviceObject) { this.log.warn(`Device ${deviceId} not found`); return; } await this.extendObjectAsync(deviceId, { common: { name: deviceName } }); this.log.info(`Device ${deviceObject.common.name} renamed to ${deviceName}`); result.result = { deviceId, name: deviceName }; } catch (error) { result.error = error.message; } if (obj.callback) { this.sendTo(obj.from, obj.command, result, obj.callback); } } async processRemoteCommand(obj) { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p; const result = {}; try { const msg = obj.message; const command = msg.command; const deviceId = msg.deviceId; let state; const powerState = (_a = await this.getStateAsync(`${deviceId}.power`)) == null ? void 0 : _a.val; const isAlive = (_b = await this.getStateAsync(`${deviceId}.alive`)) == null ? void 0 : _b.val; const temperatureUnit = (_d = (_c = await this.getStateAsync(`${deviceId}.temperature-unit`)) == null ? void 0 : _c.val) != null ? _d : 0; let minTemperature = MinCelsiusTemperature; let maxTemperature = MaxCelsiusTemperature; if (temperatureUnit === 1) { minTemperature = MinFahrenheitTemperature; maxTemperature = MaxFahrenheitTemperature; } if (isAlive === false) { throw new Error("Device is not responding"); } let newState; switch (command) { case "on-off-btn": newState = powerState === 1 ? 0 : 1; await this.setStateAsync(`${deviceId}.power`, newState); break; case "temperature-up-btn": if (powerState === 0) { throw new Error("Device power is off"); } state = (_f = (_e = await this.getStateAsync(`${deviceId}.target-temperature`)) == null ? void 0 : _e.val) != null ? _f : 0; newState = Number(state) + 1; if (newState > maxTemperature) { newState = maxTemperature; } await this.setStateAsync(`${deviceId}.target-temperature`, newState); break; case "temperature-down-btn": if (powerState === 0) { throw new Error("Device power is off"); } state = (_h = (_g = await this.getStateAsync(`${deviceId}.target-temperature`)) == null ? void 0 : _g.val) != null ? _h : 0; newState = Number(state) - 1; if (newState < minTemperature) { newState = minTemperature; } await this.setStateAsync(`${deviceId}.target-temperature`, newState); break; case "mode-btn": if (powerState === 0) { throw new Error("Device power is off"); } state = (_j = (_i = await this.getStateAsync(`${deviceId}.mode`)) == null ? void 0 : _i.val) != null ? _j : 0; newState = Number(state) + 1; if (newState > 4) { newState = 0; } await this.setStateAsync(`${deviceId}.mode`, newState); break; case "fan-btn": if (powerState === 0) { throw new Error("Device power is off"); } state = (_l = (_k = await this.getStateAsync(`${deviceId}.fan-speed`)) == null ? void 0 : _k.val) != null ? _l : 0; newState = Number(state) + 1; if (newState > 4) { newState = 0; } await this.setStateAsync(`${deviceId}.fan-speed`, newState); break; case "turbo-btn": if (powerState === 0) { throw new Error("Device power is off"); } state = (_n = (_m = await this.getStateAsync(`${deviceId}.turbo`)) == null ? void 0 : _m.val) != null ? _n : 0; newState = Number(state) + 1; if (newState > 1) { newState = 0; } await this.setStateAsync(`${deviceId}.turbo`, newState); break; case "display-btn": state = (_p = (_o = await this.getStateAsync(`${deviceId}.display-state`)) == null ? void 0 : _o.val) != null ? _p : 0; newState = Number(state) + 1; if (newState > 1) { newState = 0; } await this.setStateAsync(`${deviceId}.display-state`, newState); break; case "temperature-unit-btn": newState = temperatureUnit === 1 ? 0 : 1; await this.setStateAsync(`${deviceId}.temperature-unit`, newState); break; } result.result = "Ok"; } catch (error) { result.error = error.message; } if (obj.callback) { this.sendTo(obj.from, obj.command, result, obj.callback); } } async processGetDevicesCommand(obj) { const result = {}; try { const allObjects = await this.getAdapterObjectsAsync(); const deviceObjects = Object.keys(allObjects).map((key) => ({ id: key, value: allObjects[key] })).filter((item) => item.id.split(".").length === 3 && item.value.type === "device").map((item) => ({ id: item.id, name: item.value.common.name })); let devices = await this.collectDeviceInfo(deviceObjects); devices = devices.sort((a, b) => { var _a, _b, _c, _d; const nameA = typeof a.name === "string" ? a.name : (_b = (_a = a.name) == null ? void 0 : _a.en) != null ? _b : ""; const nameB = typeof b.name === "string" ? b.name : (_d = (_c = b.name) == null ? void 0 : _c.en) != null ? _d : ""; return nameA > nameB ? 1 : nameB > nameA ? -1 : 0; }); result.result = devices; } catch (error) { result.error = error.message; } if (obj.callback) { this.sendTo(obj.from, obj.command, result, obj.callback); } } async collectDeviceInfo(deviceObjects) { var _a, _b, _c, _d, _e; const devicesInfo = []; for (let i = 0; i < deviceObjects.length; i++) { const deviceItem = deviceObjects[i]; const deviceInfoState = ((_b = (_a = await this.getStateAsync(`${deviceItem.id}.deviceInfo`)) == null ? void 0 : _a.val) != null ? _b : "{}").toString(); const deviceObject = JSON.parse(deviceInfoState); const deviceInfo = { id: deviceObject.mac, ip: deviceObject.address, name: deviceItem.name, alive: null }; for (let j = 0; j < import_properties_map.default.length; j++) { try { const property = import_properties_map.default[j]; const state = (_c = await this.getStateAsync(`${deviceItem.id}.${property.name}`)) == null ? void 0 : _c.val; deviceInfo[property.name] = state; } catch { } } const aliveState = (_e = (_d = await this.getStateAsync(`${deviceItem.id}.alive`)) == null ? void 0 : _d.val) != null ? _e : false; deviceInfo.alive = aliveState; devicesInfo.push(deviceInfo); } return devicesInfo; } isNetworkError(error) { const networkErrorCodes = /* @__PURE__ */ new Set([ "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "ENETUNREACH", "EHOSTUNREACH", "ENETDOWN", "ENOTFOUND", "ENONET", "ECONNABORTED", "EADDRNOTAVAIL", "EADDRINUSE" ]); if (error instanceof Error) { const code = error.code; if (code && networkErrorCodes.has(code)) { return true; } const msg = error.message.toLowerCase(); if (msg.includes("timed out") || msg.includes("socket") || msg.includes("network")) { return true; } } return false; } sendError(error, message) { var _a, _b, _c, _d, _e; if (this.isNetworkError(error)) { return; } try { const adapter = this; if (adapter.supportsFeature && adapter.supportsFeature("PLUGINS")) { const sentryInstance = (_a = adapter.getPluginInstance) == null ? void 0 : _a.call(adapter, "sentry"); if (sentryInstance) { const Sentry = (_b = sentryInstance.getSentryObject) == null ? void 0 : _b.call(sentryInstance); if (Sentry) { if (message) { (_c = Sentry.configureScope) == null ? void 0 : _c.call(Sentry, (scope) => { var _a2, _b2; (_b2 = scope.addBreadcrumb) == null ? void 0 : _b2.call(scope, { type: "error", category: "error message", level: (_a2 = Sentry.Severity) == null ? void 0 : _a2.Error, message }); }); } if (typeof error === "string") { (_d = Sentry.captureException) == null ? void 0 : _d.call(Sentry, new Error(error)); } else { (_e = Sentry.captureException) == null ? void 0 : _e.call(Sentry, error); } } } } } catch (err) { console.error("Error in sendError:", err); } } } if (require.main !== module) { module.exports = (options) => new GreeHvac(options); } else { new GreeHvac(); } //# sourceMappingURL=main.js.map