UNPKG

iobroker.melcloud

Version:
1,384 lines (1,244 loc) 51.9 kB
"use strict"; const commonDefines = require("./commonDefines"); const HttpStatus = require("http-status-codes"); const Axios = require("axios").default; let gthat = null; // pointer to "this" from main.js/MelCloud instance class MelcloudAtaDevice { constructor(that) { gthat = that; this.platform = null; this.airInfo = null; this.deviceInfoRequestQueue = []; this.currentDeviceInfoRequests = 0; this.deviceSetRequestQueue = []; this.currentDeviceSetRequests = 0; this.hasBeenCreated = false; this.deviceType = commonDefines.DeviceTypes.AirToAir; // Info this.id = -1; this.name = ""; this.serialNumber = ""; this.macAddress = ""; this.buildingId = -1; this.floorId = -1; this.canCool = false; this.canHeat = false; this.canDry = false; this.minTempCoolDry = 0; this.maxTempCoolDry = 0; this.minTempHeat = 0; this.maxTempHeat = 0; this.minTempAuto = 0; this.maxTempAuto = 0; this.roomTemp = 0; this.actualFanSpeed = 0; this.numberOfFanSpeeds = 0; this.lastCommunication = null; this.nextCommunication = null; this.deviceOnline = false; this.deviceHasError = false; this.errorMessages = ""; this.errorCode = 8000; // Control this.power = false; this.operationMode = commonDefines.AtaDeviceOperationModes.UNDEF.value; this.targetTemp = 0; this.fanSpeed = 0; this.vaneVerticalDirection = 0; this.vaneHorizontalDirection = 0; this.timerToggle = false; // Reports this.powerConsumptionReportStartDate = ""; this.powerConsumptionReportEndDate = ""; this.totalPowerConsumptionCooling = 0; this.totalPowerConsumptionHeating = 0; this.totalPowerConsumptionAuto = 0; this.totalPowerConsumptionDry = 0; this.totalPowerConsumptionVent = 0; this.totalPowerConsumptionMinutes = 0; this.rawPowerConsumptionReportData = null; this.linkedDevicesIncludedInArregateEnergyReport = ""; } // Creates all necessery states and channels and writes the values into the DB async CreateAndSave() { // check if object has already been created if (this.hasBeenCreated) return; const devicePrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}`; await gthat.extendObjectAsync(devicePrefix, { type: "device", common: { statusStates: { onlineId: `${gthat.namespace}.${devicePrefix}.${commonDefines.AdapterDatapointIDs.Info}.${commonDefines.CommonDeviceStateIDs.DeviceOnline}`, errorId: `${gthat.namespace}.${devicePrefix}.${commonDefines.AdapterDatapointIDs.Info}.${commonDefines.CommonDeviceStateIDs.DeviceHasError}` }, name: `ATA Device ${this.id} (${this.name})` }, native: {}, }); //#region INFO let infoPrefix = `${devicePrefix}.${commonDefines.AdapterDatapointIDs.Info}`; await gthat.extendObjectAsync(infoPrefix, { type: "channel", common: { name: "Device information" }, native: {} }); infoPrefix += "."; await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.DeviceName, { type: "state", common: { name: "Device name", type: "string", role: "info.name", read: true, write: false, def: this.name, desc: "MELCloud device name" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.DeviceType, { type: "state", common: { name: "Device type", type: "number", role: "value", states: { 0: "Air to air heat pump / air conditioner", 1: "Air to water heat pump" }, read: true, write: false, def: this.deviceType, desc: "MELCloud device type" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.SerialNumber, { type: "state", common: { name: "Serial number", type: "string", role: "value", read: true, write: false, def: this.serialNumber, desc: "Serial number of the device" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.MacAddress, { type: "state", common: { name: "MAC address", type: "string", role: "info.mac", read: true, write: false, def: this.macAddress, desc: "MAC address of the device" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.BuildingId, { type: "state", common: { name: "Building ID", type: "number", role: "value", read: true, write: false, def: this.buildingId, desc: "MELCloud building ID" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.FloorId, { type: "state", common: { name: "Floor ID", type: "number", role: "value", read: true, write: false, def: this.floorId, desc: "MELCloud floor ID" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.CanCool, { type: "state", common: { name: "Ability to cool", type: "boolean", role: "value", read: true, write: false, def: this.canCool, desc: "Ability to cool" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.CanHeat, { type: "state", common: { name: "Ability to heat", type: "boolean", role: "value", read: true, write: false, def: this.canHeat, desc: "Ability to heat" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.CanDry, { type: "state", common: { name: "Ability to dry", type: "boolean", role: "value", read: true, write: false, def: this.canDry, desc: "Ability to dry" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MinTempCoolDry, { type: "state", common: { name: "Minimal temperature (Cool/Dry)", type: "number", role: "value.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", read: true, write: false, def: this.minTempCoolDry, desc: "Minimal temperature in cool/dry-mode" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MaxTempCoolDry, { type: "state", common: { name: "Maximal temperature (Cool/Dry)", type: "number", role: "value.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", read: true, write: false, def: this.maxTempCoolDry, desc: "Maximal temperature in cool/dry-mode" }, native: {} }); if (this.canHeat) { await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MinTempHeat, { type: "state", common: { name: "Minimal temperature (Heat)", type: "number", role: "value.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", read: true, write: false, def: this.minTempHeat, desc: "Minimal temperature in heat-mode" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MaxTempHeat, { type: "state", common: { name: "Maximal temperature (Heat)", type: "number", role: "value.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", read: true, write: false, def: this.maxTempHeat, desc: "Maximal temperature in heat-mode" }, native: {} }); } await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MinTempAuto, { type: "state", common: { name: "Minimal Temperature (Auto)", type: "number", role: "value.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", read: true, write: false, def: this.minTempAuto, desc: "Minimal temperature in auto-mode" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MaxTempAuto, { type: "state", common: { name: "Maximal Temperature (Auto)", type: "number", role: "value.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", read: true, write: false, def: this.maxTempAuto, desc: "Maximal temperature in auto-mode" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.RoomTemp, { type: "state", common: { name: "Room temperature", type: "number", role: "value.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", read: true, write: false, def: this.roomTemp, desc: "Maximal temperature in auto-mode" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.FanSpeedAuto, { type: "state", common: { name: "Fan speed (while in auto mode)", type: "number", role: "value", read: true, write: false, def: this.actualFanSpeed, desc: "Actual fan speed when fan is set to auto mode" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.NumberOfFanSpeeds, { type: "state", common: { name: "Number of fan speeds", type: "number", role: "value", read: true, write: false, def: this.numberOfFanSpeeds, desc: "Number of available fan speeds" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.LastCommunication, { type: "state", common: { name: "Last communication", type: "string", role: "date", read: true, write: false, def: this.lastCommunication, desc: "Last communication date/time (MELCloud to device)" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.NextCommunication, { type: "state", common: { name: "Next communication", type: "string", role: "date", read: true, write: false, def: this.nextCommunication, desc: "Next communication date/time (MELCloud to device)" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.DeviceOnline, { type: "state", common: { name: "Is device online", type: "boolean", role: "indicator.reachable", read: true, write: false, def: this.deviceOnline, desc: "Indicates if device is reachable" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.DeviceHasError, { type: "state", common: { name: "Has device an error", type: "boolean", role: "indicator.maintenance.alarm", read: true, write: false, def: this.deviceHasError, desc: "Indicates if device has an error" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.ErrorMessages, { type: "state", common: { name: "Error messages", type: "string", role: "value", read: true, write: false, def: this.errorMessages, desc: "Current error messages" }, native: {} }); await gthat.extendObjectAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.ErrorCode, { type: "state", common: { name: "Error code", type: "number", role: "value", read: true, write: false, def: this.errorCode, desc: "Current error code", states: { 8000: "No error" }, }, native: {} }); //#endregion //#region CONTROL let controlPrefix = `${devicePrefix}.${commonDefines.AdapterDatapointIDs.Control}`; await gthat.extendObjectAsync(controlPrefix, { type: "channel", common: { name: "Device control" }, native: {} }); controlPrefix += "."; await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.Power, { type: "state", common: { name: "Power", type: "boolean", role: "switch.power", read: true, write: true, def: this.power, desc: "Power switch" }, native: {} }); await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.Mode, { type: "state", common: { name: "Operation mode", type: "number", role: "value", read: true, write: true, def: this.operationMode, desc: "Operation mode of the device", states: { 1: "HEAT", 2: "DRY", 3: "COOL", 7: "VENT", 8: "AUTO" } }, native: {} }); const minTemp = Math.min(this.minTempAuto, this.minTempCoolDry, this.minTempHeat); const maxTemp = Math.max(this.maxTempAuto, this.maxTempCoolDry, this.maxTempHeat); await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.TargetTemp, { type: "state", common: { name: "Target temperature", type: "number", role: "level.temperature", unit: this.platform.UseFahrenheit ? "°F" : "°C", min: minTemp, max: maxTemp, step: 0.5, read: true, write: true, def: this.targetTemp, desc: "Target temperature of the device" }, native: {} }); await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.TargetTemp, { common: { step: 0.5 } }); await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.FanSpeedManual, { type: "state", common: { name: "Fan speed (while in manual mode)", type: "number", role: "value", min: 0, max: 5, states: { 0: "AUTO", 1: "LOWEST", 2: "LOW", 3: "MEDIUM", 4: "HIGH", 5: "MAX" }, read: true, write: true, def: this.fanSpeed, desc: "Current fan speed of the device (while in manual mode)" }, native: {} }); await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.VaneVerticalDirection, { type: "state", common: { name: "Vane vertical direction", type: "number", role: "value", min: 0, max: 7, states: { 0: "AUTO", 1: "TOPMOST", 2: "UP", 3: "MIDDLE", 4: "DOWN", 5: "BOTTOMMOST", 7: "SWING" }, read: true, write: true, def: this.vaneVerticalDirection, desc: "Current vertical direction of the device's vane" }, native: {} }); await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.VaneHorizontalDirection, { type: "state", common: { name: "Vane horizontal direction", type: "number", role: "value", min: 0, max: 12, states: { 0: "AUTO", 1: "LEFTMOST", 2: "LEFT", 3: "MIDDLE", 4: "RIGHT", 5: "RIGHTMOST", 8: "50/50", 12: "SWING" }, read: true, write: true, def: this.vaneHorizontalDirection, desc: "Current horizontal direction of the device's vane" }, native: {} }); await gthat.extendObjectAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.TimerToogle, { type: "state", common: { name: "Timer toggle", type: "boolean", role: "switch.enable", read: true, write: true, def: this.timerToggle, desc: "Enable or disable the device's timer" }, native: {} }); //#endregion //#region REPORTS let reportsPrefix = `${devicePrefix}.${commonDefines.AdapterDatapointIDs.Reports}`; await gthat.extendObjectAsync(reportsPrefix, { type: "channel", common: { name: "Device reports" }, native: {} }); reportsPrefix += "."; await gthat.extendObjectAsync(reportsPrefix + commonDefines.CommonDeviceStateIDs.PowerConsumptionReportStartDate, { type: "state", common: { name: "Report start date (format: YYYY-MM-DD)", type: "string", role: "date", read: true, write: true, desc: "Report data will be collected starting at this date" }, native: {} }); await gthat.extendObjectAsync(reportsPrefix + commonDefines.CommonDeviceStateIDs.PowerConsumptionReportEndDate, { type: "state", common: { name: "Report end date (format: YYYY-MM-DD)", type: "string", role: "date", read: true, write: true, desc: "Report data will be collected until this date" }, native: {} }); await gthat.extendObjectAsync(reportsPrefix + commonDefines.CommonDeviceStateIDs.GetPowerConsumptionReport, { type: "state", common: { name: "Get current power consumption report", type: "boolean", role: "button", read: false, write: true, def: false, desc: "Get current power consumption report" }, native: {} }); let lastReportDataPrefix = `${devicePrefix}.${commonDefines.AdapterDatapointIDs.Reports}.${commonDefines.AdapterDatapointIDs.LastReportData}`; await gthat.extendObjectAsync(lastReportDataPrefix, { type: "channel", common: { name: "Last report data" }, native: {} }); lastReportDataPrefix += "."; const reportModes = [commonDefines.AtaDeviceOperationModes.HEAT.id, commonDefines.AtaDeviceOperationModes.COOL.id, commonDefines.AtaDeviceOperationModes.AUTO.id, commonDefines.AtaDeviceOperationModes.VENT.id, commonDefines.AtaDeviceOperationModes.DRY.id]; reportModes.forEach(mode => { if (mode == commonDefines.AtaDeviceOperationModes.HEAT.id && !this.canHeat || mode == commonDefines.AtaDeviceOperationModes.DRY.id && !this.canDry || mode == commonDefines.AtaDeviceOperationModes.COOL.id && !this.canCool) return; gthat.extendObjectAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix + mode, { type: "state", common: { name: `Total power consumption for mode '${mode}'`, type: "number", role: "value.power.consumption", min: 0, read: true, write: false, unit: "kWh", def: 0, desc: `Total power consumption for mode '${mode}'` }, native: {} }); }); gthat.extendObjectAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix, { type: "state", common: { name: "Total power consumption for all modes", type: "number", role: "value.power.consumption", min: 0, read: true, write: false, unit: "kWh", def: 0, desc: "Total power consumption for all modes" }, native: {} }); await gthat.extendObjectAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalReportedMinutes, { type: "state", common: { name: "Total power consumption minutes", type: "number", role: "value", min: 0, read: true, write: false, unit: "min", def: 0, desc: "Total operation time" }, native: {} }); await gthat.extendObjectAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.RawPowerConsumptionData, { type: "state", common: { name: "Raw data of current report", type: "string", role: "json", read: true, write: false, desc: "Raw data of current report" }, native: {} }); //#endregion gthat.log.debug(`Created and saved ATA device ${this.id} (${this.name})`); this.hasBeenCreated = true; } // Only writes changed device data into the DB async UpdateDeviceData(deviceOption) { //#region INFO const infoPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Info}.`; await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.DeviceName, this.name, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.SerialNumber, this.serialNumber, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.MacAddress, this.macAddress, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.BuildingId, this.buildingId, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.FloorId, this.floorId, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.CanCool, this.canCool, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.CanHeat, this.canHeat, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.CanDry, this.canDry, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MinTempCoolDry, this.minTempCoolDry, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MaxTempCoolDry, this.maxTempCoolDry, true); if (this.canHeat) await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MinTempHeat, this.minTempHeat, true); if (this.canHeat) await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MaxTempHeat, this.maxTempHeat, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MinTempAuto, this.minTempAuto, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.MaxTempAuto, this.maxTempAuto, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.RoomTemp, this.roomTemp, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.FanSpeedAuto, this.actualFanSpeed, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.AtaDeviceStateIDs.NumberOfFanSpeeds, this.numberOfFanSpeeds, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.LastCommunication, this.lastCommunication, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.NextCommunication, this.nextCommunication, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.DeviceOnline, this.deviceOnline, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.DeviceHasError, this.deviceHasError, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.ErrorMessages, this.errorMessages, true); await gthat.setStateChangedAsync(infoPrefix + commonDefines.CommonDeviceStateIDs.ErrorCode, this.errorCode, true); //#endregion //#region CONTROL const controlPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Control}.`; switch (deviceOption) { case commonDefines.AtaDeviceOptions.PowerState: await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.Power, this.power, true); break; case commonDefines.AtaDeviceOptions.TargetHeatingCoolingState: await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.Mode, this.operationMode, true); break; case commonDefines.AtaDeviceOptions.TargetTemperature: await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.TargetTemp, this.targetTemp, true); break; case commonDefines.AtaDeviceOptions.FanSpeed: await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.FanSpeedManual, this.fanSpeed, true); break; case commonDefines.AtaDeviceOptions.VaneHorizontalDirection: await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.VaneHorizontalDirection, this.vaneHorizontalDirection, true); break; case commonDefines.AtaDeviceOptions.VaneVerticalDirection: await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.VaneVerticalDirection, this.vaneVerticalDirection, true); break; case "ALL": default: await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.Power, this.power, true); await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.Mode, this.operationMode, true); await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.TargetTemp, this.targetTemp, true); await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.FanSpeedManual, this.fanSpeed, true); await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.VaneHorizontalDirection, this.vaneHorizontalDirection, true); await gthat.setStateChangedAsync(controlPrefix + commonDefines.AtaDeviceStateIDs.VaneVerticalDirection, this.vaneVerticalDirection, true); break; } //#endregion gthat.log.debug(`Updated device data for ATA device ${this.id} (${this.name})`); } // Only writes changed report data into the DB async UpdateReportData() { const reportsPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Reports}.`; await gthat.setStateChangedAsync(reportsPrefix + commonDefines.CommonDeviceStateIDs.PowerConsumptionReportStartDate, this.powerConsumptionReportStartDate, true); await gthat.setStateChangedAsync(reportsPrefix + commonDefines.CommonDeviceStateIDs.PowerConsumptionReportEndDate, this.powerConsumptionReportEndDate, true); const lastReportDataPrefix = `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Reports}.${commonDefines.AdapterDatapointIDs.LastReportData}.`; let totalConsumption = 0; if (this.canCool) { await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix + commonDefines.AtaDeviceOperationModes.COOL.id, commonDefines.roundValue(this.totalPowerConsumptionCooling, 3), true); totalConsumption += this.totalPowerConsumptionCooling; } if (this.canHeat) { await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix + commonDefines.AtaDeviceOperationModes.HEAT.id, commonDefines.roundValue(this.totalPowerConsumptionHeating, 3), true); totalConsumption += this.totalPowerConsumptionHeating; } await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix + commonDefines.AtaDeviceOperationModes.AUTO.id, commonDefines.roundValue(this.totalPowerConsumptionAuto, 3), true); totalConsumption += this.totalPowerConsumptionAuto; if (this.canDry) { await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix + commonDefines.AtaDeviceOperationModes.DRY.id, commonDefines.roundValue(this.totalPowerConsumptionDry, 3), true); totalConsumption += this.totalPowerConsumptionDry; } await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix + commonDefines.AtaDeviceOperationModes.VENT.id, commonDefines.roundValue(this.totalPowerConsumptionVent, 3), true); totalConsumption += this.totalPowerConsumptionVent; await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalPowerConsumptionPrefix, commonDefines.roundValue(totalConsumption, 3), true); await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.TotalReportedMinutes, this.totalPowerConsumptionMinutes, true); await gthat.setStateChangedAsync(lastReportDataPrefix + commonDefines.CommonDeviceStateIDs.RawPowerConsumptionData, JSON.stringify(this.rawPowerConsumptionReportData), true); gthat.log.debug(`Updated report data for device ${this.id} (${this.name})`); } getDeviceInfo(callback, deviceOption, value) { const gthis = this; if (gthis.airInfo != null) { gthat.log.debug(`Data already available for: ${gthis.id} (${gthis.name})`); callback && callback(deviceOption, value, gthis); if (gthis.deviceInfoRequestQueue.length) { const args = gthis.deviceInfoRequestQueue.shift(); gthat.log.debug(`Dequeuing getDeviceInfo remote request for device option '${args[1].id}' with value '${args[2].value != undefined ? args[2].value : args[2]}'...`); gthis.getDeviceInfo.apply(gthis, args); } return; } gthat.log.debug(`Getting device data for ${gthis.id} (${gthis.name})`); if (gthis.currentDeviceInfoRequests < 1) { gthis.currentDeviceInfoRequests++; const url = `https://app.melcloud.com/Mitsubishi.Wifi.Client/Device/Get?id=${gthis.id}&buildingID=${gthis.buildingId}`; Axios.get(url, { httpsAgent: gthis.platform.customHttpsAgent, headers: { "Host": "app.melcloud.com", "X-MitsContextKey": gthis.platform.contextKey } }).then(function handleDeviceInfoResponse(response) { gthis.currentDeviceInfoRequests--; if (!response || !response.data || JSON.stringify(response.data).search("<!DOCTYPE html>") != -1) { gthat.log.error(`There was a problem receiving the response from: ${url}`); gthis.airInfo = null; } else { const statusCode = response.status; gthat.log.debug(`Received response from: ${url} (status code: ${statusCode} - ${response.statusText})`); if (statusCode != HttpStatus.StatusCodes.OK) { gthis.airInfo = null; gthat.log.error(`Invalid HTTP status code (${statusCode} - ${response.statusText}). Getting device data failed!`); } else { gthat.log.debug(`Response from cloud: ${JSON.stringify(response.data)}`); gthis.airInfo = response.data; // Cache airInfo data for 1 minute setTimeout(function clearAirInfo() { gthis.airInfo = null; }, 60 * 1000); callback && callback(deviceOption, value, gthis); } } if (gthis.deviceInfoRequestQueue.length) { const args = gthis.deviceInfoRequestQueue.shift(); gthat.log.debug(`Dequeuing getDeviceInfo remote request for device option '${args[1].id}' with value '${args[2].value != undefined ? args[2].value : args[2]}'`); gthis.getDeviceInfo.apply(gthis, args); } }).catch(error => { gthat.log.error(`There was a problem getting device data from: ${url}`); gthat.log.error(`Error: ${error}`); gthis.airInfo = null; gthis.currentDeviceInfoRequests--; if (error.response && error.response.status && error.response.status == 429) { gthat.log.error("You have probably been rate limited by the MELCloud servers because of too much requests. Stop the adapter for a few hours, increase the polling interval in the settings and try again later."); } if (gthis.deviceInfoRequestQueue.length) { const args = gthis.deviceInfoRequestQueue.shift(); gthat.log.debug(`Dequeuing getDeviceInfo remote request for device option '${args[1].id}' with value '${args[2].value != undefined ? args[2].value : args[2]}'`); gthis.getDeviceInfo.apply(gthis, args); } }); } else { gthat.log.debug(`Queueing getDeviceInfo remote request for '${deviceOption.id}' with value '${value.value != undefined ? value.value : value}'...`); gthis.deviceInfoRequestQueue.push(arguments); } } setDevice(deviceOption, value, gthis) { if (gthis.currentDeviceSetRequests < 1) { gthis.currentDeviceSetRequests++; gthat.log.debug(`Changing device option '${deviceOption.id}' to '${value.value != undefined ? value.value : value}'...`); const modifiedAirInfo = gthis.airInfo; if (modifiedAirInfo == null) { gthat.log.error(`setDevice(): modifiedAirInfo is not filled - please report this to the developer!`); return; } value = gthis.verifyDeviceOptionValue(deviceOption, value, gthis); if (deviceOption == commonDefines.AtaDeviceOptions.PowerState) { switch (value) { case commonDefines.DevicePowerStates.OFF: modifiedAirInfo.Power = commonDefines.DevicePowerStates.OFF.value; modifiedAirInfo.EffectiveFlags = commonDefines.DevicePowerStates.OFF.effectiveFlags; break; case commonDefines.DevicePowerStates.ON: modifiedAirInfo.Power = commonDefines.DevicePowerStates.ON.value; modifiedAirInfo.EffectiveFlags = commonDefines.DevicePowerStates.ON.effectiveFlags; break; default: gthat.log.error("setDevice(): Unsupported value for device option - please report this to the developer!"); return; } } else if (deviceOption == commonDefines.AtaDeviceOptions.TargetHeatingCoolingState) { switch (value) { case commonDefines.AtaDeviceOperationModes.HEAT: modifiedAirInfo.OperationMode = commonDefines.AtaDeviceOperationModes.HEAT.value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOperationModes.HEAT.effectiveFlags; break; case commonDefines.AtaDeviceOperationModes.DRY: modifiedAirInfo.OperationMode = commonDefines.AtaDeviceOperationModes.DRY.value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOperationModes.DRY.effectiveFlags; break; case commonDefines.AtaDeviceOperationModes.COOL: modifiedAirInfo.OperationMode = commonDefines.AtaDeviceOperationModes.COOL.value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOperationModes.COOL.effectiveFlags; break; case commonDefines.AtaDeviceOperationModes.VENT: modifiedAirInfo.OperationMode = commonDefines.AtaDeviceOperationModes.VENT.value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOperationModes.VENT.effectiveFlags; break; case commonDefines.AtaDeviceOperationModes.AUTO: modifiedAirInfo.OperationMode = commonDefines.AtaDeviceOperationModes.AUTO.value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOperationModes.AUTO.effectiveFlags; break; default: gthat.log.error("setDevice(): Unsupported value for device option - please report this to the developer!"); return; } } else if (deviceOption == commonDefines.AtaDeviceOptions.TargetTemperature) { modifiedAirInfo.SetTemperature = value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOptions.TargetTemperature.effectiveFlags; } else if (deviceOption == commonDefines.AtaDeviceOptions.FanSpeed) { modifiedAirInfo.SetFanSpeed = value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOptions.FanSpeed.effectiveFlags; } else if (deviceOption == commonDefines.AtaDeviceOptions.VaneHorizontalDirection) { modifiedAirInfo.VaneHorizontal = value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOptions.VaneHorizontalDirection.effectiveFlags; } else if (deviceOption == commonDefines.AtaDeviceOptions.VaneVerticalDirection) { modifiedAirInfo.VaneVertical = value; modifiedAirInfo.EffectiveFlags = commonDefines.AtaDeviceOptions.VaneVerticalDirection.effectiveFlags; } else { gthat.log.error("setDevice(): Unsupported device option - please report this to the developer!"); return; } modifiedAirInfo.HasPendingCommand = true; const url = "https://app.melcloud.com/Mitsubishi.Wifi.Client/Device/SetAta"; const body = JSON.stringify(modifiedAirInfo); gthat.log.silly(`Request body: ${body}`); Axios.post(url, body, { httpsAgent: gthis.platform.customHttpsAgent, headers: { "Host": "app.melcloud.com", "X-MitsContextKey": gthis.platform.contextKey, "Content-Type": "application/json; charset=utf-8" } }).then(function handleSetDeviceResponse(response) { if (!response) { gthat.log.error(`There was a problem receiving the response from: ${url}`); gthis.airInfo = null; return; } else { const statusCode = response.status; const statusText = response.statusText; gthat.log.debug(`Received response from: ${url} (status code: ${statusCode} - ${statusText})`); if (statusCode != HttpStatus.StatusCodes.OK) { gthis.airInfo = null; gthat.log.error(`Invalid HTTP status code (${statusCode} - ${statusText}). Changing device option failed!`); return; } const responseData = response.data; gthat.log.debug(`Response from cloud: ${JSON.stringify(responseData)}`); gthis.lastCommunication = responseData.LastCommunication; gthis.nextCommunication = responseData.NextCommunication; gthis.roomTemp = responseData.RoomTemperature; gthis.deviceOnline = !responseData.Offline; gthis.errorCode = responseData.ErrorCode; gthis.errorMessages = responseData.ErrorMessage; switch (deviceOption) { case commonDefines.AtaDeviceOptions.PowerState: gthis.power = responseData.Power; break; case commonDefines.AtaDeviceOptions.TargetHeatingCoolingState: gthis.operationMode = responseData.OperationMode; break; case commonDefines.AtaDeviceOptions.TargetTemperature: gthis.targetTemp = responseData.SetTemperature; break; case commonDefines.AtaDeviceOptions.FanSpeed: gthis.fanSpeed = responseData.SetFanSpeed; break; case commonDefines.AtaDeviceOptions.VaneHorizontalDirection: gthis.vaneHorizontalDirection = responseData.VaneHorizontal; break; case commonDefines.AtaDeviceOptions.VaneVerticalDirection: gthis.vaneVerticalDirection = responseData.VaneVertical; break; default: break; } gthis.UpdateDeviceData(deviceOption); // write updated values gthis.currentDeviceSetRequests--; if (gthis.deviceSetRequestQueue.length) { const args = gthis.deviceSetRequestQueue.shift(); gthat.log.debug(`Dequeuing setDevice remote request for device option '${args[0].id}' with value '${args[1].value != undefined ? args[1].value : args[1]}'`); gthis.setDevice.apply(gthis, args); } } }).catch(error => { gthat.log.error(`There was a problem setting info to: ${url}`); gthat.log.error(error); gthis.currentDeviceSetRequests--; if (error.response && error.response.status && error.response.status == 429) { gthat.log.error("You have probably been rate limited by the MELCloud servers because of too much requests. Stop the adapter for a few hours, increase the polling interval in the settings and try again later."); } if (gthis.deviceSetRequestQueue.length) { const args = gthis.deviceSetRequestQueue.shift(); gthat.log.debug(`Dequeuing setDevice remote request for device option '${args[0].id}' with value '${args[1].value != undefined ? args[1].value : args[1]}'`); gthis.setDevice.apply(gthis, args); } }); } else { gthat.log.debug(`Queueing setDevice remote request for '${deviceOption.id}' with value '${value.value != undefined ? value.value : value}'...`); gthis.deviceSetRequestQueue.push(arguments); } } verifyDeviceOptionValue(deviceOption, value, gthis) { switch (deviceOption) { case commonDefines.AtaDeviceOptions.FanSpeed: if (value > gthis.numberOfFanSpeeds) { gthat.log.warn(`Fan speed limited to ${gthis.numberOfFanSpeeds} because device can't handle more than that!`); return gthis.numberOfFanSpeeds; } return value; case commonDefines.AtaDeviceOptions.TargetTemperature: // eslint-disable-next-line no-case-declarations let min, max; switch (gthis.operationMode) { case commonDefines.AtaDeviceOperationModes.COOL.value: case commonDefines.AtaDeviceOperationModes.DRY.value: min = gthis.minTempCoolDry; max = gthis.maxTempCoolDry; break; case commonDefines.AtaDeviceOperationModes.HEAT.value: min = gthis.minTempHeat; max = gthis.maxTempHeat; break; case commonDefines.AtaDeviceOperationModes.AUTO.value: min = gthis.minTempAuto; max = gthis.maxTempAuto; break; default: min = gthis.platform.UseFahrenheit ? 60 : 16; max = gthis.platform.UseFahrenheit ? 104 : 40; break; } if (value < min) { value = min; gthat.log.warn(`SetTemperature limited to ${min} because device can't handle lower than that!`); } else if (value > max) { value = max; gthat.log.warn(`SetTemperature limited to ${max} because device can't handle more than that!`); } return value; case commonDefines.AtaDeviceOptions.VaneHorizontalDirection: if (value < 0 || value > 5 && value != 8 && value != 12) { gthat.log.warn(`VaneHorizontalDirection: unsupported value '${value}' - falling back to '0'!`); value = 0; } return value; case commonDefines.AtaDeviceOptions.VaneVerticalDirection: if (value < 0 || value > 5 && value != 7) { gthat.log.warn(`VaneVerticalDirection: unsupported value '${value}' - falling back to '0'!`); value = 0; } return value; case commonDefines.AtaDeviceOptions.TargetHeatingCoolingState: if (value == commonDefines.AtaDeviceOperationModes.COOL.value && !gthis.canCool) { gthat.log.warn(`TargetHeatingCoolingState: unsupported value '${value}'. Device can not cool!`); } else if (value == commonDefines.AtaDeviceOperationModes.DRY.value && !gthis.canDry) { gthat.log.warn(`TargetHeatingCoolingState: unsupported value '${value}'. Device can not dry!`); } else if (value == commonDefines.AtaDeviceOperationModes.HEAT.value && !gthis.canHeat) { gthat.log.warn(`TargetHeatingCoolingState: unsupported value '${value}'. Device can not heat!`); } return value; // don't modify the value as the correct value cant't be known here default: return value; } } async getPowerConsumptionReport(isCumulatedReport = false) { // eslint-disable-next-line no-async-promise-executor return /** @type {Promise<void>} */(new Promise(async (resolve, reject) => { const gthis = this; gthat.log.debug(`Getting power consumption report for ${gthis.id} (${gthis.name})`); const url = "https://app.melcloud.com/Mitsubishi.Wifi.Client/EnergyCost/Report"; const body = JSON.stringify(await this.buildPowerConsumptionReportRequestBody(isCumulatedReport)); gthat.log.silly(`Request body: ${body}`); if (body == "{}") return; // creating body failed or was provided dates were invalid Axios.post(url, body, { httpsAgent: gthis.platform.customHttpsAgent, headers: { "Host": "app.melcloud.com", "X-MitsContextKey": gthis.platform.contextKey, "Content-Type": "application/json; charset=utf-8" } }).then(function handleConsumptionReportResponse(response) { if (!response) { gthat.log.error(`There was a problem receiving the response from: ${url}`); reject(); } else { const statusCode = response.status; const statusText = response.statusText; gthat.log.debug(`Received response from: ${url} (status code: ${statusCode} - ${statusText})`); if (statusCode != HttpStatus.StatusCodes.OK) { gthat.log.error(`Invalid HTTP status code (${statusCode} - ${statusText}). Getting power consumption report failed!`); reject(); return; } const responseData = response.data; gthat.log.debug(`Response from cloud: ${JSON.stringify(responseData)}`); // only save date portion of timestamp without the empty time let timestampPos = responseData.FromDate.indexOf("T"); if (timestampPos != -1) { gthis.powerConsumptionReportStartDate = responseData.FromDate.substring(0, timestampPos); } else { gthis.powerConsumptionReportStartDate = responseData.FromDate; } timestampPos = responseData.ToDate.indexOf("T"); if (timestampPos != -1) { gthis.powerConsumptionReportEndDate = responseData.ToDate.substring(0, timestampPos); } else { gthis.powerConsumptionReportEndDate = responseData.ToDate; } // round all consumption values to 3 digits gthis.totalPowerConsumptionCooling = responseData.TotalCoolingConsumed; gthis.totalPowerConsumptionHeating = responseData.TotalHeatingConsumed; gthis.totalPowerConsumptionAuto = responseData.TotalAutoConsumed; gthis.totalPowerConsumptionDry = responseData.TotalDryConsumed; gthis.totalPowerConsumptionVent = responseData.TotalFanConsumed; gthis.totalPowerConsumptionMinutes = responseData.TotalMinutes; gthis.rawPowerConsumptionReportData = responseData; gthis.linkedDevicesIncludedInArregateEnergyReport = responseData.LinkedDevicesIncludedInArregateEnergyReport; gthis.UpdateReportData(); resolve(); } }).catch(error => { gthat.log.error(`There was a problem getting power consumption report from: ${url}`); gthat.log.error(`Error: ${error}`); if (error.response && error.response.status && error.response.status == 429) { gthat.log.error("You have probably been rate limited by the MELCloud servers because of too much requests. Stop the adapter for a few hours, increase the polling interval in the settings and try again later."); } reject(); }); })); } async buildPowerConsumptionReportRequestBody(isCumulatedReport = false) { const requestBody = {}; const startStateId = isCumulatedReport ? `${commonDefines.AdapterDatapointIDs.Reports}.${commonDefines.CommonDeviceStateIDs.PowerConsumptionReportStartDate}` : `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Reports}.${commonDefines.CommonDeviceStateIDs.PowerConsumptionReportStartDate}`; const startDateObj = await gthat.getStateAsync(startStateId); let startDate = (startDateObj == null || startDateObj.val == null) ? "" : startDateObj.val; const endStateId = isCumulatedReport ? `${commonDefines.AdapterDatapointIDs.Reports}.${commonDefines.CommonDeviceStateIDs.PowerConsumptionReportEndDate}` : `${commonDefines.AdapterDatapointIDs.Devices}.${this.id}.${commonDefines.AdapterDatapointIDs.Reports}.${commonDefines.CommonDeviceStateIDs.PowerConsumptionReportEndDate}`; const endDateObj = await gthat.getStateAsync(endStateId); let endDate = (endDateObj == null || endDateObj.val == null) ? "" : endDateObj.val; if (startDate == "") { gthat.log.warn("No valid start date was provided (format: YYYY-MM-DD). Defaulting to 6 months prior."); const d = new Date(); d.setMonth(d.getMonth() - 6); startDate = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; } const parsedStartDate = startDate.split("-"); if (parsedStartDate.length != 3 || parsedStartDate[0].length != 4 || parsedStartDate[1].length > 2 || parsedStartDate[2].length > 2) { gthat.log.warn("No valid start date was provided (format: YYYY-MM-DD). Defaulting to 6 months prior."); const d = new Date(); d.setMonth(d.getMonth() - 6); startDate = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; } if (endDate == "") { gthat.log.warn("No valid end date was provided (format: YYYY-MM-DD). Defaulting to today."); const d = new Date(); endDate = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; } const parsedEndDate = endDate.split("-"); if (parsedEndDate.length != 3 || parsedEndDate[0].length != 4 || parsedEndDate[1].length > 2 || parsedEndDate[2].length > 2) { gthat.log.warn("No valid end date was provided (format: YYYY-MM-DD). Defaulting to today."); const d = new Date(); endDate = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; } requestBody.DeviceId = this.id; requestBody.FromDate = `${startDate}T00:00:00`; requestBody.ToDate = `${endDate}T00:00:00`; requestBody.UseCurrency = false; return requestBody; } async toggleTimerState(enableTimer) { // eslint-disable-next-line no-async-promise-executor return /** @type {Promise<void>} */(new Promise(async (resolve, reject) => { const gthis = this; gthat.log.debug(`${enableTimer ? `Enabling` : `Disabling`} timer for ${gthis.id} (${gthis.name})`); // Step 1: Get current timer infos const getTimerUrl = `https://app.melcloud.com/Mitsubishi.Wifi.Client/Timer/Get2?deviceId=${gthis.id}`; Axios.get(getTimerUrl, { httpsAgent: gthis.platform.customHttpsAgent, headers: { "Host": "app.melcloud.com", "X-MitsContextKey": gthis.platform.contextKey } }).then(function handleGetTimerResponse(response) { if (!response) { gthat.log.error(`There was a problem receiving the response from: ${getTimerUrl}`); reject(); } else { const statusCode = response.status; const statusText = response.statusText; gthat.log.debug(`Received response from: ${getTimerUrl} (status code: ${statusCode} - ${statusText})`); if (statusCode != HttpStatus.StatusCodes.OK) { gthat.log.error(`Invalid HTTP status code (${statusCode} - ${statusText}). Getting timer information failed!`); reject(); return; } const responseData = response.data; gthat.log.debug(`Response from cloud: ${JSON.stringify(responseData)}`); gthis.timerToggle = responseData.Enabled; gthat.setStateChangedAsync(`${commonDefines.AdapterDatapointIDs.Devices}.${gthis.id}.${commonDefines.AdapterDatapointIDs.Control}.${commonDefines.AtaDeviceStateIDs.TimerToogle}`, gthis.timerToggle, true); if (enableTimer == gthis.timerToggle) { gthat.log.warn(`Timer for ${gthis.id} (${gthis.name}) is already ${enableTimer ? `enabled` : `disabled`}. Ignoring request.`); resolve(); return; } if (responseData.Seasons[0].Events.length == 0) { gthat.log.warn(`No timer events for ${gthis.id} (${gthis.name}) set. Please set them first in the MelCloud app/website before toggling it here. Ignoring request.`); resolve(); return; } // Step 2: Set desired timer state with infos retrieved from step 1 const toggleRequest = response.data; toggleRequest.Devices = gthis.id; toggleRequest.TimerEnabled = enableTimer; toggleRequest.SkipPage1 = true; const setTimerUrl = "https://app.melcloud.com/Mitsubishi.Wifi.Client/Timer/SetAta2"; const body = JSON.stringify(toggleRequest); gthat.log.silly(`Request body: ${body}`); Axios.post(setTimerUrl, body, { httpsAgent: gthis.platform.customHttpsAgent, headers: { "Host": "app.melcloud.com", "X-MitsContextKey": gthis.platform.contextKey, "Content-Type": "application/json; charset=utf-8" } }).then(function handleToggleTimerResponse(response) { if (!response) { gthat.log.error(`There was a problem receiving the response from: ${setTimerUrl}`); } else { const statusCode = response.status; const statusText = response.statusText; gthat.log.debug(`Received response from: ${setTimerUrl} (statu