homebridge-melcloud-control
Version:
Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.
427 lines (395 loc) • 20.7 kB
JavaScript
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 MelCloudErv 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;
//deviceData
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;
const hideDryModeControl = deviceData.HideDryModeControl;
const hideRoomTemperature = deviceData.HideRoomTemperature ?? false;
const hideSupplyTemperature = deviceData.HideSupplyTemperature ?? false;
const hideOutdoorTemperature = deviceData.HideOutdoorTemperature ?? false;
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 ervFlags = device.ErvFlags;
const ervModel = device.ErvModel;
const labelControls = device.LabelControls;
const devicePowerDisabled = device.DevicePowerDisabled;
const silentMode = device.SilentMode;
const deviceHolidayMode = device.DeviceHolidayMode;
const externalControl = device.ExternalControl;
const bypassVentilationSkipped = device.BypassVentilationSkipped;
const autoVentilationSkipped = device.AutoVentilationSkipped;
const fanSpeedSkipped = device.FanSpeedSkipped;
const fanSpeedRestricted = device.FanSpeedRestricted;
const hasTemperatureControlUnit = device.HasTemperatureControlUnit;
const hasCoolOperationMode = device.HasCoolOperationMode;
const hasHeatOperationMode = device.HasHeatOperationMode;
const hasAutoOperationMode = device.HasAutoOperationMode;
const hasBypassVentilationMode = device.HasBypassVentilationMode ?? false;
const hasAutoVentilationMode = device.HasAutoVentilationMode ?? false;
const hasRoomTemperature = device.HasRoomTemperature ?? false;
const hasSupplyTemperature = device.HasSupplyTemperature ?? false;
const hasOutdoorTemperature = device.HasOutdoorTemperature ?? false;
const hasCO2Sensor = device.HasCO2Sensor;
const hasPM25Sensor = device.HasPM25Sensor;
const pM25SensorStatus = device.PM25SensorStatus;
const pM25Level = device.PM25Level;
const numberOfFanSpeeds = device.NumberOfFanSpeeds;
const hasHalfDegreeIncrements = device.HasHalfDegreeIncrements;
const temperatureIncrementOverride = device.TemperatureIncrementOverride;
const temperatureIncrement = device.TemperatureIncrement;
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 setSupplyTemperatureMode = device.SetSupplyTemperatureMode;
const hasAutomaticFanSpeed = device.HasAutomaticFanSpeed ?? false;
const coreMaintenanceRequired = device.CoreMaintenanceRequired ?? false;
const filterMaintenanceRequired = device.FilterMaintenanceRequired ?? false;
const power = device.Power ?? false;
const roomTemperature = device.RoomTemperature;
const supplyTemperature = device.SupplyTemperature;
const outdoorTemperature = device.OutdoorTemperature;
const roomCO2Level = device.RoomCO2Level;
const nightPurgeMode = device.NightPurgeMode ?? false;
const thermostatOn = device.ThermostatOn;
const setTemperature = device.SetTemperature;
const actualSupplyFanSpeed = device.ActualSupplyFanSpeed;
const actualExhaustFanSpeed = device.ActualExhaustFanSpeed;
const setFanSpeed = device.SetFanSpeed;
const automaticFanSpeed = device.AutomaticFanSpeed;
const operationMode = device.OperationMode; //0, Heat, 2, Cool, 4, 5, 6, Fan, Auto
const actualOperationMode = device.ActualOperationMode;
const ventilationMode = device.VentilationMode; //Lossnay, Bypass, Auto
const actualVentilationMode = device.ActualVentilationMode; //Lossnay, Bypass
const effectiveFlags = device.EffectiveFlags;
const lastEffectiveFlags = device.LastEffectiveFlags;
const defaultCoolingSetTemperature = device.DefaultCoolingSetTemperature ?? 23;
const defaultHeatingSetTemperature = device.DefaultHeatingSetTemperature ?? 21;
const hasEnergyConsumedMeter = device.HasEnergyConsumedMeter;
const currentEnergyConsumed = device.CurrentEnergyConsumed
const currentEnergyAssignment = device.CurrentEnergyAssignment;
const coolingDisabled = device.CoolingDisabled
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 effectivePCycle = device.EffectivePCycle;
const mqttFlags = device.MqttFlags;
const hasErrorMessages = device.HasErrorMessages;
const hasZone2 = device.HasZone2;
const offline = device.Offline ?? false;
const minPcycle = device.MinPcycle;
const maxPcycle = device.MaxPcycle;
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('message', `Units are not configured in MELCloud service`);
};
const deviceState = {
Power: power,
RoomTemperature: roomTemperature,
SupplyTemperature: supplyTemperature,
OutdoorTemperature: outdoorTemperature,
NightPurgeMode: nightPurgeMode,
SetTemperature: setTemperature,
SetFanSpeed: setFanSpeed,
OperationMode: operationMode,
VentilationMode: ventilationMode,
RoomCO2Level: roomCO2Level,
ActualSupplyFanSpeed: actualSupplyFanSpeed,
ActualExhaustFanSpeed: actualExhaustFanSpeed,
CoreMaintenanceRequired: coreMaintenanceRequired,
FilterMaintenanceRequired: filterMaintenanceRequired,
TemperatureIncrement: temperatureIncrement,
DefaultCoolingSetTemperature: defaultCoolingSetTemperature,
DefaultHeatingSetTemperature: defaultHeatingSetTemperature,
PM25SensorStatus: pM25SensorStatus,
PM25Level: pM25Level,
HideRoomTemperature: hideRoomTemperature,
HideSupplyTemperature: hideSupplyTemperature,
HideOutdoorTemperature: hideOutdoorTemperature
}
//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 ventilation mode
switch (displayMode) {
case 1: //Heather/Cooler
switch (deviceData.Device.VentilationMode) {
case 0: //LOSNAY
deviceData.Device.SetTemperature = deviceData.Device.DefaultHeatingSetTemperature;
break;
case 1: //BYPASS
deviceData.Device.SetTemperature = deviceData.Device.DefaultCoolingSetTemperature;
break;
case 2: //AUTO
const setTemperature = (deviceData.Device.DefaultCoolingSetTemperature + deviceData.Device.DefaultHeatingSetTemperature) / 2;
deviceData.Device.SetTemperature = setTemperature;
break;
};
case 2: //Thermostat
deviceData.Device.SetTemperature = deviceData.Device.SetTemperature;
break;
};
//device state
const payload = {
data: {
DeviceID: deviceData.Device.DeviceID,
EffectiveFlags: deviceData.Device.EffectiveFlags,
Power: deviceData.Device.Power,
SetTemperature: deviceData.Device.SetTemperature,
SetFanSpeed: deviceData.Device.SetFanSpeed,
OperationMode: deviceData.Device.OperationMode,
VentilationMode: deviceData.Device.VentilationMode,
DefaultCoolingSetTemperature: deviceData.Device.DefaultCoolingSetTemperature,
DefaultHeatingSetTemperature: deviceData.Device.DefaultHeatingSetTemperature,
HideRoomTemperature: deviceData.Device.HideRoomTemperature,
HideSupplyTemperature: deviceData.Device.HideSupplyTemperature,
HideOutdoorTemperature: deviceData.Device.HideOutdoorTemperature,
NightPurgeMode: deviceData.Device.NightPurgeMode,
HasPendingCommand: true
}
}
await this.axiosInstancePost(ApiUrls.SetErv, 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 MelCloudErv;