UNPKG

homebridge-melcloud-control

Version:

Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.

456 lines (423 loc) • 22.6 kB
import { promises as fsPromises } from 'fs'; import { Agent } from 'https'; import axios from 'axios'; import EventEmitter from 'events'; import ImpulseGenerator from './impulsegenerator.js'; import { ApiUrls } from './constants.js'; class MelCloudAta extends EventEmitter { constructor(config) { super(); this.devicesFile = config.devicesFile; this.deviceId = config.deviceId; this.enableDebugMode = config.enableDebugMode; //set default values this.deviceState = {}; this.axiosInstancePost = axios.create({ method: 'POST', baseURL: ApiUrls.BaseURL, timeout: 25000, headers: { 'X-MitsContextKey': config.contextKey, 'content-type': 'application/json' }, withCredentials: true, httpsAgent: new Agent({ keepAlive: false, rejectUnauthorized: false }) }); this.impulseGenerator = new ImpulseGenerator(); this.impulseGenerator.on('checkState', async () => { try { await this.checkState(); } catch (error) { this.emit('error', `Impulse generator error: ${error}`); }; }).on('state', (state) => { const emitState = state ? this.emit('success', `Impulse generator started`) : this.emit('warn', `Impulse generator stopped`); }); }; async checkState() { try { //read device info from file const devicesData = await this.readData(this.devicesFile); if (!Array.isArray(devicesData)) { this.emit('warn', `Device data not found`); return null; } const deviceData = devicesData.find(device => device.DeviceID === this.deviceId); const debug = this.enableDebugMode ? this.emit('debug', `Device Data: ${JSON.stringify(deviceData, null, 2)}`) : false; //device info const deviceId = deviceData.DeviceID; const deviceName = deviceData.DeviceName; const buildingId = deviceData.BuildingID; const buildingName = deviceData.BuildingName; const floorId = deviceData.FloorID; const floorName = deviceData.FloorName; const areaId = deviceData.AreaID; const areaName = deviceData.AreaName; const imageId = deviceData.ImageID; const installationDate = deviceData.InstallationDate; const lastServiceDate = deviceData.LastServiceDate; //presets const presets = deviceData.Presets ?? []; const ownerId = deviceData.OwnerID; const ownerName = deviceData.OwnerName; const ownerEmail = deviceData.OwnerEmail; const accessLevel = deviceData.AccessLevel; const directAccess = deviceData.DirectAccess; const endDate = deviceData.EndDate; const zone1Name = deviceData.Zone1Name; const zone2Name = deviceData.Zone2Name; const minTemperature = deviceData.MinTemperature; const maxTemperature = deviceData.MaxTemperature; const hideVaneControls = deviceData.HideVaneControls ?? false; const hideDryModeControl = deviceData.HideDryModeControl ?? false; const hideRoomTemperature = deviceData.HideRoomTemperature; const hideSupplyTemperature = deviceData.HideSupplyTemperature; const hideOutdoorTemperature = deviceData.HideOutdoorTemperature; const buildingCountry = deviceData.BuildingCountry; const ownerCountry = deviceData.OwnerCountry; const adaptorType = deviceData.AdaptorType; const linkedDevice = deviceData.LinkedDevice; const type = deviceData.Type; const macAddress = deviceData.MacAddress; const serialNumber = deviceData.SerialNumber ?? 'Undefined'; //device const device = deviceData.Device ?? {}; const pCycleActual = device.PCycleActual; const errorMessages = device.ErrorMessages; const deviceType = device.DeviceType; const canCool = device.CanCool; const canHeat = device.CanHeat; const canDry = device.CanDry; const canAuto = device.CanAuto; const hasAutomaticFanSpeed = device.HasAutomaticFanSpeed; const airDirectionFunction = device.AirDirectionFunction; const swingFunction = device.SwingFunction; const numberOfFanSpeeds = device.NumberOfFanSpeeds; const useTemperatureA = device.UseTemperatureA; const temperatureIncrementOverride = device.TemperatureIncrementOverride; //Auto, 1, 0.5 const temperatureIncrement = device.TemperatureIncrement; //1, 0.5 const minTempCoolDry = device.MinTempCoolDry; const maxTempCoolDry = device.MaxTempCoolDry; const minTempHeat = device.MinTempHeat; const maxTempHeat = device.MaxTempHeat; const minTempAutomatic = device.MinTempAutomatic; const maxTempAutomatic = device.MaxTempAutomatic; const legacyDevice = device.LegacyDevice; const unitSupportsStandbyMode = device.UnitSupportsStandbyMode; const isSplitSystem = device.IsSplitSystem; const hasHalfDegreeIncrements = device.HasHalfDegreeIncrements; const hasOutdoorTemperature = device.HasOutdoorTemperature; const modelIsAirCurtain = device.ModelIsAirCurtain; const modelSupportsFanSpeed = device.ModelSupportsFanSpeed; const modelSupportsAuto = device.ModelSupportsAuto; const modelSupportsHeat = device.ModelSupportsHeat; const modelSupportsDry = device.ModelSupportsDry; const modelSupportsVaneVertical = device.ModelSupportsVaneVertical; const modelSupportsVaneHorizontal = device.ModelSupportsVaneHorizontal; const modelSupportsWideVane = device.ModelSupportsWideVane; const modelDisableEnergyReport = device.ModelDisableEnergyReport; const modelSupportsStandbyMode = device.ModelSupportsStandbyMode; const modelSupportsEnergyReporting = device.ModelSupportsEnergyReporting; const prohibitSetTemperature = device.ProhibitSetTemperature; const prohibitOperationMode = device.ProhibitOperationMode; const prohibitPower = device.ProhibitPower; const power = device.Power; const roomTemperature = device.RoomTemperature; const outdoorTemperature = device.OutdoorTemperature; const setTemperature = device.SetTemperature; const actualFanSpeed = device.ActualFanSpeed; const fanSpeed = device.FanSpeed; const automaticFanSpeed = device.AutomaticFanSpeed; const vaneVerticalDirection = device.VaneVerticalDirection; const vaneVerticalSwing = device.VaneVerticalSwing; const vaneHorizontalDirection = device.VaneHorizontalDirection; const vaneHorizontalSwing = device.VaneHorizontalSwing; const operationMode = device.OperationMode; const effectiveFlags = device.EffectiveFlags; const inStandbyMode = device.InStandbyMode; const demandPercentage = device.DemandPercentage; const configuredDemandPercentage = device.ConfiguredDemandPercentage; const hasDemandSideControl = device.HasDemandSideControl; const defaultCoolingSetTemperature = device.DefaultCoolingSetTemperature; const defaultHeatingSetTemperature = device.DefaultHeatingSetTemperature; const roomTemperatureLabel = device.RoomTemperatureLabel; const heatingEnergyConsumedRate1 = device.HeatingEnergyConsumedRate1; const heatingEnergyConsumedRate2 = device.HeatingEnergyConsumedRate2; const coolingEnergyConsumedRate1 = device.CoolingEnergyConsumedRate1; const coolingEnergyConsumedRate2 = device.CoolingEnergyConsumedRate2; const autoEnergyConsumedRate1 = device.AutoEnergyConsumedRate1; const autoEnergyConsumedRate2 = device.AutoEnergyConsumedRate2; const dryEnergyConsumedRate1 = device.DryEnergyConsumedRate1; const dryEnergyConsumedRate2 = device.DryEnergyConsumedRate2; const fanEnergyConsumedRate1 = device.FanEnergyConsumedRate1; const fanEnergyConsumedRate2 = device.FanEnergyConsumedRate2; const otherEnergyConsumedRate1 = device.OtherEnergyConsumedRate1; const otherEnergyConsumedRate2 = device.OtherEnergyConsumedRate2; const hasEnergyConsumedMeter = device.HasEnergyConsumedMeter; const currentEnergyConsumed = device.CurrentEnergyConsumed; const currentEnergyMode = device.CurrentEnergyMode; const coolingDisabled = device.CoolingDisabled const energyCorrectionModel = device.EnergyCorrectionModel; const energyCorrectionActive = device.EnergyCorrectionActive; const minPcycle = device.MinPcycle; const maxPcycle = device.MaxPcycle; const effectivePCycle = device.EffectivePCycle; const maxOutdoorUnits = device.MaxOutdoorUnits; const maxIndoorUnits = device.MaxIndoorUnits; const maxTemperatureControlUnits = device.MaxTemperatureControlUnits; const modelCode = device.ModelCode; //const deviceId = device.DeviceID; //const macAddress = device.MacAddress; //const serialNumber = device.SerialNumber; const timeZoneID = device.TimeZoneID; const diagnosticMode = device.DiagnosticMode; const diagnosticEndDate = device.DiagnosticEndDate; const expectedCommand = device.ExpectedCommand; const owner = device.Owner; const detectedCountry = device.DetectedCountry; //const adaptorType = device.AdaptorType; const firmwareDeployment = device.FirmwareDeployment; const firmwareUpdateAborted = device.FirmwareUpdateAborted; //const linkedDevice = device.LinkedDevice; const wifiSignalStrength = device.WifiSignalStrength; const wifiAdapterStatus = device.WifiAdapterStatus; const position = device.Position; const pCycle = device.PCycle; const pCycleConfigured = device.PCycleConfigured; const recordNumMax = device.RecordNumMax; const lastTimeStamp = device.LastTimeStamp; const errorCode = device.ErrorCode; const hasError = device.HasError; const lastReset = device.LastReset; const flashWrites = device.FlashWrites; const scene = device.Scene; const sSLExpirationDate = device.SSLExpirationDate; const sPTimeout = device.SPTimeout; const passcode = device.Passcode; const serverCommunicationDisabled = device.ServerCommunicationDisabled; const consecutiveUploadErrors = device.ConsecutiveUploadErrors; const doNotRespondAfter = device.DoNotRespondAfter; const ownerRoleAccessLevel = device.OwnerRoleAccessLevel; //const ownerCountry = device.OwnerCountry; const hideEnergyReport = device.HideEnergyReport; const exceptionHash = device.ExceptionHash; const exceptionDate = device.ExceptionDate; const exceptionCount = device.ExceptionCount; const rate1StartTime = device.Rate1StartTime; const rate2StartTime = device.Rate2StartTime; const protocolVersion = device.ProtocolVersion; const unitVersion = device.UnitVersion; const firmwareAppVersion = device.FirmwareAppVersion?.toString() ?? 'Undefined'; const firmwareWebVersion = device.FirmwareWebVersion; const firmwareWlanVersion = device.FirmwareWlanVersion; const linkToMELCloudHome = device.LinkToMELCloudHome; const linkedByUserFromMELCloudHome = device.LinkedByUserFromMELCloudHome; const mqttFlags = device.MqttFlags; const hasErrorMessages = device.HasErrorMessages; const hasZone2 = device.HasZone2; const offline = device.Offline ?? false; const supportsHourlyEnergyReport = device.SupportsHourlyEnergyReport; //units const units = Array.isArray(device.Units) ? device.Units : []; const unitsCount = units.length; const manufacturer = 'Mitsubishi'; //indoor let idIndoor = 0; let deviceIndoor = 0; let serialNumberIndoor = 'Undefined'; let modelNumberIndoor = 0; let modelIndoor = false; let typeIndoor = 0; //outdoor let idOutdoor = 0; let deviceOutdoor = 0; let serialNumberOutdoor = 'Undefined'; let modelNumberOutdoor = 0; let modelOutdoor = false; let typeOutdoor = 0; //units array for (const unit of units) { const unitId = unit.ID; const unitDevice = unit.Device; const unitSerialNumber = unit.SerialNumber ?? 'Undefined'; const unitModelNumber = unit.ModelNumber; const unitModel = unit.Model ?? false; const unitType = unit.UnitType; const unitIsIndoor = unit.IsIndoor ?? false; switch (unitIsIndoor) { case true: idIndoor = unitId; deviceIndoor = unitDevice; serialNumberIndoor = unitSerialNumber; modelNumberIndoor = unitModelNumber; modelIndoor = unitModel; typeIndoor = unitType; break; case false: idOutdoor = unitId; deviceOutdoor = unitDevice; serialNumberOutdoor = unitSerialNumber; modelNumberOutdoor = unitModelNumber; modelOutdoor = unitModel; typeOutdoor = unitType; break; } } //diagnostic //const diagnosticMode = deviceData.DiagnosticMode; //const diagnosticEndDate = deviceData.DiagnosticEndDate; const location = deviceData.Location; //const detectedCountry = deviceData.DetectedCountry; const registrations = deviceData.Registrations; const localIPAddress = deviceData.LocalIPAddress; const timeZone = deviceData.TimeZone; const registReason = deviceData.RegistReason; //const expectedCommand = deviceData.ExpectedCommand; const registRetry = deviceData.RegistRetry; const dateCreated = deviceData.DateCreated; //const firmwareDeployment = deviceData.FirmwareDeployment; //const firmwareUpdateAborted = deviceData.FirmwareUpdateAborted; //permissions const permissionCanSetOperationMode = deviceData.Permissions.CanSetOperationMode; const permissionCanSetFanSpeed = deviceData.Permissions.CanSetFanSpeed; const permissionCanSetVaneDirection = deviceData.Permissions.CanSetVaneDirection; const permissionCanSetPower = deviceData.Permissions.CanSetPower; const permissionCanSetTemperatureIncrementOverride = deviceData.Permissions.CanSetTemperatureIncrementOverride; const permissionCanDisableLocalController = deviceData.Permissions.CanDisableLocalController; //display info if units are not configured in MELCloud service if (unitsCount === 0) { this.emit('warn', `Units are not configured in MELCloud service`); }; const deviceState = { Power: power, InStandbyMode: inStandbyMode, RoomTemperature: roomTemperature, OutdoorTemperature: outdoorTemperature, SetTemperature: setTemperature, ActualFanSpeed: actualFanSpeed, FanSpeed: fanSpeed, AutomaticFanSpeed: automaticFanSpeed, OperationMode: operationMode, VaneVerticalDirection: vaneVerticalDirection, VaneVerticalSwing: vaneVerticalSwing, VaneHorizontalDirection: vaneHorizontalDirection, VaneHorizontalSwing: vaneHorizontalSwing, DefaultCoolingSetTemperature: defaultCoolingSetTemperature, DefaultHeatingSetTemperature: defaultHeatingSetTemperature, ProhibitPower: prohibitPower, ProhibitSetTemperature: prohibitSetTemperature, ProhibitOperationMode: prohibitOperationMode, HideVaneControls: hideVaneControls, HideDryModeControl: hideDryModeControl } //check state changes const deviceDataHasNotChanged = JSON.stringify(deviceState) === JSON.stringify(this.deviceState); if (deviceDataHasNotChanged) { return; } this.deviceState = deviceState; //restFul this.emit('restFul', 'info', deviceData); this.emit('restFul', 'state', deviceData.Device); //mqtt this.emit('mqtt', 'Info', deviceData); this.emit('mqtt', 'State', deviceData.Device); //emit info this.emit('deviceInfo', manufacturer, modelIndoor, modelOutdoor, serialNumber, firmwareAppVersion); //emit state this.emit('deviceState', deviceData); return deviceData; } catch (error) { throw new Error(`Check state error: ${error}`); }; }; async readData(path) { try { const savedData = await fsPromises.readFile(path) if (savedData.toString().trim().length === 0) { return null; } try { const data = JSON.parse(savedData); return data; } catch (error) { throw new Error(`Parse JSON error: ${error}`); } } catch (error) { throw new Error(`Read data error: ${error}`); } } async send(deviceData, displayMode) { try { //set target temp based on display mode and operation mode switch (displayMode) { case 1: //Heather/Cooler switch (deviceData.Device.OperationMode) { case 1: //HEAT deviceData.Device.SetTemperature = deviceData.Device.DefaultHeatingSetTemperature; break; case 2: //DRY deviceData.Device.SetTemperature = deviceData.Device.DefaultCoolingSetTemperature; break; case 3: //COOL deviceData.Device.SetTemperature = deviceData.Device.DefaultCoolingSetTemperature; break; case 7: //FAN deviceData.Device.SetTemperature = deviceData.Device.SetTemperature; break; case 8: //AUTO const setTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2; deviceData.Device.SetTemperature = setTemperature; break; case 9: //HISEE HEAT deviceData.Device.SetTemperature = deviceData.Device.DefaultHeatingSetTemperature; break; case 10: //ISEE DRY deviceData.Device.SetTemperature = deviceData.Device.DefaultCoolingSetTemperature; break; case 11: //ISEE COOL deviceData.Device.SetTemperature = deviceData.Device.DefaultCoolingSetTemperature; break; }; case 2: //Thermostat deviceData.Device.SetTemperature = deviceData.Device.SetTemperature; break; }; const payload = { data: { DeviceID: deviceData.Device.DeviceID, EffectiveFlags: deviceData.Device.EffectiveFlags, Power: deviceData.Device.Power, SetTemperature: deviceData.Device.SetTemperature, SetFanSpeed: deviceData.Device.FanSpeed, OperationMode: deviceData.Device.OperationMode, VaneHorizontal: deviceData.Device.VaneHorizontalDirection, VaneVertical: deviceData.Device.VaneVerticalDirection, DefaultHeatingSetTemperature: deviceData.Device.DefaultHeatingSetTemperature, DefaultCoolingSetTemperature: deviceData.Device.DefaultCoolingSetTemperature, ProhibitSetTemperature: deviceData.Device.ProhibitSetTemperature, ProhibitOperationMode: deviceData.Device.ProhibitOperationMode, ProhibitPower: deviceData.Device.ProhibitPower, HideVaneControls: deviceData.HideVaneControls, HideDryModeControl: deviceData.HideDryModeControl, HasPendingCommand: true } } await this.axiosInstancePost(ApiUrls.SetAta, payload); this.updateData(deviceData); return true; } catch (error) { throw new Error(`Send data error: ${error}`); }; }; updateData(deviceData) { setTimeout(() => { this.emit('externalIntegrations', deviceData); this.emit('deviceState', deviceData); }, 500); } }; export default MelCloudAta;