UNPKG

homebridge-melcloud-control

Version:

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

853 lines (797 loc) • 96.3 kB
import EventEmitter from 'events'; import MelCloudErv from './melclouderv.js'; import RestFul from './restful.js'; import Mqtt from './mqtt.js'; import Functions from './functions.js'; import { TemperatureDisplayUnits, Ventilation, DeviceType } from './constants.js'; let Accessory, Characteristic, Service, Categories, AccessoryUUID; class DeviceErv extends EventEmitter { constructor(api, account, device, presets, schedules, scenes, buttons, defaultTempsFile, melCloudClass, melCloudAccountData, melCloudDeviceData) { super(); Accessory = api.platformAccessory; Characteristic = api.hap.Characteristic; Service = api.hap.Service; Categories = api.hap.Categories; AccessoryUUID = api.hap.uuid; //account config this.account = account; this.accountType = account.type; this.accountName = account.name; this.accountTypeMelCloud = account.type === 'melcloud'; this.logDeviceInfo = account.log?.deviceInfo || false; this.logInfo = account.log?.info || false; this.logWarn = account.log?.warn || false; this.logDebug = account.log?.debug || false; this.logError = account.log?.error || false; //device config this.device = device; this.deviceId = device.id; this.deviceName = device.name; this.deviceTypeString = DeviceType[device.type]; this.displayType = device.displayType; this.temperatureRoomSensor = device.temperatureRoomSensor || false; this.temperatureOutdoorSensor = device.temperatureOutdoorSensor || false; this.temperatureSupplySensor = device.temperatureSupplySensor || false; this.inStandbySensor = device.inStandbySensor || false; this.connectSensor = device.connectSensor || false; this.errorSensor = device.errorSensor || false; this.holidayModeSupport = device.holidayModeSupport || false; this.presets = presets; this.schedules = schedules; this.scenes = scenes; this.buttons = buttons; //files this.defaultTempsFile = defaultTempsFile; //melcloud this.melCloudClass = melCloudClass; this.melCloudDeviceData = melCloudDeviceData; this.melCloudAccountData = melCloudAccountData; //external integrations this.restFul = account.restFul ?? {}; this.restFul.port = device.restFulPort; this.restFulConnected = false; this.mqtt = account.mqtt ?? {}; this.mqttConnected = false; const serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor, null]; const characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState, null]; //presets configured for (const preset of this.presets) { preset.serviceType = serviceType[preset.displayType]; preset.characteristicType = characteristicType[preset.displayType]; preset.state = false; preset.previousSettings = {}; } //schedules configured for (const schedule of this.schedules) { schedule.serviceType = serviceType[schedule.displayType]; schedule.characteristicType = characteristicType[schedule.displayType]; schedule.state = false; } //scenes configured for (const scene of this.scenes) { scene.serviceType = serviceType[scene.displayType]; scene.characteristicType = characteristicType[scene.displayType]; scene.state = false; } //buttons configured for (const button of this.buttons) { button.serviceType = serviceType[button.displayType]; button.characteristicType = characteristicType[button.displayType]; button.state = false; button.previousValue = null; } this.functions = new Functions(this.logWarn, this.logError, this.logDebug) .on('warn', warn => this.emit('warn', warn)) .on('error', error => this.emit('error', error)) .on('debug', debug => this.emit('debug', debug)); //other variables this.displayDeviceInfo = true; this.deviceData = {}; this.accessory = {}; } async setOverExternalIntegration(integration, deviceData, key, value) { try { const accountTypeMelCloud = this.accountTypeMelCloud; let payload = {}; let flag = null; switch (key) { case 'Power': payload.power = value; flag = Ventilation.EffectiveFlags.Power; break; case 'OperationMode': payload.operationMode = value; flag = Ventilation.EffectiveFlags.OperationMode; break; case 'VentilationMode': payload.ventilationMode = value; flag = Ventilation.EffectiveFlags.VentilationMode; break; case 'SetTemperature': payload.setTemperature = value; flag = Ventilation.EffectiveFlags.SetTemperature; break; case 'DefaultCoolingSetTemperature': payload.defaultCoolingSetTemperature = value; flag = Ventilation.EffectiveFlags.SetTemperature; break; case 'DefaultHeatingSetTemperature': payload.defaultHeatingSetTemperature = value; flag = Ventilation.EffectiveFlags.SetTemperature; break; case 'NightPurgeMode': if (!accountTypeMelCloud) return; payload.nightPurgeMode = value; flag = Ventilation.EffectiveFlags.NightPurgeMode; break; case 'SetFanSpeed': payload.setFanSpeed = value; flag = Ventilation.EffectiveFlags.SetFanSpeed; break; case 'Schedules': if (accountTypeMelCloud) return; payload.enabled = value; flag = 'schedule'; break; case 'HolidayMode': if (accountTypeMelCloud) return; payload.enabled = value; flag = 'holidaymode'; break; default: this.emit('warn', `${integration}, received key: ${key}, value: ${value}`); return; }; return await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, flag); } catch (error) { throw new Error(`${integration} set key: ${key}, value: ${value}, error: ${error.message ?? error}`); }; } async externalIntegrations() { //RESTFul server const restFulEnabled = this.restFul.enable || false; if (restFulEnabled) { try { await new Promise((resolve) => { const timer = setTimeout(resolve, 5000); this.restFul1 = new RestFul({ port: this.device.restFul.port, logWarn: this.logWarn, logDebug: this.logDebug, }) .once('connected', (success) => { clearTimeout(timer); this.restFulConnected = true; this.emit('success', success); resolve(); }) .on('set', async (key, value) => { try { await this.setOverExternalIntegration('RESTFul', this.deviceData, key, value); } catch (error) { if (this.logWarn) this.emit('warn', `RESTFul set error: ${error}`); }; }) .on('debug', (debug) => this.emit('debug', debug)) .on('warn', (warn) => this.emit('warn', warn)) .on('error', (error) => this.emit('error', error)); }); } catch (error) { this.emit('warn', `RESTFul integration start error: ${error}`); } } const mqttEnabled = this.mqtt.enable || false; if (mqttEnabled) { try { await new Promise((resolve) => { const timer = setTimeout(resolve, 10000); this.mqtt1 = new Mqtt({ host: this.mqtt.host, port: this.mqtt.port || 1883, clientId: this.mqtt.clientId ? `melcloud_${this.mqtt.clientId}_${Math.random().toString(16).slice(3)}` : `melcloud_${Math.random().toString(16).slice(3)}`, prefix: this.mqtt.prefix ? `melcloud/${this.mqtt.prefix}/${this.deviceTypeString}/${this.deviceName}` : `melcloud/${this.deviceTypeString}/${this.deviceName}`, user: this.mqtt.auth?.user, passwd: this.mqtt.auth?.passwd, logWarn: this.logWarn, logDebug: this.logDebug }) .once('connected', (success) => { clearTimeout(timer); this.mqttConnected = true; this.emit('success', success); resolve(); }) .on('subscribed', (success) => { this.emit('success', success); }) .on('set', async (key, value) => { try { await this.setOverExternalIntegration('MQTT', this.deviceData, key, value); } catch (error) { if (this.logWarn) this.emit('warn', `MQTT set, error: ${error}`); }; }) .on('debug', (debug) => this.emit('debug', debug)) .on('warn', (warn) => this.emit('warn', warn)) .on('error', (error) => this.emit('error', error)); }); } catch (error) { this.emit('warn', `MQTT integration start error: ${error}`); } }; return true; } //prepare accessory async prepareAccessory() { try { const accountTypeMelCloud = this.accountTypeMelCloud; const deviceData = this.deviceData; const deviceId = this.deviceId; const deviceTypeString = this.deviceTypeString; const deviceName = this.deviceName; const accountName = this.accountName; const presetsOnServer = this.accessory.presets; const schedulesOnServer = this.accessory.schedules; const scenesOnServer = this.accessory.scenes; const supportsRoomTemperature = this.accessory.supportsRoomTemperature; const supportsSupplyTemperature = this.accessory.supportsSupplyTemperature; const supportsOutdoorTemperature = this.accessory.supportsOutdoorTemperature; const supportsCoolOperationMode = this.accessory.supportsCoolOperationMode; const supportsHeatOperationMode = this.accessory.supportsHeatOperationMode; const supportsAutoVentilationMode = this.accessory.supportsAutoVentilationMode; const supportsBypassVentilationMode = this.accessory.supportsBypassVentilationMode; const supportsAutomaticFanSpeed = this.accessory.supportsAutomaticFanSpeed; const supportsCO2Sensor = this.accessory.supportsCO2Sensor; const supportsPM25Sensor = this.accessory.supportsPM25Sensor; const supportsFanSpeed = this.accessory.supportsFanSpeed; const numberOfFanSpeeds = this.accessory.numberOfFanSpeeds; //accessory if (this.logDebug) this.emit('debug', `Prepare accessory`); const accessoryName = deviceName; const accessoryUUID = AccessoryUUID.generate(accountName + deviceId.toString()); const accessoryCategory = Categories.AIR_PURIFIER; const accessory = new Accessory(accessoryName, accessoryUUID, accessoryCategory); //information service if (this.logDebug) this.emit('debug', `Prepare information service`); this.informationService = accessory.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Manufacturer, this.manufacturer) .setCharacteristic(Characteristic.Model, this.model) .setCharacteristic(Characteristic.SerialNumber, this.serialNumber) .setCharacteristic(Characteristic.FirmwareRevision, this.firmwareRevision) .setCharacteristic(Characteristic.ConfiguredName, accessoryName); //services const serviceName = `${deviceTypeString} ${accessoryName}`; switch (this.displayType) { case 1: //Heater Cooler if (this.logDebug) this.emit('debug', `Prepare heather/cooler service`); const melCloudService = new Service.HeaterCooler(serviceName, `HeaterCooler ${deviceId}`); melCloudService.setPrimaryService(true); melCloudService.getCharacteristic(Characteristic.Active) .onGet(async () => { const state = this.accessory.power; return state; }) .onSet(async (state) => { try { const payload = { power: state ? true : false }; if (this.logInfo) this.emit('info', `Set power: ${state ? 'On' : 'Off'}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, Ventilation.EffectiveFlags.Power); } catch (error) { if (this.logWarn) this.emit('warn', `Set power error: ${error}`); }; }); melCloudService.getCharacteristic(Characteristic.CurrentHeaterCoolerState) .onGet(async () => { const value = this.accessory.currentOperationMode; return value; }); melCloudService.getCharacteristic(Characteristic.TargetHeaterCoolerState) .setProps({ minValue: this.accessory.operationModeSetPropsMinValue, maxValue: this.accessory.operationModeSetPropsMaxValue, validValues: this.accessory.operationModeSetPropsValidValues }) .onGet(async () => { const value = this.accessory.targetOperationMode ?? 0; //LOSSNAY, BYPASS, AUTO return value; }) .onSet(async (value) => { try { switch (value) { case 0: //AUTO - AUTO value = supportsAutoVentilationMode ? 2 : 0; break; case 1: //HEAT - LOSSNAY value = 0; break; case 2: //COOL - BYPASS value = supportsBypassVentilationMode ? 1 : 0; break; }; const payload = { ventilationMode: value }; if (this.logInfo) this.emit('info', `Set operation mode: ${Ventilation.VentilationModeMapEnumToString[value]}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, Ventilation.EffectiveFlags.VentilationMode); } catch (error) { if (this.logWarn) this.emit('warn', `Set operation mode error: ${error}`); }; }); melCloudService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.accessory.roomTemperature; return value; }); if (supportsFanSpeed) { melCloudService.getCharacteristic(Characteristic.RotationSpeed) .setProps({ minValue: this.accessory.fanSpeedSetPropsMinValue, maxValue: this.accessory.fanSpeedSetPropsMaxValue, minStep: 1 }) .onGet(async () => { const value = this.accessory.fanSpeed; //STOP, 1, 2, 3, 4, OFF return value; }) .onSet(async (value) => { try { const payload = {}; const max = numberOfFanSpeeds; const minValue = supportsAutomaticFanSpeed ? 0 : 1; const clampedValue = Math.min(Math.max(value, minValue), max); payload.setFanSpeed = clampedValue; if (this.logInfo) this.emit('info', `Set fan speed mode: ${Ventilation.FanSpeedMapEnumToString[clampedValue]}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, Ventilation.EffectiveFlags.SetFanSpeed); } catch (error) { if (this.logWarn) this.emit('warn', `Set fan speed mode error: ${error}`); }; }); } //device can cool if (supportsAutoVentilationMode && supportsCoolOperationMode) { melCloudService.getCharacteristic(Characteristic.CoolingThresholdTemperature) .setProps({ minValue: this.accessory.minTempCoolDryAuto, maxValue: this.accessory.maxTempCoolDryAuto, minStep: this.accessory.temperatureIncrement }) .onGet(async () => { const value = this.accessory.ventilationMode === 2 ? this.accessory.defaultHeatingSetTemperature : this.accessory.setTemperature; return value; }) .onSet(async (value) => { try { deviceData.Device.DefaultCoolingSetTemperature = value; const payload = {}; if (this.logInfo) this.emit('info', `Set cooling threshold temperature: ${value}${this.accessory.temperatureUnit}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, Ventilation.EffectiveFlags.SetTemperature); } catch (error) { if (this.logWarn) this.emit('warn', `Set cooling threshold temperature error: ${error}`); }; }); }; //device can heat if (supportsAutoVentilationMode && supportsHeatOperationMode) { melCloudService.getCharacteristic(Characteristic.HeatingThresholdTemperature) .setProps({ minValue: this.accessory.minTempHeat, maxValue: this.accessory.maxTempHeat, minStep: this.accessory.temperatureIncrement }) .onGet(async () => { const value = this.accessory.ventilationMode === 2 ? this.accessory.defaultHeatingSetTemperature : this.accessory.setTemperature; return value; }) .onSet(async (value) => { try { deviceData.Device.DefaultHeatingSetTemperature = value; const payload = {}; if (this.logInfo) this.emit('info', `Set heating threshold temperature: ${value}${this.accessory.temperatureUnit}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, Ventilation.EffectiveFlags.SetTemperature); } catch (error) { if (this.logWarn) this.emit('warn', `Set heating threshold temperature error: ${error}`); }; }); }; melCloudService.getCharacteristic(Characteristic.TemperatureDisplayUnits) .onGet(async () => { const value = this.accessory.useFahrenheit; return value; }) .onSet(async (value) => { if (!accountTypeMelCloud) return; try { this.accessory.useFahrenheit = value ? true : false; this.melCloudAccountData.UseFahrenheit = value ? true : false; this.melCloudAccountData.Account.LoginData.UseFahrenheit = value ? true : false; const payload = this.melCloudAccountData; if (this.logInfo) this.emit('info', `Set temperature display unit: ${TemperatureDisplayUnits[value]}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, 'account'); } catch (error) { if (this.logWarn) this.emit('warn', `Set temperature display unit error: ${error}`); }; }); this.melCloudService = melCloudService; break; case 2: //Thermostat if (this.logDebug) this.emit('debug', `Prepare thermostat service`); const melCloudServiceT = new Service.Thermostat(serviceName, `Thermostat ${deviceId}`); melCloudServiceT.setPrimaryService(true); melCloudServiceT.getCharacteristic(Characteristic.CurrentHeatingCoolingState) .onGet(async () => { const value = this.accessory.currentOperationMode; return value; }); melCloudServiceT.getCharacteristic(Characteristic.TargetHeatingCoolingState) .setProps({ minValue: this.accessory.operationModeSetPropsMinValue, maxValue: this.accessory.operationModeSetPropsMaxValue, validValues: this.accessory.operationModeSetPropsValidValues }) .onGet(async () => { const value = this.accessory.targetOperationMode ?? 0; //LOSSNAY, BYPASS, AUTO return value; }) .onSet(async (value) => { try { const payload = {}; let flag = null; switch (value) { case 0: //OFF - POWER OFF payload.power = false; flag = Ventilation.EffectiveFlags.Power; break; case 1: //HEAT - LOSSNAY payload.power = true; flag = Ventilation.EffectiveFlags.Power + Ventilation.EffectiveFlags.VentilationMode; break; case 2: //COOL - BYPASS payload.ventilationMode = supportsBypassVentilationMode ? 1 : 0; flag = Ventilation.EffectiveFlags.Power + Ventilation.EffectiveFlags.VentilationMode; break; case 3: //AUTO - AUTO payload.ventilationMode = supportsAutoVentilationMode ? 2 : 0; flag = Ventilation.EffectiveFlags.Power + Ventilation.EffectiveFlags.VentilationMode; break; }; if (this.logInfo) this.emit('info', `Set operation mode: ${Ventilation.VentilationModeMapEnumToString[payload.ventilationMode]}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, flag); } catch (error) { if (this.logWarn) this.emit('warn', `Set operation mode error: ${error}`); }; }); melCloudServiceT.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.accessory.roomTemperature; return value; }); melCloudServiceT.getCharacteristic(Characteristic.TargetTemperature) .setProps({ minValue: this.accessory.minTempHeat, maxValue: this.accessory.maxTempHeat, minStep: this.accessory.temperatureIncrement }) .onGet(async () => { const value = this.accessory.setTemperature; return value; }) .onSet(async (value) => { try { const payload = { setTemperature: value }; if (this.logInfo) this.emit('info', `Set temperature: ${value}${this.accessory.temperatureUnit}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, Ventilation.EffectiveFlags.SetTemperature); } catch (error) { if (this.logWarn) this.emit('warn', `Set temperature error: ${error}`); }; }); melCloudServiceT.getCharacteristic(Characteristic.TemperatureDisplayUnits) .onGet(async () => { const value = this.accessory.useFahrenheit; return value; }) .onSet(async (value) => { if (!accountTypeMelCloud) return; try { this.accessory.useFahrenheit = value ? true : false; this.melCloudAccountData.UseFahrenheit = value ? true : false; this.melCloudAccountData.Account.LoginData.UseFahrenheit = value ? true : false; const payload = this.melCloudAccountData; if (this.logInfo) this.emit('info', `Set temperature display unit: ${TemperatureDisplayUnits[value]}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, 'account'); } catch (error) { if (this.logWarn) this.emit('warn', `Set temperature display unit error: ${error}`); }; }); this.melCloudService = melCloudServiceT; break; default: if (this.logWarn) this.emit('warn', `Received unknown display type: ${this.displayType}`); return; }; //add service to accessory accessory.addService(this.melCloudService); //temperature sensor service room if (this.temperatureRoomSensor && supportsRoomTemperature) { if (this.logDebug) this.emit('debug', `Prepare room temperature sensor service`); this.roomTemperatureSensorService = new Service.TemperatureSensor(`${serviceName} Room`, `roomTemperatureSensorService${deviceId}`); this.roomTemperatureSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.roomTemperatureSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Room`); this.roomTemperatureSensorService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const state = this.accessory.roomTemperature; return state; }) accessory.addService(this.roomTemperatureSensorService); } //temperature sensor service supply if (this.temperatureSupplySensor && supportsSupplyTemperature) { if (this.logDebug) this.emit('debug', `Prepare supply temperature sensor service`); this.supplyTemperatureSensorService = new Service.TemperatureSensor(`${serviceName} Supply`, `supplyTemperatureSensorService${deviceId}`); this.supplyTemperatureSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.supplyTemperatureSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Supply`); this.supplyTemperatureSensorService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const state = this.accessory.supplyTemperature; return state; }) accessory.addService(this.supplyTemperatureSensorService); } //temperature sensor service outdoor if (this.temperatureOutdoorSensor && supportsOutdoorTemperature) { if (this.logDebug) this.emit('debug', `Prepare outdoor temperature sensor service`); this.outdoorTemperatureSensorService = new Service.TemperatureSensor(`${serviceName} Outdoor`, `outdoorTemperatureSensorService${deviceId}`); this.outdoorTemperatureSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.outdoorTemperatureSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Outdoor`); this.outdoorTemperatureSensorService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const state = this.accessory.outdoorTemperature; return state; }) accessory.addService(this.outdoorTemperatureSensorService); } //core maintenance if (this.accessory.coreMaintenanceRequired !== null) { this.coreMaintenanceService = new Service.FilterMaintenance(`${serviceName} Core Maintenance`, `coreMaintenanceService${deviceId}`); this.coreMaintenanceService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.coreMaintenanceService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Core Maintenance`); this.coreMaintenanceService.getCharacteristic(Characteristic.FilterChangeIndication) .onGet(async () => { const value = this.accessory.coreMaintenanceRequired; return value; }); this.coreMaintenanceService.getCharacteristic(Characteristic.ResetFilterIndication) .onSet(async (state) => { }); accessory.addService(this.coreMaintenanceService); } //filter maintenance if (this.accessory.filterMaintenanceRequired !== null) { this.filterMaintenanceService = new Service.FilterMaintenance(`${serviceName} Filter Maintenance`, `filterMaintenanceService${deviceId}`); this.filterMaintenanceService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.filterMaintenanceService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Filter Maintenance`); this.filterMaintenanceService.getCharacteristic(Characteristic.FilterChangeIndication) .onGet(async () => { const value = this.accessory.filterMaintenanceRequired; return value; }); this.filterMaintenanceService.getCharacteristic(Characteristic.ResetFilterIndication) .onSet(async (state) => { }); accessory.addService(this.filterMaintenanceService); } //room CO2 sensor if (supportsCO2Sensor) { this.carbonDioxideSensorService = new Service.CarbonDioxideSensor(`${serviceName} CO2 Sensor`, `carbonDioxideSensorService${deviceId}`); this.carbonDioxideSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.carbonDioxideSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} CO2 Sensor`); this.carbonDioxideSensorService.getCharacteristic(Characteristic.CarbonDioxideDetected) .onGet(async () => { const value = this.accessory.roomCO2Detected; return value; }); this.carbonDioxideSensorService.getCharacteristic(Characteristic.CarbonDioxideLevel) .onGet(async () => { const value = this.accessory.roomCO2Level; return value; }); accessory.addService(this.carbonDioxideSensorService); } //room PM2.5 sensor if (supportsPM25Sensor) { this.airQualitySensorService = new Service.AirQualitySensor(`${serviceName} PM2.5 Sensor`, `airQualitySensorService${deviceId}`); this.airQualitySensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.airQualitySensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} PM2.5 Sensor`); this.airQualitySensorService.getCharacteristic(Characteristic.AirQuality) .onGet(async () => { const value = this.accessory.pM25AirQuality; return value; }); this.airQualitySensorService.getCharacteristic(Characteristic.PM2_5Density) .onGet(async () => { const value = this.accessory.pM25Level; return value; }); accessory.addService(this.airQualitySensorService); } //in standby sensor if (this.inStandbySensor && this.accessory.inStandbyMode !== null) { if (this.logDebug) this.emit('debug', `Prepare in standby mode service`); this.inStandbyService = new Service.ContactSensor(`${serviceName} In Standby`, `inStandbyService${deviceId}`); this.inStandbyService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.inStandbyService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} In Standby`); this.inStandbyService.getCharacteristic(Characteristic.ContactSensorState) .onGet(async () => { const state = this.accessory.inStandbyMode; return state; }) accessory.addService(this.inStandbyService); } //connect sensor if (this.connectSensor && this.accessory.isConnected !== null) { if (this.logDebug) this.emit('debug', `Prepare connect service`); this.connectService = new Service.ContactSensor(`${serviceName} Connected`, `connectService${deviceId}`); this.connectService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.connectService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Connected`); this.connectService.getCharacteristic(Characteristic.ContactSensorState) .onGet(async () => { const state = this.accessory.isConnected; return state; }) accessory.addService(this.connectService); } //error sensor if (this.errorSensor && this.accessory.isInError !== null) { if (this.logDebug) this.emit('debug', `Prepare error service`); this.errorService = new Service.ContactSensor(`${serviceName} Error`, `errorService${deviceId}`); this.errorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.errorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Error`); this.errorService.getCharacteristic(Characteristic.ContactSensorState) .onGet(async () => { const state = this.accessory.isInError; return state; }) accessory.addService(this.errorService); } //holiday mode if (this.holidayModeSupport && this.accessory.holidayModeEnabled !== null) { //control if (this.logDebug) this.emit('debug', `Prepare holiday mode control service`); this.holidayModeControlService = new Service.Switch(`${serviceName} Holiday Mode`, `holidayModeControlService${deviceId}`); this.holidayModeControlService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.holidayModeControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Holiday Mode`); this.holidayModeControlService.getCharacteristic(Characteristic.On) .onGet(async () => { const state = this.accessory.holidayModeEnabled; return state; }) .onSet(async (state) => { try { const payload = { enabled: state }; if (this.logInfo) this.emit('info', `Holiday mode: ${state ? 'Enabled' : 'Disabled'}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, 'holidaymode'); } catch (error) { if (this.logWarn) this.emit('warn', `Set holiday mode error: ${error}`); }; }); accessory.addService(this.holidayModeControlService); if (this.logDebug) this.emit('debug', `Prepare holiday mode control sensor service`); this.holidayModeControlSensorService = new Service.ContactSensor(`${serviceName} Holiday Mode Control`, `holidayModeControlSensorService${deviceId}`); this.holidayModeControlSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.holidayModeControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Holiday Mode Control`); this.holidayModeControlSensorService.getCharacteristic(Characteristic.ContactSensorState) .onGet(async () => { const state = this.accessory.holidayModeEnabled; return state; }) accessory.addService(this.holidayModeControlSensorService); //sensors if (this.logDebug) this.emit('debug', `Prepare holiday mode sensor service`); this.holidayModeSensorService = new Service.ContactSensor(`${serviceName} Holiday Mode`, `holidayModeSensorService${deviceId}`); this.holidayModeSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.holidayModeSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Holiday Mode State`); this.holidayModeSensorService.getCharacteristic(Characteristic.ContactSensorState) .onGet(async () => { const state = this.accessory.holidayMode.Active; return state; }) accessory.addService(this.holidayModeSensorService); } //presets if (this.presets.length > 0) { if (this.logDebug) this.emit('debug', `Prepare presets services`); this.presetControlServices = []; this.presetControlSensorServices = []; this.presets.forEach((preset, i) => { //get name const name = preset.name || `Preset ${i}`; //get name prefix const namePrefix = preset.namePrefix; const serviceName1 = namePrefix ? `${accessoryName} ${name}` : name; const serviceType = preset.serviceType; const characteristicType = preset.characteristicType; //control if (preset.displayType > 3) { if (this.logDebug) this.emit('debug', `Prepare preset control ${name} service`); const presetControlService = new Service.Switch(serviceName1, `presetControlService${deviceId} ${i}`); presetControlService.addOptionalCharacteristic(Characteristic.ConfiguredName); presetControlService.setCharacteristic(Characteristic.ConfiguredName, serviceName1); presetControlService.getCharacteristic(Characteristic.On) .onGet(async () => { const state = preset.state; return state; }) .onSet(async (state) => { try { let payload = {}; switch (state) { case true: preset.previousSettings = deviceData.Device; const presetData = presetsOnServer.find(p => String(p.ID) === preset.id); payload = { power: presetData.Power, operationMode: presetData.OperationMode, setTemperature: presetData.SetTemperature, vaneHorizontalDirection: presetData.VaneHorizontalDirection, vaneVerticalDirection: presetData.VaneVerticalDirection, setFanSpeed: presetData.SetFanSpeed }; break; case false: payload = { power: preset.previousSettings.Power, operationMode: preset.previousSettings.OperationMode, setTemperature: preset.previousSettings.SetTemperature, vaneHorizontalDirection: preset.previousSettings.VaneHorizontalDirection, vaneVerticalDirection: preset.previousSettings.VaneVerticalDirection, setFanSpeed: preset.previousSettings.SetFanSpeed }; break; }; if (this.logInfo) this.emit('info', `Preset ${name}: ${state ? 'Set' : 'Unset'}`); await this.melCloudErv.send(this.accountType, this.displayType, deviceData, payload, Ventilation.EffectiveFlags.Presets); } catch (error) { if (this.logWarn) this.emit('warn', `Set preset error: ${error}`); }; }); this.presetControlServices.push(presetControlService); accessory.addService(presetControlService); } //sensor if (preset.displayType < 7) { if (this.logDebug) this.emit('debug', `Prepare preset control sensor s${name} ervice`); const presetControlSensorService = new serviceType(serviceName1, `presetControlSensorService${deviceId} ${i}`); presetControlSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); presetControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${serviceName1} Control`); presetControlSensorService.getCharacteristic(characteristicType) .onGet(async () => { const state = preset.state; return state; }) this.presetControlSensorServices.push(presetControlSensorService); accessory.addService(presetControlSensorService); } }); } //schedules if (this.schedules.length > 0 && this.accessory.scheduleEnabled !== null) { if (this.logDebug) this.emit('debug', `Prepare schedules services`); this.scheduleSensorServices = []; this.schedules.forEach((schedule, i) => { //get name const name = schedule.name || `Schedule ${i}`; //get name prefix const namePrefix = schedule.namePrefix; const serviceName1 = namePrefix ? `${accessoryName} ${name}` : name; const serviceName2 = namePrefix ? `${accessoryName} Schedules` : 'Schedules'; const serviceType = schedule.serviceType; const characteristicType = schedule.characteristicType; //control if (i === 0) { if (schedule.displayType > 3) { if (this.logDebug) this.emit('debug', `Prepare schedule control ${name} service`); this.scheduleControlService = new Service.Switch(serviceName2, `scheduleControlService${deviceId} ${i}`); this.scheduleControlService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.scheduleControlService.setCharacteristic(Characteristic.ConfiguredName, serviceName2); this.scheduleControlService.getCharacteristic(Characteristic.On) .onGet(async () => { const state = this.accessory.scheduleEnabled; return state; })