UNPKG

homebridge-lookin-http-ac

Version:
250 lines 11.2 kB
export class AccessoryAC { platform; accessory; ip; irMap; off; debug; reqNum; reqTime; debounceTime; lowThreshold; mediumThreshold; acState; heaterCooler; contactSensor; debounceTimer = null; pendingCommand = null; constructor(platform, accessory, ip, poller, irMap, off, debug, reqNum, reqTime, debounceTime, lowThreshold, mediumThreshold) { this.platform = platform; this.accessory = accessory; this.ip = ip; this.irMap = irMap; this.off = off; this.debug = debug; this.reqNum = reqNum; this.reqTime = reqTime; this.debounceTime = debounceTime; this.lowThreshold = lowThreshold; this.mediumThreshold = mediumThreshold; if (this.debug) { this.platform.log.warn(JSON.stringify(irMap, null, 4)); } const { Service, Characteristic } = this.platform; this.acState = { activeHeaterCooler: 0, mode: this.platform.Characteristic.TargetHeaterCoolerState.COOL, heatTemperature: poller.temperature, coldTemperature: poller.temperature, fan: AccessoryAC.calcFan(false, poller.temperature, poller.temperature, lowThreshold, mediumThreshold), }; // Heater/Cooler service this.heaterCooler = accessory.getService(Service.HeaterCooler) ?? accessory.addService(Service.HeaterCooler, accessory.displayName); this.heaterCooler.updateCharacteristic(Characteristic.TargetHeaterCoolerState, this.acState.mode); this.heaterCooler.updateCharacteristic(Characteristic.CoolingThresholdTemperature, this.acState.coldTemperature); this.heaterCooler.updateCharacteristic(Characteristic.HeatingThresholdTemperature, this.acState.heatTemperature); this.heaterCooler.updateCharacteristic(Characteristic.TemperatureDisplayUnits, Characteristic.TemperatureDisplayUnits.CELSIUS); this.heaterCooler .getCharacteristic(Characteristic.TemperatureDisplayUnits) .onGet(() => Characteristic.TemperatureDisplayUnits.CELSIUS); this.heaterCooler .getCharacteristic(Characteristic.Active) .onGet(() => this.acState.activeHeaterCooler) .onSet(this.setActive.bind(this)); this.heaterCooler .getCharacteristic(Characteristic.CurrentHeaterCoolerState) .onGet(this.getCurrentHeaterCoolerState.bind(this)); this.heaterCooler .getCharacteristic(Characteristic.TargetHeaterCoolerState) .setProps({ validValues: [ Characteristic.TargetHeaterCoolerState.HEAT, Characteristic.TargetHeaterCoolerState.COOL, ], }) .onGet(() => this.acState.mode) .onSet(this.setMode.bind(this)); this.heaterCooler .getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => poller.temperature); this.heaterCooler .getCharacteristic(Characteristic.CoolingThresholdTemperature) .setProps({ minValue: 16, maxValue: 30, minStep: 1 }) .onGet(() => this.acState.coldTemperature) .onSet(this.setColdTemperature.bind(this)); this.heaterCooler .getCharacteristic(Characteristic.HeatingThresholdTemperature) .setProps({ minValue: 16, maxValue: 30, minStep: 1 }) .onGet(() => this.acState.heatTemperature) .onSet(this.setHeatTemperature.bind(this)); // Contact Sensor service this.contactSensor = accessory.getService(Service.ContactSensor) ?? accessory.addService(Service.ContactSensor, accessory.displayName + ' Contact'); this.contactSensor.updateCharacteristic(Characteristic.ContactSensorState, Characteristic.ContactSensorState.CONTACT_NOT_DETECTED); // Poller updates current temperature poller.on('update', async () => { this.heaterCooler.updateCharacteristic(Characteristic.CurrentTemperature, poller.temperature); const oldFan = this.acState.fan; this.acState.fan = AccessoryAC.calcFan(this.acState.mode === Characteristic.TargetHeaterCoolerState.HEAT, poller.temperature, this.acState.mode === Characteristic.TargetHeaterCoolerState.HEAT ? this.acState.heatTemperature : this.acState.coldTemperature, lowThreshold, mediumThreshold); if (oldFan !== this.acState.fan && this.acState.activeHeaterCooler === 1) { await this.sendAcCommand(); } }); poller.on('contactUpdate', (detected) => { const { Characteristic } = this.platform; const state = detected ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; this.contactSensor.updateCharacteristic(Characteristic.ContactSensorState, state); if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Contact Sensor -> ${detected ? 'CONTACT_DETECTED' : 'CONTACT_NOT_DETECTED'}`); } }); // Start contact sensor polling // Clean up timers this.platform.api?.on?.('shutdown', () => { if (this.debounceTimer) { clearTimeout(this.debounceTimer); } }); this.heaterCooler.setPrimaryService(true); } async getCurrentHeaterCoolerState() { const { CurrentHeaterCoolerState } = this.platform.Characteristic; if (this.acState.activeHeaterCooler === 0) { return CurrentHeaterCoolerState.IDLE; } if (this.acState.mode === 1) { return CurrentHeaterCoolerState.HEATING; } return CurrentHeaterCoolerState.COOLING; } async setActive(value) { this.acState.activeHeaterCooler = value; if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Set Active Heater -> ${value}`); } await this.sendAcCommand(); } async setMode(value) { this.acState.mode = value; if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Set Mode -> ${value === 1 ? 'HEAT' : 'COOL'}`); } await this.sendAcCommand(); } async setColdTemperature(value) { const val = value; this.acState.coldTemperature = val >= 16 ? val : 16; if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Set Cold Temperature -> ${val}`); } if (this.acState.activeHeaterCooler === 1 && this.acState.mode === this.platform.Characteristic.TargetHeaterCoolerState.COOL) { await this.sendAcCommand(); } } static calcFan(isHeat, currentTemp, targetTemp, lowThreshold, mediumThreshold) { if (isHeat) { if (currentTemp >= (targetTemp + lowThreshold)) { return 'off'; } else if (currentTemp < (targetTemp + lowThreshold) && currentTemp >= (targetTemp - lowThreshold)) { return 'low'; } else if (currentTemp < (targetTemp - lowThreshold) && currentTemp >= (targetTemp - mediumThreshold)) { return 'medium'; } else { return 'high'; } } else { if (currentTemp <= (targetTemp - lowThreshold)) { return 'off'; } else if (currentTemp > (targetTemp - lowThreshold) && currentTemp <= (targetTemp + lowThreshold)) { return 'low'; } else if (currentTemp > (targetTemp + lowThreshold) && currentTemp <= (targetTemp + mediumThreshold)) { return 'medium'; } else { return 'high'; } } } async setHeatTemperature(value) { const val = value; this.acState.heatTemperature = val >= 16 ? val : 16; if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Set Heat Temperature -> ${val}`); } if (this.acState.activeHeaterCooler === 1 && this.acState.mode === this.platform.Characteristic.TargetHeaterCoolerState.HEAT) { await this.sendAcCommand(); } } async sendAcCommand() { let code = ''; if (this.acState.activeHeaterCooler === 1 && this.acState.fan !== 'off') { const modeStr = this.acState.mode === this.platform.Characteristic.TargetHeaterCoolerState.HEAT ? 'heat' : 'cool'; const temp = this.acState.mode === this.platform.Characteristic.TargetHeaterCoolerState.HEAT ? this.acState.heatTemperature : this.acState.coldTemperature; code = this.irMap?.[this.acState.fan]?.[modeStr]?.[`t${temp}`]; if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Preparing to send command with Mode: ${modeStr}, Temp: ${temp}, Fan: ${this.acState.fan}, Code: ${code}`); } if (!code) { this.platform.log.warn(`[${this.accessory.displayName}] No IR code for ${this.acState.fan}/${modeStr}/${temp}`); return; } } else { if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Preparing to send command with Active:${this.acState.activeHeaterCooler}, Fan: ${this.acState.fan}, Code: ${code}`); } code = this.off; } this.pendingCommand = code; if (this.debounceTimer) { clearTimeout(this.debounceTimer); } this.debounceTimer = setTimeout(() => { if (this.pendingCommand) { void this.performSend(this.pendingCommand); this.pendingCommand = null; } }, this.debounceTime); } async performSend(code) { const url = `http://${this.ip}/commands/ir/prontohex/${code}`; if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] Sending IR code (x${this.reqNum}): ${url}`); } for (let i = 0; i < this.reqNum; i++) { try { const res = await fetch(url); if (!res.ok) { this.platform.log.error(`[${res.status}] Failed to send IR code (attempt ${i + 1}/${this.reqNum})`); } else if (this.debug) { this.platform.log.warn(`[${this.accessory.displayName}] IR code sent (attempt ${i + 1}/${this.reqNum})`); } } catch (err) { this.platform.log.error(`[${this.accessory.displayName}] Failed to send IR code (attempt ${i + 1}/${this.reqNum}): ${err}`); } if (i + 1 < this.reqNum) { await new Promise((resolve) => setTimeout(resolve, this.reqTime)); } } } } //# sourceMappingURL=acAccessory.js.map