UNPKG

homebridge-tasmota-control

Version:
971 lines (863 loc) 60.8 kB
import axios from 'axios'; import EventEmitter from 'events'; import ImpulseGenerator from './impulsegenerator.js'; import Functions from './functions.js'; import { ApiCommands, MiElHVAC, TemperatureDisplayUnits } from './constants.js'; let Accessory, Characteristic, Service, Categories, AccessoryUUID; class MiElHvac extends EventEmitter { constructor(api, config, info, serialNumber, deviceInfo) { super(); Accessory = api.platformAccessory; Characteristic = api.hap.Characteristic; Service = api.hap.Service; Categories = api.hap.Categories; AccessoryUUID = api.hap.uuid; //info this.info = info; this.serialNumber = serialNumber; //mitsubishi ac const miElHvac = config.miElHvac ?? {}; this.heatDryFanMode = miElHvac.heatDryFanMode || 1; //NONE, HEAT, DRY, FAN this.coolDryFanMode = miElHvac.coolDryFanMode || 1; //NONE, COOL, DRY, FAN this.autoDryFanMode = miElHvac.autoDryFanMode || 1; //NONE, COOL, DRY, FAN //external sensor const remoteTemperatureSensor = miElHvac.remoteTemperatureSensor ?? {}; const remoteTemperatureSensorEnable = remoteTemperatureSensor.enable || false; const remoteTemperatureSensorPath = remoteTemperatureSensor.path; const remoteTemperatureSensorAuth = remoteTemperatureSensor.auth || false; const remoteTemperatureSensorUser = remoteTemperatureSensor.user; const remoteTemperatureSensorPasswd = remoteTemperatureSensor.passwd; this.remoteTemperatureSensorEnable = remoteTemperatureSensorEnable; //presets this.presets = (miElHvac.presets || []).filter(preset => (preset.displayType ?? 0) > 0); for (const preset of this.presets) { preset.serviceType = [null, Service.Outlet, Service.Switch, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][preset.displayType]; preset.characteristicType = [null, Characteristic.On, Characteristic.On, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][preset.displayType]; preset.name = preset.name || 'Preset'; preset.state = false; preset.previousSettings = {}; } //buttons this.buttons = (miElHvac.buttons || []).filter(button => (button.displayType ?? 0) > 0); for (const button of this.buttons) { button.serviceType = [null, Service.Outlet, Service.Switch][button.displayType]; button.characteristicType = [null, Characteristic.On, Characteristic.On][button.displayType]; button.name = button.name || 'Button'; button.state = false; button.previousValue = null; } //sensors this.sensors = (miElHvac.sensors || []).filter(sensor => (sensor.displayType ?? 0) > 0); for (const sensor of this.sensors) { sensor.serviceType = [null, Service.MotionSensor, Service.OccupancySensor, Service.ContactSensor][sensor.displayType]; sensor.characteristicType = [null, Characteristic.MotionDetected, Characteristic.OccupancyDetected, Characteristic.ContactSensorState][sensor.displayType]; sensor.name = sensor.name || 'Sensor'; sensor.state = false; sensor.previousValue = null; } //frost protect const frostProtect = miElHvac.frostProtect ?? {}; this.frostProtectEnable = frostProtect.enable || false; this.frostProtectLowTemp = frostProtect.lowTemp || 14; this.frostProtectHighTemp = frostProtect.highTemp || 16; this.frostProtectActive = false; //extra sensors this.temperatureSensor = miElHvac.temperatureSensor || false; this.temperatureSensorOutdoor = miElHvac.temperatureSensorOutdoor || false; //other config this.sensorsNamePrefix = config.sensorsNamePrefix || false; this.logDeviceInfo = config.log?.deviceInfo || false; this.logInfo = config.log?.info || false; this.logDebug = config.log?.debug || false; //mielhvac this.mielHvac = {}; this.previousStateSwingV = 'auto'; this.previousStateSwingH = 'center'; this.functions = new Functions(); //axios instance this.client = deviceInfo.client; //axios instance remote temp if (remoteTemperatureSensorEnable) { this.axiosInstanceRemoteTemp = axios.create({ baseURL: remoteTemperatureSensorPath, timeout: 20000, withCredentials: remoteTemperatureSensorAuth, auth: { username: remoteTemperatureSensorUser, password: remoteTemperatureSensorPasswd } }); } //lock flags this.locks = { checkState: false, updateRemoteTemp: false, }; this.impulseGenerator = new ImpulseGenerator() .on('checkState', () => this.handleWithLock('checkState', async () => { await this.checkState(); })) .on('updateRemoteTemp', () => this.handleWithLock('updateRemoteTemp', async () => { await this.updateRemoteTemp(); })) .on('state', (state) => { this.emit(state ? 'success' : 'warn', `Impulse generator ${state ? 'started' : 'stopped'}`); }); } async handleWithLock(lockKey, fn) { if (this.locks[lockKey]) return; this.locks[lockKey] = true; try { await fn(); } catch (error) { this.emit('error', `Inpulse generator error: ${error}`); } finally { this.locks[lockKey] = false; } } async checkState() { if (this.logDebug) this.emit('debug', `Requesting status`); try { //power status const powerStatusData = await this.client.get(ApiCommands.PowerStatus); const powerStatus = powerStatusData.data ?? {}; if (this.logDebug) this.emit('debug', `Power status: ${JSON.stringify(powerStatus, null, 2)}`); //sensor status const sensorStatusData = await this.client.get(ApiCommands.Status); const sensorStatus = sensorStatusData.data ?? {}; if (this.logDebug) this.emit('debug', `Sensors status: ${JSON.stringify(sensorStatus, null, 2)}`); //sensor status keys const sensorStatusKeys = Object.keys(sensorStatus); //status SNS const statusSnsSupported = sensorStatusKeys.includes('StatusSNS'); const statusSns = statusSnsSupported ? sensorStatus.StatusSNS : {}; //status SNS const time = statusSns.Time ?? ''; const temperatureUnit = statusSns.TempUnit === 'C' ? '°C' : 'F'; //mielhvac const miElHvac = statusSns.MiElHVAC; if (!miElHvac || Object.keys(miElHvac).length === 0) { this.emit('warn', "Empty data received"); return null; } const power = miElHvac.PowerState === 'on' ? 1 : 0; const roomTemperature = miElHvac.RoomTemperature; if (!roomTemperature) return null; const setTemperature = miElHvac.SetTemperature; const operationMode = miElHvac.Mode ?? 'Unknown'; const fanSpeed = miElHvac.FanSpeed ?? 'Unknown'; const vaneVerticalDirection = miElHvac.SwingV ?? 'Unknown'; const vaneHorizontalDirection = miElHvac.SwingH ?? 'Unknown'; const prohibit = miElHvac.Prohibit ?? 'Unknown'; const purify = miElHvac.Purify ?? 'Unknown'; const econoCool = miElHvac.EonoCool ?? 'Unknown'; const powerFull = miElHvac.PowerFull ?? 'Unknown'; const nightMode = miElHvac.NightMode ?? 'Unknown'; const airDirection = miElHvac.AirDirection ?? 'Unknown'; const compressorStatus = miElHvac.CompressorStatus ?? 'Unknown'; const compressorFrequency = miElHvac.CompressorFrequency ?? 0; const operationPower = miElHvac.Power ?? 0; const operationEnergy = miElHvac.Energy ?? 0; const operationStage = miElHvac.OperationStage ?? 'Unknown'; const fanSpeedStage = miElHvac.FanStage ?? 'Unknown'; const operationModeStage = miElHvac.ModeStage ?? 'Unknown'; const outdoorTemperature = miElHvac.OutdoorTemperature ?? null; const swingMode = vaneVerticalDirection === 'swing' && vaneHorizontalDirection === 'swing' ? 1 : 0; const remoteTemperatureSensorState = miElHvac.RemoteTemperatureSensorState ?? false; //ON, OFF const remoteTemperatureSensorAutoClearTime = miElHvac.RemoteTemperatureSensorAutoClearTime ?? 0; //time in ms const defaultCoolingSetTemperature = parseFloat(await this.functions.readData(this.info.defaultCoolingSetTemperatureFile)); const defaultHeatingSetTemperature = parseFloat(await this.functions.readData(this.info.defaultHeatingSetTemperatureFile)); const modelSupportsHeat = true; const modelSupportsDry = true; const modelSupportsCool = true; const modelSupportsAuto = true; const modelSupportsFanSpeed = true; const hasAutomaticFanSpeed = true; const numberOfFanSpeeds = 5; const lockPhysicalControl = prohibit === 'all' ?? false; const useFahrenheit = temperatureUnit === 'F' ?? false; const temperatureIncrement = useFahrenheit ? 1 : 0.5; const hideDryModeControl = false; const hideVaneControls = false; const obj = { time: time, power: power, roomTemperature: roomTemperature, outdoorTemperature: outdoorTemperature, setTemperature: setTemperature, operationMode: operationMode, operationModeStage: operationModeStage, currentOperationMode: 0, targetOperationMode: 0, vaneVerticalDirection: vaneVerticalDirection, vaneHorizontalDirection: vaneHorizontalDirection, prohibit: prohibit, purify: purify, econoCool: econoCool, powerFull: powerFull, nightMode: nightMode, airDirection: airDirection, swingMode: swingMode, compressorStatus: compressorStatus, compressorFrequency: compressorFrequency, operationPower: operationPower, operationEnergy: operationEnergy, operationStage: operationStage, defaultCoolingSetTemperature: defaultCoolingSetTemperature, defaultHeatingSetTemperature: defaultHeatingSetTemperature, remoteTemperatureSensorState: remoteTemperatureSensorState, remoteTemperatureSensorAutoClearTime: remoteTemperatureSensorAutoClearTime, modelSupportsHeat: modelSupportsHeat, modelSupportsDry: modelSupportsDry, modelSupportsCool: modelSupportsCool, modelSupportsAuto: modelSupportsAuto, modelSupportsFanSpeed: modelSupportsFanSpeed, hasAutomaticFanSpeed: hasAutomaticFanSpeed, numberOfFanSpeeds: numberOfFanSpeeds, lockPhysicalControl: prohibit === 'all' ? 1 : 0, temperatureUnit: temperatureUnit, useFahrenheit: useFahrenheit, temperatureIncrement: temperatureIncrement, hideDryModeControl: hideDryModeControl, hideVaneControls: hideVaneControls }; // Map status to index safely, fallback to 0 if not found const operationModeStatusMap = { 'manual': 0, 'heat': 2, 'dry': 1, 'cool': 3, 'fan': 1, 'heat_isee': 2, 'dry_isee': 1, 'cool_isee': 3, 'auto_fan': 1, 'auto_heat': 2, 'auto_cool': 3, 'auto_leader': 4 }; const statusIndex = operationModeStatusMap[operationModeStage] ?? 0; switch (operationMode) { case 'heat': obj.currentOperationMode = [2, 1, 2, 3, 0][statusIndex]; // INACTIVE, IDLE, HEATING, COOLING obj.targetOperationMode = 1; // AUTO, HEAT, COOL break; case 'dry': obj.currentOperationMode = [1, 1, 2, 3, 0][statusIndex]; obj.targetOperationMode = this.autoDryFanMode === 2 ? 0 : this.heatDryFanMode === 2 ? 1 : this.coolDryFanMode === 2 ? 2 : obj.targetOperationMode; break; case 'cool': obj.currentOperationMode = [3, 1, 2, 3, 0][statusIndex]; obj.targetOperationMode = 2; break; case 'fan': obj.currentOperationMode = [1, 1, 2, 3, 0][statusIndex]; obj.targetOperationMode = this.autoDryFanMode === 3 ? 0 : this.heatDryFanMode === 3 ? 1 : this.coolDryFanMode === 3 ? 2 : obj.targetOperationMode; break; case 'auto': obj.currentOperationMode = [2, 1, 2, 3, 0][statusIndex]; obj.targetOperationMode = 0; break; case 'heat_isee': obj.currentOperationMode = [2, 1, 2, 3, 0][statusIndex]; obj.targetOperationMode = 1; break; case 'dry_isee': obj.currentOperationMode = [1, 1, 2, 3, 0][statusIndex]; obj.targetOperationMode = this.autoDryFanMode === 2 ? 0 : this.heatDryFanMode === 2 ? 1 : this.coolDryFanMode === 2 ? 2 : obj.targetOperationMode; break; case 'cool_isee': obj.currentOperationMode = [3, 1, 2, 3, 0][statusIndex]; obj.targetOperationMode = 2; break; default: this.emit('warn', `Unknown operating mode: ${operationMode}`); return null; } // If power is off, force currentOperationMode to 0 (inactive) obj.currentOperationMode = !power ? 0 : obj.currentOperationMode; // Set min/max/valid values for operation mode controls obj.operationModeSetPropsMinValue = modelSupportsAuto && modelSupportsHeat ? 0 : !modelSupportsAuto && modelSupportsHeat ? 1 : modelSupportsAuto && !modelSupportsHeat ? 0 : 2; obj.operationModeSetPropsMaxValue = 2; obj.operationModeSetPropsValidValues = modelSupportsAuto && modelSupportsHeat ? [0, 1, 2] : !modelSupportsAuto && modelSupportsHeat ? [1, 2] : modelSupportsAuto && !modelSupportsHeat ? [0, 2] : [2]; if (modelSupportsFanSpeed) { const fanSpeedMap = { 'auto': 0, 'quiet': 1, '1': 2, '2': 3, '3': 4, '4': 5 }; const fanIndex = fanSpeedMap[fanSpeed]; obj.fanSpeed = 0; obj.fanSpeedSetPropsMaxValue = 0; if (typeof fanIndex === 'number') { switch (numberOfFanSpeeds) { case 2: obj.fanSpeed = hasAutomaticFanSpeed ? [3, 1, 2][fanIndex] ?? 1 : [0, 1, 2][fanIndex] ?? 1; obj.fanSpeedSetPropsMaxValue = hasAutomaticFanSpeed ? 3 : 2; break; case 3: obj.fanSpeed = hasAutomaticFanSpeed ? [4, 1, 2, 3][fanIndex] ?? 1 : [0, 1, 2, 3][fanIndex] ?? 1; obj.fanSpeedSetPropsMaxValue = hasAutomaticFanSpeed ? 4 : 3; break; case 4: obj.fanSpeed = hasAutomaticFanSpeed ? [5, 1, 2, 3, 4][fanIndex] ?? 1 : [0, 1, 2, 3, 4][fanIndex] ?? 1; obj.fanSpeedSetPropsMaxValue = hasAutomaticFanSpeed ? 5 : 4; break; case 5: obj.fanSpeed = hasAutomaticFanSpeed ? [6, 1, 2, 3, 4, 5][fanIndex] ?? 1 : [0, 1, 2, 3, 4, 5][fanIndex] ?? 1; obj.fanSpeedSetPropsMaxValue = hasAutomaticFanSpeed ? 6 : 5; break; default: this.emit('warn', `Unknown fan speeds: ${numberOfFanSpeeds}`); } // Cap value to max if (obj.fanSpeed > obj.fanSpeedSetPropsMaxValue) { obj.fanSpeed = obj.fanSpeedSetPropsMaxValue; } } } this.mielHvac = obj; //update characteristics if (this.miElHvacService) { const svc = this.miElHvacService; svc.updateCharacteristic(Characteristic.Active, power) .updateCharacteristic(Characteristic.CurrentHeaterCoolerState, obj.currentOperationMode) .updateCharacteristic(Characteristic.TargetHeaterCoolerState, obj.targetOperationMode) .updateCharacteristic(Characteristic.CurrentTemperature, roomTemperature) .updateCharacteristic(Characteristic.LockPhysicalControls, lockPhysicalControl) .updateCharacteristic(Characteristic.TemperatureDisplayUnits, useFahrenheit) .updateCharacteristic(Characteristic.SwingMode, swingMode); if (obj.targetOperationMode === 0 || obj.targetOperationMode === 2) svc.updateCharacteristic(Characteristic.CoolingThresholdTemperature, obj.targetOperationMode === 0 ? defaultCoolingSetTemperature : setTemperature); if (obj.targetOperationMode === 0 || obj.targetOperationMode === 1) svc.updateCharacteristic(Characteristic.HeatingThresholdTemperature, obj.targetOperationMode === 0 ? defaultHeatingSetTemperature : setTemperature); if (modelSupportsFanSpeed) svc.updateCharacteristic(Characteristic.RotationSpeed, obj.fanSpeed); if (this.frostProtectEnable) { if (roomTemperature <= this.frostProtectLowTemp && !power) { svc.setCharacteristic(Characteristic.Active, true) .setCharacteristic(Characteristic.TargetHeaterCoolerState, 1) .setCharacteristic(Characteristic.HeatingThresholdTemperature, this.frostProtectHighTemp); this.frostProtectActive = true; } if (roomTemperature >= this.frostProtectHighTemp && this.frostProtectActive) { svc.setCharacteristic(Characteristic.Active, false); this.frostProtectActive = false; } } } // Update presets state if (this.presets.length > 0) { this.presets.forEach((preset, index) => { let iseeMode = operationMode; if (iseeMode === 'heat_isee') iseeMode = 'heat'; else if (iseeMode === 'dry_isee') iseeMode = 'dry'; else if (iseeMode === 'cool_isee') iseeMode = 'cool'; const sameTemp = Number(preset.setTemp).toFixed(1) === Number(setTemperature).toFixed(1); const sameFan = preset.fanSpeed === fanSpeed; const sameSwingV = preset.swingV === vaneVerticalDirection; const sameSwingH = preset.swingH === vaneHorizontalDirection; const sameMode = preset.mode === iseeMode; preset.state = power ? (sameMode && sameTemp && sameFan && sameSwingV && sameSwingH) : false; this.presetsServices?.[index]?.updateCharacteristic(preset.characteristicType, preset.state); }); } if (this.buttons.length > 0) { const modeMap = { 0: () => power === 1, 1: () => power && ['heat', 'heat_isee'].includes(operationMode), 2: () => power && ['dry', 'dry_isee'].includes(operationMode), 3: () => power && ['cool', 'cool_isee'].includes(operationMode), 4: () => power && operationMode === 'fan', 5: () => power && operationMode === 'auto', }; const vaneHMap = { 10: 'left', 11: 'left_middle', 12: 'left_center', 13: 'center', 14: 'right_center', 15: 'right_middle', 16: 'right', 17: 'split', 18: 'swing', 19: 'airdirection', }; const vaneVMap = { 20: 'auto', 21: 'up', 22: 'up_middle', 23: 'center', 24: 'down_middle', 25: 'down', 26: 'swing', }; const fanSpeedMap = { 30: 'auto', 31: 'quiet', 32: '1', 33: '2', 34: '3', 35: '4', }; const airDirMap = { 40: 'indirect', 41: 'direct', 42: 'even', }; const prohibitMap = { 50: 'all', 51: 'power', 52: 'mode', 53: 'temp', }; const functionsMap = { 60: 'purify', //purify 61: 'econoCool', //econocool 62: 'powerFull', //powerfull 63: 'noghtMode', //nightmode }; const functionsStateMap = { 60: 'on', //purify 61: 'on', //econocool 62: 'on', //powerfull 63: 'on', //nightmode }; this.buttons.forEach((button, index) => { const mode = button.mode; let state = false; if (modeMap[mode]) { state = modeMap[mode](); } else if (vaneHMap[mode]) { state = power && vaneHorizontalDirection === vaneHMap[mode]; } else if (vaneVMap[mode]) { state = power && vaneVerticalDirection === vaneVMap[mode]; } else if (fanSpeedMap[mode]) { state = power && fanSpeed === fanSpeedMap[mode]; } else if (airDirMap[mode]) { state = power && airDirection === airDirMap[mode]; } else if (prohibitMap[mode]) { state = power && prohibit === prohibitMap[mode]; } else if (functionsStateMap[mode]) { state = power && functionsMap[mode] === functionsStateMap[mode]; } else { this.emit('warn', `Unknown button mode: ${mode} detected`); } button.state = state; const characteristicType = button.characteristicType; this.buttonsServices?.[index]?.updateCharacteristic(characteristicType, state); }); } if (this.sensors.length > 0) { const powerOn = power === 1; // Helper: match by value with power check const is = (val, match) => powerOn && val === match; const isOneOf = (val, matches) => powerOn && matches.includes(val); this.sensors.forEach((sensor, index) => { const mode = sensor.mode; const sensorStates = { 0: powerOn, 1: isOneOf(operationMode, ['heat', 'heat_isee']), 2: isOneOf(operationMode, ['dry', 'dry_isee']), 3: isOneOf(operationMode, ['cool', 'cool_isee']), 4: is(operationMode, 'fan'), 5: is(operationMode, 'auto'), 6: is(purify, 'on'), 10: is(vaneHorizontalDirection, 'left'), 11: is(vaneHorizontalDirection, 'left_middle'), 12: is(vaneHorizontalDirection, 'left_center'), 13: is(vaneHorizontalDirection, 'center'), 14: is(vaneHorizontalDirection, 'right_center'), 15: is(vaneHorizontalDirection, 'right_middle'), 16: is(vaneHorizontalDirection, 'right'), 17: is(vaneHorizontalDirection, 'split'), 18: is(vaneHorizontalDirection, 'swing'), 19: is(vaneHorizontalDirection, 'airdirection'), 20: is(vaneVerticalDirection, 'auto'), 21: is(vaneVerticalDirection, 'up'), 22: is(vaneVerticalDirection, 'up_middle'), 23: is(vaneVerticalDirection, 'center'), 24: is(vaneVerticalDirection, 'down_middle'), 25: is(vaneVerticalDirection, 'down'), 26: is(vaneVerticalDirection, 'swing'), 27: is(hideVaneControls, true), 30: is(fanSpeed, 'auto'), 31: is(fanSpeed, 'quiet'), 32: is(fanSpeed, '1'), 33: is(fanSpeed, '2'), 34: is(fanSpeed, '3'), 35: is(fanSpeed, '4'), 40: is(airDirection, 'indirect'), 41: is(airDirection, 'direct'), 42: is(airDirection, 'even'), 50: prohibit === 'all', 51: prohibit === 'power', 52: prohibit === 'mode', 53: prohibit === 'temp', 60: remoteTemperatureSensorState, 70: operationStage === 'normal', 71: operationStage === 'filter', 72: operationStage === 'defrost', 73: operationStage === 'standby', 74: operationStage === 'preheat', 80: fanSpeedStage === 'off', 81: fanSpeedStage === 'quiet', 82: fanSpeedStage === '1', 83: fanSpeedStage === '2', 84: fanSpeedStage === '3', 85: fanSpeedStage === '4', 86: fanSpeedStage === '5', 90: operationMode !== 'auto', 91: operationModeStage === 'auto_fan', 92: operationModeStage === 'auto_heat', 93: operationModeStage === 'auto_cool', 94: operationModeStage === 'auto_leader', }; if (mode in sensorStates) { sensor.state = sensorStates[mode]; } else { this.emit('warn', `Unknown sensor mode: ${mode} detected`); } // Update characteristic{ const characteristicType = sensor.characteristicType; this.sensorsServices?.[index]?.updateCharacteristic(characteristicType, sensor.state); }); } //update room temperature sensor this.roomTemperatureSensorService?.updateCharacteristic(Characteristic.CurrentTemperature, roomTemperature); //update outdoor temperature sensor this.outdoorTemperatureSensorService?.updateCharacteristic(Characteristic.CurrentTemperature, outdoorTemperature); //log current state if (this.logInfo) { this.emit('info', `Power: ${power ? 'ON' : 'OFF'}`); const info = power ? this.emit('info', `Target operation mode: ${operationMode.toUpperCase()}`) : false; const info1 = power ? this.emit('info', `Current operation mode: ${operationModeStage.toUpperCase()}`) : false; const info2 = power ? this.emit('info', `Target temperature: ${setTemperature}${temperatureUnit}`) : false; const info3 = power ? this.emit('info', `Current temperature: ${roomTemperature}${temperatureUnit}`) : false; const info4 = power && outdoorTemperature !== null ? this.emit('info', `Outdoor temperature: ${outdoorTemperature}${temperatureUnit}`) : false; const info5 = power && modelSupportsFanSpeed ? this.emit('info', `Target Fan speed: ${fanSpeed.toUpperCase()}`) : false; const info6 = power && modelSupportsFanSpeed ? this.emit('info', `Current Fan speed: ${fanSpeedStage.toUpperCase()}`) : false; const info7 = power && vaneHorizontalDirection !== 'Unknown' ? this.emit('info', `Vane horizontal: ${MiElHVAC.HorizontalVane[vaneHorizontalDirection] ?? vaneHorizontalDirection}`) : false; const info8 = power && vaneVerticalDirection !== 'Unknown' ? this.emit('info', `Vane vertical: ${MiElHVAC.VerticalVane[vaneVerticalDirection] ?? vaneVerticalDirection}`) : false; const info9 = power ? this.emit('info', `Swing mode: ${MiElHVAC.SwingMode[swingMode]}`) : false; const info10 = power && vaneHorizontalDirection === 'isee' && airDirection !== 'Unknown' ? this.emit('info', `Air direction: ${MiElHVAC.AirDirection[airDirection]}`) : false; const info11 = power ? this.emit('info', `Prohibit: ${MiElHVAC.Prohibit[prohibit]}`) : false; const info12 = power ? this.emit('info', `Temperature display unit: ${temperatureUnit}`) : false; const info13 = power ? this.emit('info', `Compressor: ${compressorStatus.toUpperCase()}`) : false; const info14 = power ? this.emit('info', `OperationPower: ${operationPower}W`) : false; const info15 = power ? this.emit('info', `OperationEnergy: ${operationEnergy}kWh`) : false; } return true; } catch (error) { throw new Error(`Check state error: ${error}`); } } async updateRemoteTemp() { try { //get remote temp const rmoteTempData = await this.axiosInstanceRemoteTemp.get(); const remoteTemp = rmoteTempData.data ?? false; if (this.logDebug) this.emit('debug', `Remote temp: ${JSON.stringify(remoteTemp, null, 2)}`); //set remote temp const temp = `${MiElHVAC.SetRemoteTemp}${remoteTemp}` await this.client.get(temp); return true } catch (error) { throw new Error(`Update remote temperature error: ${error}`); } } async deviceInfo() { this.emit('devInfo', `----- ${this.info.deviceName} -----`); this.emit('devInfo', `Manufacturer: Tasmota`); this.emit('devInfo', `Hardware: ${this.info.modelName}`); this.emit('devInfo', `Serialnr: ${this.serialNumber}`) this.emit('devInfo', `Firmware: ${this.info.firmwareRevision}`); this.emit('devInfo', `Device: MiELHVAC`); this.emit('devInfo', `----------------------------------`); return; } //prepare accessory async prepareAccessory() { if (this.logDebug) this.emit('debug', `Prepare Accessory`); try { //accessory const accessoryName = this.info.deviceName; const accessoryUUID = AccessoryUUID.generate(this.serialNumber); const accessoryCategory = Categories.AIR_CONDITIONER const accessory = new Accessory(accessoryName, accessoryUUID, accessoryCategory); //Prepare information service if (this.logDebug) this.emit('debug', `Prepare Information Service`); accessory.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Manufacturer, 'Tasmota') .setCharacteristic(Characteristic.Model, this.info.modelName ?? 'Model Name') .setCharacteristic(Characteristic.SerialNumber, this.serialNumber ?? 'Serial Number') .setCharacteristic(Characteristic.FirmwareRevision, this.info.firmwareRevision.replace(/[a-zA-Z]/g, '') ?? '0') .setCharacteristic(Characteristic.ConfiguredName, accessoryName); //Prepare services if (this.logDebug) this.emit('debug', `Prepare Services`); if (this.logDebug) this.emit('debug', `Prepare mitsubishi hvac service`); const autoDryFanMode = [MiElHVAC.SetMode.auto, MiElHVAC.SetMode.auto, MiElHVAC.SetMode.dry, MiElHVAC.SetMode.fan][this.autoDryFanMode]; //NONE, AUTO, DRY, FAN const heatDryFanMode = [MiElHVAC.SetMode.heat, MiElHVAC.SetMode.heat, MiElHVAC.SetMode.dry, MiElHVAC.SetMode.fan][this.heatDryFanMode]; //NONE, HEAT, DRY, FAN const coolDryFanMode = [MiElHVAC.SetMode.cool, MiElHVAC.SetMode.cool, MiElHVAC.SetMode.dry, MiElHVAC.SetMode.fan][this.coolDryFanMode]; //NONE, COOL, DRY, FAN //services this.miElHvacService = new Service.HeaterCooler(accessoryName, `HeaterCooler ${this.serialNumber}`); this.miElHvacService.setPrimaryService(true); this.miElHvacService.addOptionalCharacteristic(Characteristic.ConfiguredName); this.miElHvacService.setCharacteristic(Characteristic.ConfiguredName, accessoryName); this.miElHvacService.getCharacteristic(Characteristic.Active) .onGet(async () => { const state = this.mielHvac.power; return state; }) .onSet(async (state) => { try { const power = [MiElHVAC.PowerOff, MiElHVAC.PowerOn][state]; await this.client.get(power); if (this.logInfo) this.emit('info', `Set power: ${state ? 'ON' : 'OFF'}`); } catch (error) { this.emit('warn', `Set power error: ${error}`); } }); this.miElHvacService.getCharacteristic(Characteristic.CurrentHeaterCoolerState) .onGet(async () => { const value = this.mielHvac.currentOperationMode; return value; }); this.miElHvacService.getCharacteristic(Characteristic.TargetHeaterCoolerState) .setProps({ minValue: this.mielHvac.operationModeSetPropsMinValue, maxValue: this.mielHvac.operationModeSetPropsMaxValue, validValues: this.mielHvac.operationModeSetPropsValidValues }) .onGet(async () => { const value = this.mielHvac.targetOperationMode; //1 = HEAT, 2 = DRY 3 = COOL, 7 = FAN, 8 = AUTO return value; }) .onSet(async (value) => { try { switch (value) { case 0: //AUTO await this.client.get(autoDryFanMode); break; case 1: //HEAT await this.client.get(heatDryFanMode); break; case 2: //COOL await this.client.get(coolDryFanMode); break; }; if (this.logInfo) this.emit('info', `Set operation mode: ${MiElHVAC.OperationMode[value]}`); } catch (error) { this.emit('warn', `Set operation mode error: ${error}`); } }); this.miElHvacService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.mielHvac.roomTemperature; return value; }); if (this.mielHvac.modelSupportsFanSpeed) { this.miElHvacService.getCharacteristic(Characteristic.RotationSpeed) .setProps({ minValue: 0, maxValue: this.mielHvac.fanSpeedSetPropsMaxValue, minStep: 1 }) .onGet(async () => { const value = this.mielHvac.fanSpeed; //AUTO, 1, 2, 3, 4, 5 return value; }) .onSet(async (value) => { try { let fanSpeed = 0; let fanSpeedModeText = 'off'; switch (this.mielHvac.numberOfFanSpeeds) { case 2: //Fan speed mode 2 fanSpeed = this.mielHvac.hasAutomaticFanSpeed ? [0, 1, 2, 0][value] : [1, 1, 2][value]; fanSpeedModeText = this.mielHvac.hasAutomaticFanSpeed ? ['off', 'quiet', '1', 'auto'][value] : ['off', 'quiet', '1'][value]; break; case 3: //Fan speed mode 3 fanSpeed = this.mielHvac.hasAutomaticFanSpeed ? [0, 1, 2, 3, 0][value] : [1, 1, 2, 3][value]; fanSpeedModeText = this.mielHvac.hasAutomaticFanSpeed ? ['off', 'quiet', '1', '2', 'auto'][value] : ['off', 'quiet', '1', '2',][value]; break; case 4: //Fan speed mode 4 fanSpeed = this.mielHvac.hasAutomaticFanSpeed ? [0, 1, 2, 3, 4, 0][value] : [1, 1, 2, 3, 4][value]; fanSpeedModeText = this.mielHvac.hasAutomaticFanSpeed ? ['off', 'quiet', '1', '2', '3', 'auto'][value] : ['off', 'quiet', '1', '2', '3'][value]; break; case 5: //Fan speed mode 5 fanSpeed = this.mielHvac.hasAutomaticFanSpeed ? [0, 1, 2, 3, 4, 5, 0][value] : [1, 1, 2, 3, 4, 5][value]; fanSpeedModeText = this.mielHvac.hasAutomaticFanSpeed ? ['off', 'quiet', '1', '2', '3', '4', 'auto'][value] : ['off', 'quiet', '1', '2', '3', '4'][value]; break; } //fan speed mode const fanSpeedMap = ['auto', 'quiet', '1', '2', '3', '4'][fanSpeed]; await this.client.get(MiElHVAC.SetFanSpeed[fanSpeedMap]); if (this.logInfo) this.emit('info', `Set fan speed mode: ${MiElHVAC.FanSpeed[fanSpeedModeText]}`); } catch (error) { this.emit('warn', `Set fan speed mode error: ${error}`); } }); } if (this.mielHvac.swingMode) { this.miElHvacService.getCharacteristic(Characteristic.SwingMode) .onGet(async () => { const value = this.mielHvac.swingMode; return value; }) .onSet(async (value) => { try { switch (value) { case 0: await this.client.get(MiElHVAC.SetSwingV[this.previousStateSwingV]); await this.client.get(MiElHVAC.SetSwingH[this.previousStateSwingH]); break; case 1: //set vane v this.previousStateSwingV = this.mielHvac.vaneVerticalDirection; await this.client.get(MiElHVAC.SetSwingV.swing); //set vane h this.previousStateSwingH = this.mielHvac.vaneHorizontalDirection; await this.client.get(MiElHVAC.SetSwingH.swing); break; } if (this.logInfo) this.emit('info', `Set air direction mode: ${MiElHVAC.SwingMode[value]}`); } catch (error) { this.emit('warn', `Set vane swing mode error: ${error}`); } }); } this.miElHvacService.getCharacteristic(Characteristic.CoolingThresholdTemperature) .setProps({ minValue: 16, maxValue: 31, minStep: this.mielHvac.temperatureIncrement }) .onGet(async () => { const value = this.mielHvac.targetOperationMode === 2 ? this.mielHvac.setTemperature : this.mielHvac.defaultCoolingSetTemperature; return value; }) .onSet(async (value) => { try { if (this.mielHvac.targetOperationMode === 0) { await this.functions.saveData(this.info.defaultCoolingSetTemperatureFile, value); value = (value + this.info.mielHvac.defaultHeatingSetTemperature) / 2; } const temp = `${MiElHVAC.SetTemp}${value}` await this.client.get(temp); if (this.logInfo) this.emit('info', `Set ${this.mielHvac.targetOperationMode === 2 ? 'temperature' : 'cooling threshold temperature'}: ${value}${this.mielHvac.temperatureUnit}`); } catch (error) { this.emit('warn', `Set cooling threshold temperature error: ${error}`); } }); if (this.mielHvac.modelSupportsHeat) { this.miElHvacService.getCharacteristic(Characteristic.HeatingThresholdTemperature) .setProps({ minValue: 10, maxValue: 31, minStep: this.mielHvac.temperatureIncrement }) .onGet(async () => { const value = this.mielHvac.targetOperationMode === 1 ? this.mielHvac.setTemperature : this.mielHvac.defaultHeatingSetTemperature; return value; }) .onSet(async (value) => { try { if (this.mielHvac.targetOperationMode === 0) { await this.functions.saveData(this.info.defaultHeatingSetTemperatureFile, value); value = (value + this.info.mielHvac.defaultCoolingSetTemperature) / 2; } const temp = `${MiElHVAC.SetTemp}${value}` await this.client.get(temp); if (this.logInfo) this.emit('info', `Set ${this.mielHvac.targetOperationMode === 1 ? 'temperature' : 'heating threshold temperature'}: ${value}${this.mielHvac.temperatureUnit}`); } catch (error) { this.emit('warn', `Set heating threshold temperature error: ${error}`); } }); } this.miElHvacService.getCharacteristic(Characteristic.LockPhysicalControls) .onGet(async () => { const value = this.mielHvac.lockPhysicalControl; return value; }) .onSet(async (value) => { try { const lock = [MiElHVAC.SetProhibit.off, MiElHVAC.SetProhibit.all][value]; await this.client.get(lock); if (this.logInfo) this.emit('info', `Set local physical controls: ${value ? 'LOCK' : 'UNLOCK'}`); } catch (error) { this.emit('warn', `Set lock physical controls error: ${error}`); } }); this.miElHvacService.getCharacteristic(Characteristic.TemperatureDisplayUnits) .onGet(async () => { const value = this.mielHvac.useFahrenheit; return value; }) .onSet(async (value) => { try { const unit = [MiElHVAC.SetDisplayUnit.c, MiElHVAC.SetDisplayUnit.f][value]; //await this.client.get(unit); if (this.logInfo) this.emit('info', `Set temperature display unit: ${TemperatureDisplayUnits[value]}`); } catch (error) { this.emit('warn', `Set temperature display unit error: ${error}`); } }); accessory.addService(this.miElHvacService); //presets services if (this.presets.length > 0) { if (this.logDebug) this.emit('debug', 'Prepare presets services'); this.presetsServices = []; this.presets.forEach((preset, index) => { const { name: presetName, namePrefix, serviceType, characteristicType, mode, setTemp, fanSpeed, swingV, swingH } = preset; const serviceName = namePrefix ? `${accessoryName} ${presetName}` : presetName; const presetService = new serviceType(serviceName, `Preset ${index}`); presetService.addOptionalCharacteristic(Characteristic.ConfiguredName); presetService.setCharacteristic(Characteristic.ConfiguredName, serviceName); presetService.getCharacteristic(characteristicType) .onGet(async () => preset.state) .onSet(async (state) => { try { if (state) { // Power on if needed if (!this.mielHvac.power) { await this.client.get(MiElHVAC.PowerOn); } // Apply preset commands in sequence const commands = [ MiElHVAC.SetMode[mode], `${MiElHVAC.SetTemp}${setTemp}`, MiElHVAC.SetFanSpeed[fanSpeed], MiElHVAC.SetSwingV[swingV], MiElHVAC.SetSwingH[swingH] ]; for (const cmd of commands) { await this.client.get(cmd); } if (this.logInfo) { this.emit('info', `Set: ${presetName}`); } await new Promise(resolve => setTimeout(resolve, 250)); } } catch (error) { this.emit('warn', `Set preset error: ${error}`); } }); this.presetsServices.push(presetService); accessory.addService(presetService); }); } if (this.buttons.length > 0) { if (this.logDebug) this.emit('debug', 'Prepare buttons services'); this.buttonsServices = []; this.buttons.forEach((button, index) => { const { mode, name: buttonName, namePrefix, serviceType, characteristicType } = button; const serviceName = namePrefix ? `${accessoryName} ${buttonName}` : buttonName; const buttonService = new serviceType(serviceName, `Button ${index}`); buttonService.addOptionalCharacteristic(Characteristic.ConfiguredName); buttonService.setCharacteristic(Characteristic.ConfiguredName, serviceName); buttonService.getCharacteristic(characteristicType) .onGet(async () => button.state) .onSet(async (state) => { try { let data; const mappings = {