UNPKG

homebridge-melcloud-control

Version:

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

844 lines (802 loc) • 132 kB
import EventEmitter from 'events'; import MelCloudAta from './melcloudata.js'; import RestFul from './restful.js'; import Mqtt from './mqtt.js'; import Functions from './functions.js'; import { TemperatureDisplayUnits, AirConditioner, DeviceType } from './constants.js'; let Accessory, Characteristic, Service, Categories, AccessoryUUID; class DeviceAta 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.heatDryFanMode = device.heatDryFanMode || 1; //NONE, HEAT, DRY, FAN this.coolDryFanMode = device.coolDryFanMode || 1; //NONE, COOL, DRY, FAN this.autoDryFanMode = device.autoDryFanMode || 1; //NONE, AUTO, DRY, FAN this.temperatureRoomSensor = device.temperatureRoomSensor || false; this.temperatureOutdoorSensor = device.temperatureOutdoorSensor || false; this.inStandbySensor = device.inStandbySensor || false; this.connectSensor = device.connectSensor || false; this.errorSensor = device.errorSensor || false; this.frostProtectionSupport = device.frostProtectionSupport || false; this.overheatProtectionSupport = device.overheatProtectionSupport || false; this.holidayModeSupport = device.holidayModeSupport || false; this.remoteRoomTemperatureSupport = device.remoteRoomTemperatureSupport || 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 = {}; this.remoteRoomTemperature = 20; } async setOverExternalIntegration(integration, deviceData, key, value) { try { const accountTypeMelCloud = this.accountTypeMelCloud; let payload = {}; let flag = null; switch (key) { case 'Power': payload.power = value; break; case 'OperationMode': payload.operationMode = value; flag = AirConditioner.EffectiveFlags.OperationMode break; case 'SetTemperature': payload.setTemperature = value; flag = AirConditioner.EffectiveFlags.SetTemperature; break; case 'DefaultCoolingSetTemperature': payload.defaultCoolingSetTemperature = value; flag = AirConditioner.EffectiveFlags.SetTemperature; break; case 'DefaultHeatingSetTemperature': payload.defaultHeatingSetTemperature = value; flag = AirConditioner.EffectiveFlags.SetTemperature; break; case 'FanSpeed': key = accountTypeMelCloud ? 'fanSpeed' : 'setFanSpeed'; payload[key] = value; flag = AirConditioner.EffectiveFlags.SetFanSpeed; break; case 'VaneHorizontalDirection': payload.vaneHorizontalDirection = value; flag = AirConditioner.EffectiveFlags.VaneHorizontalDirection; break; case 'VaneVerticalDirection': payload.vaneVerticalDirection = value; flag = AirConditioner.EffectiveFlags.VaneVerticalDirection; break; case 'ProhibitSetTemperature': if (!accountTypeMelCloud) return; payload.prohibitSetTemperature = value; flag = AirConditioner.EffectiveFlags.Prohibit; break; case 'ProhibitOperationMode': if (!accountTypeMelCloud) return; payload.prohibitOperationMode = value; flag = AirConditioner.EffectiveFlags.Prohibit; break; case 'ProhibitPower': if (!accountTypeMelCloud) return; payload.prohibitPower = value; flag = AirConditioner.EffectiveFlags.Prohibit; break; case 'FrostProtection': if (accountTypeMelCloud) return; payload.enabled = value; flag = 'frostprotection'; break; case 'OverheatProtection': if (accountTypeMelCloud) return; payload.enabled = value; flag = 'overheatprotection'; break; case 'Schedules': if (accountTypeMelCloud) return; payload.enabled = value; flag = 'schedule'; break; case 'HolidayMode': if (accountTypeMelCloud) return; payload.enabled = value; flag = 'holidaymode'; break; case 'RemoteRoomTemperature': this.remoteRoomTemperature = value; return; default: this.emit('warn', `${integration}, received key: ${key}, value: ${value}`); return; }; return await this.melCloudAta.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 supportsHeat = this.accessory.supportsHeat; const supportsDry = this.accessory.supportsDry; const supportsCool = this.accessory.supportsCool; const supportsAuto = this.accessory.supportsAuto; const supportsFanSpeed = this.accessory.supportsFanSpeed; const supportsAutomaticFanSpeed = this.accessory.supportsAutomaticFanSpeed; const supportsOutdoorTemperature = this.accessory.supportsOutdoorTemperature; const numberOfFanSpeeds = this.accessory.numberOfFanSpeeds; const supportsSwingFunction = this.accessory.supportsSwingFunction; const supportsWideVane = this.accessory.supportsWideVane; const autoDryFanMode = [this.accessory.operationMode, 8, supportsDry ? 2 : 8, 7][this.autoDryFanMode]; //NONE, AUTO - 8, DRY - 2, FAN - 7 const heatDryFanMode = [this.accessory.operationMode, 1, supportsDry ? 2 : 1, 7][this.heatDryFanMode]; //NONE, HEAT - 1, DRY - 2, FAN - 7 const coolDryFanMode = [this.accessory.operationMode, 3, supportsDry ? 2 : 3, 7][this.coolDryFanMode]; //NONE, COOL - 3, DRY - 2, FAN - 7 //accessory if (this.logDebug) this.emit('debug', `Prepare accessory`); const accessoryName = deviceName; const accessoryUUID = AccessoryUUID.generate(accountName + deviceId.toString()); const accessoryCategory = Categories.AIR_CONDITIONER; 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); //melcloud services const serviceName = `${deviceTypeString} ${accessoryName}`; switch (this.displayType) { case 1: //Heater Cooler if (this.logDebug) this.emit('debug', `Prepare heater/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.melCloudAta.send(this.accountType, this.displayType, deviceData, payload); } 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; //1 = HEAT, 2 = DRY 3 = COOL, 7 = FAN, 8 = AUTO return value; }) .onSet(async (value) => { try { switch (value) { case 0: //AUTO - AUTO value = autoDryFanMode; break; case 1: //HEAT - HEAT value = heatDryFanMode; break; case 2: //COOL - COOL value = coolDryFanMode; break; }; const payload = { operationMode: value }; if (this.logInfo) this.emit('info', `Set operation mode: ${AirConditioner.OperationModeMapEnumToString[value]}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, AirConditioner.EffectiveFlags.OperationMode); } 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.currentFanSpeed; //AUTO, 1, 2, 3, 4, 5, 6, OFF return value; }) .onSet(async (value) => { try { const payload = {}; const fanKeySet = accountTypeMelCloud ? 'fanSpeed' : 'setFanSpeed'; const max = numberOfFanSpeeds; const minValue = supportsAutomaticFanSpeed ? 0 : 1; const clampedValue = Math.min(Math.max(value, minValue), max); payload[fanKeySet] = clampedValue; if (this.logInfo) this.emit('info', `Set fan speed mode: ${AirConditioner.FanSpeedMapEnumToString[clampedValue]}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, AirConditioner.EffectiveFlags.SetFanSpeed); } catch (error) { if (this.logWarn) this.emit('warn', `Set fan speed mode error: ${error}`); }; }); }; if (supportsSwingFunction) { melCloudService.getCharacteristic(Characteristic.SwingMode) .onGet(async () => { //Vane Horizontal: Auto, 1, 2, 3, 4, 5, 6, 7 = Split, 12 = Swing //Vertical: Auto, 1, 2, 3, 4, 5, 7 = Swing //Home Vane Horizontal: Auto, 1, 2, 3, 4, 5, 6, 7 = Swing, 8 = Split //Vertical: Auto, 1, 2, 3, 4, 5, 6 = Swing const value = this.accessory.currentSwingMode; return value; }) .onSet(async (value) => { try { const payload = {}; if (supportsWideVane) payload.vaneHorizontalDirection = value ? 12 : 0; payload.vaneVerticalDirection = value ? 7 : 0; if (this.logInfo) this.emit('info', `Set air direction mode: ${AirConditioner.AirDirectionMapEnumToString[value]}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, AirConditioner.EffectiveFlags.VaneVerticalVaneHorizontal); } catch (error) { if (this.logWarn) this.emit('warn', `Set air direction mode error: ${error}`); }; }); }; melCloudService.getCharacteristic(Characteristic.CoolingThresholdTemperature) // 16 - 31 .setProps({ minValue: this.accessory.minSetCoolDryAutoRoomTemperature, maxValue: this.accessory.maxSetHeatCoolDryAutoRoomTemperature, minStep: this.accessory.temperatureStep }) .onGet(async () => { const value = this.accessory.operationMode === 8 ? this.accessory.defaultCoolingSetTemperature : this.accessory.setTemperature; return value; }) .onSet(async (value) => { try { if (this.accessory.operationMode === 8) deviceData.Device.DefaultCoolingSetTemperature = value < 16 ? 16 : value; const payload = { setTemperature: value < 16 ? 16 : value }; if (this.logInfo) this.emit('info', `Set cooling threshold temperature: ${value}${this.accessory.temperatureUnit}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, AirConditioner.EffectiveFlags.SetTemperature); } catch (error) { if (this.logWarn) this.emit('warn', `Set cooling threshold temperature error: ${error}`); }; }); if (supportsHeat) { melCloudService.getCharacteristic(Characteristic.HeatingThresholdTemperature) // 10 - 31 .setProps({ minValue: this.accessory.minSetHeatRoomTemperature, maxValue: this.accessory.maxSetHeatCoolDryAutoRoomTemperature, minStep: this.accessory.temperatureStep }) .onGet(async () => { const value = this.accessory.operationMode === 8 ? this.accessory.defaultHeatingSetTemperature : this.accessory.setTemperature; return value; }) .onSet(async (value) => { try { if (this.accessory.operationMode === 8) deviceData.Device.DefaultHeatingSetTemperature = value; const payload = { setTemperature: value }; if (this.logInfo) this.emit('info', `Set heating threshold temperature: ${value}${this.accessory.temperatureUnit}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, AirConditioner.EffectiveFlags.SetTemperature); } catch (error) { if (this.logWarn) this.emit('warn', `Set heating threshold temperature error: ${error}`); }; }); }; melCloudService.getCharacteristic(Characteristic.LockPhysicalControls) .onGet(async () => { const value = this.accessory.lockPhysicalControl; return value; }) .onSet(async (value) => { if (!accountTypeMelCloud) return; try { value = value ? true : false; const payload = {}; payload.prohibitSetTemperature = value; payload.prohibitOperationMode = value; payload.prohibitPower = value; if (this.logInfo) this.emit('info', `Set local physical controls: ${value ? 'Lock' : 'Unlock'}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, AirConditioner.EffectiveFlags.Prohibit); } catch (error) { if (this.logWarn) this.emit('warn', `Set lock physical controls 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.melCloudAta.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; //1 = HEAT, 2 = DRY 3 = COOL, 7 = FAN, 8 = AUTO return value; }) .onSet(async (value) => { try { let flag = null; switch (value) { case 0: //OFF - POWER OFF value = deviceData.Device.OperationMode; break; case 1: //HEAT - HEAT value = heatDryFanMode; flag = AirConditioner.EffectiveFlags.OperationModeSetTemperature; break; case 2: //COOL - COOL value = coolDryFanMode; flag = AirConditioner.EffectiveFlags.OperationModeSetTemperature break; case 3: //AUTO - AUTO value = autoDryFanMode; flag = AirConditioner.EffectiveFlags.OperationModeSetTemperature; break; }; const payload = { operationMode: value }; if (this.logInfo) this.emit('info', `Set operation mode: ${AirConditioner.OperationModeMapEnumToString[value]}`); await this.melCloudAta.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.minSetCoolDryAutoRoomTemperature, maxValue: this.accessory.maxSetHeatCoolDryAutoRoomTemperature, minStep: this.accessory.temperatureStep }) .onGet(async () => { const value = this.accessory.setTemperature; return value; }) .onSet(async (value) => { try { if (deviceData.Device.OperationMode === 1 && value < this.accessory.minSetHeatRoomTemperature) { value = this.accessory.minSetHeatRoomTemperature; } else if (value < 16) { value = 16; } const payload = { setTemperature: value }; if (this.logInfo) this.emit('info', `Set temperature: ${value}${this.accessory.temperatureUnit}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, AirConditioner.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.melCloudAta.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 services if (this.temperatureRoomSensor && this.accessory.roomTemperature !== null) { 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); } 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); } //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); } //frost protection if (this.frostProtectionSupport && this.accessory.frostProtectionEnabled !== null) { //control if (this.logDebug) this.emit('debug', `Prepare frost protection control service`); const frostProtectionControlService = new Service.HeaterCooler(`${serviceName} Frost Protection`, `frostProtectionControlService${deviceId}`); frostProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName); frostProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`); frostProtectionControlService.getCharacteristic(Characteristic.Active) .onGet(async () => { const state = this.accessory.frostProtectionEnabled; return state; }) .onSet(async (state) => { try { const payload = { enabled: state ? true : false, min: deviceData.FrostProtection.Min, max: deviceData.FrostProtection.Max }; if (this.logInfo) this.emit('info', `Frost protection: ${state ? 'Enabled' : 'Disabled'}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, 'frostprotection'); } catch (error) { if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`); }; }); frostProtectionControlService.getCharacteristic(Characteristic.CurrentHeaterCoolerState) .onGet(async () => { const value = this.accessory.frostProtection.Active ? 2 : 1; return value; }) frostProtectionControlService.getCharacteristic(Characteristic.TargetHeaterCoolerState) .setProps({ minValue: 0, maxValue: 0, validValues: [0] }) .onGet(async () => { const value = 0 return value; }) .onSet(async (state) => { try { const payload = { enabled: true, min: deviceData.FrostProtection.Min, max: deviceData.FrostProtection.Max }; if (this.logInfo) this.emit('info', `Frost protection: Enabled`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, 'frostprotection'); } catch (error) { if (this.logWarn) this.emit('warn', `Set frost protection error: ${error}`); }; }); frostProtectionControlService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.accessory.roomTemperature; return value; }); frostProtectionControlService.getCharacteristic(Characteristic.CoolingThresholdTemperature) //max .setProps({ minValue: 6, maxValue: 16, minStep: 1 }) .onGet(async () => { const value = this.accessory.frostProtection.Max; return value; }) .onSet(async (value) => { try { let { min, max } = await this.functions.adjustTempProtection(deviceData.FrostProtection.Min, deviceData.FrostProtection.Max, value, 'max', 4, 14, 6, 16); const payload = { enabled: deviceData.FrostProtection.Enabled, min: min, max: max }; if (this.logInfo) this.emit('info', `Set frost protection max. temperature: ${max}${this.accessory.temperatureUnit}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, 'frostprotection'); } catch (error) { if (this.logWarn) this.emit('warn', `Set frost protection max. temperature error: ${error}`); }; }); frostProtectionControlService.getCharacteristic(Characteristic.HeatingThresholdTemperature) //min .setProps({ minValue: 4, maxValue: 14, minStep: 1 }) .onGet(async () => { const value = this.accessory.frostProtection.Min; return value; }) .onSet(async (value) => { try { let { min, max } = await this.functions.adjustTempProtection(deviceData.FrostProtection.Min, deviceData.FrostProtection.Max, value, 'min', 4, 14, 6, 16); const payload = { enabled: deviceData.FrostProtection.Enabled, min: min, max: max }; if (this.logInfo) this.emit('info', `Set frost protection min. temperature: ${min}${this.accessory.temperatureUnit}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, 'frostprotection'); } catch (error) { if (this.logWarn) this.emit('warn', `Set frost protection min. temperature error: ${error}`); }; }); this.frostProtectionControlService = frostProtectionControlService; accessory.addService(frostProtectionControlService); if (this.logDebug) this.emit('debug', `Prepare frost protection control sensor service`); this.frostProtectionControlSensorService = new Service.ContactSensor(`${serviceName} Frost Protection Control`, `frostProtectionControlSensorService${deviceId}`); this.frostProtectionControlSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.frostProtectionControlSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection Control`); this.frostProtectionControlSensorService.getCharacteristic(Characteristic.ContactSensorState) .onGet(async () => { const state = this.accessory.frostProtectionEnabled; return state; }) accessory.addService(this.frostProtectionControlSensorService); //sensor if (this.logDebug) this.emit('debug', `Prepare frost protection service`); this.frostProtectionSensorService = new Service.ContactSensor(`${serviceName} Frost Protection`, `frostProtectionSensorService${deviceId}`); this.frostProtectionSensorService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.frostProtectionSensorService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Frost Protection`); this.frostProtectionSensorService.getCharacteristic(Characteristic.ContactSensorState) .onGet(async () => { const state = this.accessory.frostProtection.Active; return state; }) accessory.addService(this.frostProtectionSensorService); } //overheat protection if (this.overheatProtectionSupport && this.accessory.overheatProtectionEnabled !== null) { //control if (this.logDebug) this.emit('debug', `Prepare overheat protection control service`); const overheatProtectionControlService = new Service.HeaterCooler(`${serviceName} Overheat Protection`, `overheatProtectionControlService${deviceId}`); overheatProtectionControlService.addOptionalCharacteristic(Characteristic.ConfiguredName); overheatProtectionControlService.setCharacteristic(Characteristic.ConfiguredName, `${accessoryName} Overheat Protection`); overheatProtectionControlService.getCharacteristic(Characteristic.Active) .onGet(async () => { const state = this.accessory.overheatProtectionEnabled; return state; }) .onSet(async (state) => { try { const payload = { enabled: state ? true : false, min: deviceData.OverheatProtection.Min, max: deviceData.OverheatProtection.Max }; if (this.logInfo) this.emit('info', `Overheat protection: ${state ? 'Enabled' : 'Disabled'}`); await this.melCloudAta.send(this.accountType, this.displayType, deviceData, payload, 'overheatprotection'); } catch (error) { if (this.logWarn) this.emit('warn', `Set overheat protection error: ${error}`); }; }); overheatProtectionControlService.getCharacteristic(Characteristic.CurrentHeaterCoolerState) .onGet(async () => { const value = this.accessory.overheatProtection.Active ? 2 : 1; return value; }) overheatProtectionControlService.getCharacteristic(Characteristic.TargetHeaterCoolerState) .setProps({ minValue: 0, maxValue: 0, validValues: [0] }) .onGet(async () => { const value = 0 return value; }) .onSet(async (value) => { try { const payload = { enabled: true, min: deviceData.OverheatProtection.Min, max: deviceData.OverheatProtection.Max }; if (this.logInfo) this.emit('info', `Set overheat protection: Enabled`);