homebridge-lookin-http-ac
Version:
250 lines • 11.2 kB
JavaScript
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