UNPKG

homebridge-pentair-intellicenter-ai

Version:

Homebridge plugin for the Pentair IntelliCenter pool control system (maintained with AI assistance)

415 lines 21.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HeaterAccessory = void 0; const types_1 = require("./types"); const util_1 = require("./util"); const settings_1 = require("./settings"); const constants_1 = require("./constants"); const uuid_1 = require("uuid"); /** * Platform Accessory * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ class HeaterAccessory { constructor(platform, accessory) { var _a; this.platform = platform; this.accessory = accessory; this.heater = this.accessory.context.heater; this.body = this.accessory.context.body; this.platform.log.debug(`Setting accessory details for device: ${JSON.stringify(this.heater, null, 2)}`); this.isFahrenheit = this.platform.getConfig().temperatureUnits !== types_1.TemperatureUnits.C; this.minValue = this.platform.getConfig().minimumTemperature; this.maxValue = this.platform.getConfig().maximumTemperature; this.lowTemperature = this.body.lowTemperature; this.highTemperature = this.body.highTemperature; if (this.isFahrenheit) { this.minValue = (0, util_1.fahrenheitToCelsius)(this.minValue); this.maxValue = (0, util_1.fahrenheitToCelsius)(this.maxValue); if (this.lowTemperature) { this.lowTemperature = (0, util_1.fahrenheitToCelsius)(this.lowTemperature); } if (this.highTemperature) { this.highTemperature = (0, util_1.fahrenheitToCelsius)(this.highTemperature); } } if ((_a = this.body) === null || _a === void 0 ? void 0 : _a.temperature) { this.temperature = this.isFahrenheit && this.body.temperature ? (0, util_1.fahrenheitToCelsius)(this.body.temperature) : this.body.temperature; } else { this.temperature = undefined; } this.platform.log.debug(`Temperature Slider Min: ${this.minValue}, Max: ${this.maxValue}, ` + `current temperature: ${this.temperature}`); this.accessory .getService(this.platform.Service.AccessoryInformation) .setCharacteristic(this.platform.Characteristic.Manufacturer, settings_1.MANUFACTURER) .setCharacteristic(this.platform.Characteristic.Model, this.heater.type) .setCharacteristic(this.platform.Characteristic.SerialNumber, `${this.body.id}.${this.heater.id}`); this.service = this.accessory.getService(this.platform.Service.Thermostat) || this.accessory.addService(this.platform.Service.Thermostat); this.service.setCharacteristic(this.platform.Characteristic.Name, `${this.body.name} ${this.heater.name}`); this.bindStaticValues(); this.bindThermostat(); } bindThermostat() { if (this.lowTemperature) { this.service .getCharacteristic(this.platform.Characteristic.TargetTemperature) .onSet(this.setTargetTemperature.bind(this)) .onGet(this.getTargetTemperature.bind(this)) .setProps({ minValue: this.minValue, maxValue: this.maxValue, minStep: constants_1.THERMOSTAT_STEP_VALUE, }) .updateValue(this.lowTemperature || 0); } // Add cooling setpoint support for heat pumps if (this.heater.coolingEnabled && this.highTemperature) { this.service .getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature) .onSet(this.setCoolingThresholdTemperature.bind(this)) .onGet(this.getCoolingThresholdTemperature.bind(this)) .setProps({ minValue: this.minValue, maxValue: this.maxValue, minStep: constants_1.THERMOSTAT_STEP_VALUE, }) .updateValue(this.highTemperature || 0); this.service .getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature) .onSet(this.setHeatingThresholdTemperature.bind(this)) .onGet(this.getHeatingThresholdTemperature.bind(this)) .setProps({ minValue: this.minValue, maxValue: this.maxValue, minStep: constants_1.THERMOSTAT_STEP_VALUE, }) .updateValue(this.lowTemperature || 0); } if (this.temperature) { this.service .getCharacteristic(this.platform.Characteristic.CurrentTemperature) .onGet(this.getCurrentTemperature.bind(this)) .updateValue(this.temperature) .setProps({ minValue: constants_1.CURRENT_TEMP_MIN_C, maxValue: constants_1.CURRENT_TEMP_MAX_C, minStep: constants_1.THERMOSTAT_STEP_VALUE, }); } // Determine valid values based on cooling capability const validValues = [this.platform.Characteristic.TargetHeatingCoolingState.OFF]; let maxValue = this.platform.Characteristic.TargetHeatingCoolingState.HEAT; if (this.heater.coolingEnabled) { // For devices with both heating and cooling, only show OFF and AUTO validValues.push(this.platform.Characteristic.TargetHeatingCoolingState.AUTO); maxValue = this.platform.Characteristic.TargetHeatingCoolingState.AUTO; } else { // For heating-only devices, show OFF and HEAT validValues.push(this.platform.Characteristic.TargetHeatingCoolingState.HEAT); } this.service .getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState) .onGet(this.getMode.bind(this)) .onSet(this.setMode.bind(this)) .updateValue(this.getMode()) .setProps({ minValue: this.platform.Characteristic.TargetHeatingCoolingState.OFF, maxValue: maxValue, validValues: validValues, }); } getMode() { // If heater is not selected for this body, it's OFF if (this.body.heaterId !== this.heater.id) { return this.platform.Characteristic.TargetHeatingCoolingState.OFF; } // If heater is selected but no cooling capability, it's HEAT if (!this.heater.coolingEnabled) { return this.platform.Characteristic.TargetHeatingCoolingState.HEAT; } // For cooling-enabled heaters, if heater is selected it's AUTO return this.platform.Characteristic.TargetHeatingCoolingState.AUTO; } async setMode(value) { this.platform.log.info(`Set heat power to ${value} for heater ${this.heater.name}`); let heater = this.heater.id; let mode = types_1.HeatMode.On; if (value === this.platform.Characteristic.TargetHeatingCoolingState.OFF) { heater = constants_1.NO_HEATER_ID; mode = types_1.HeatMode.Off; } else if (value === this.platform.Characteristic.TargetHeatingCoolingState.AUTO || value === this.platform.Characteristic.TargetHeatingCoolingState.HEAT || value === this.platform.Characteristic.TargetHeatingCoolingState.COOL) { // For AUTO, HEAT, or COOL modes, select this heater heater = this.heater.id; mode = types_1.HeatMode.On; } if (mode === types_1.HeatMode.On) { // Turn on the pump. const command = { command: types_1.IntelliCenterRequestCommand.SetParamList, messageID: (0, uuid_1.v4)(), objectList: [ { objnam: this.body.id, params: { [constants_1.STATUS_KEY]: types_1.CircuitStatus.On }, }, ], }; this.platform.sendCommandNoWait(command); } const command = { command: types_1.IntelliCenterRequestCommand.SetParamList, messageID: (0, uuid_1.v4)(), objectList: [ { objnam: this.body.id, params: { [constants_1.HEATER_KEY]: heater }, }, ], }; this.platform.sendCommandNoWait(command); } bindStaticValues() { this.service.updateCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits, this.platform.getConfig().temperatureUnits === types_1.TemperatureUnits.F ? this.platform.Characteristic.TemperatureDisplayUnits.FAHRENHEIT : this.platform.Characteristic.TemperatureDisplayUnits.CELSIUS); const initialState = this.getCurrentHeatingCoolingState(); this.platform.log.debug(`[${this.heater.name}] bindStaticValues: Setting initial CurrentHeatingCoolingState to ${initialState}`); this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, initialState); } getCurrentHeatingCoolingState() { const heatSource = this.body.heatSource; const heatMode = this.body.heatMode; this.platform.log.debug(`[${this.heater.name}] getCurrentHeatingCoolingState: HTSRC: ${heatSource}, HTMODE: ${heatMode}, ` + `heater.id: ${this.heater.id}, temp: ${this.temperature}°C, low: ${this.lowTemperature}°C, high: ${this.highTemperature}°C`); // Check HTSRC + HTMODE logic first if available const htSrcState = this.checkHeatSourceState(heatSource, heatMode); if (htSrcState !== null) { return htSrcState; } // Fallback to temperature comparison logic return this.checkTemperatureState(); } checkHeatModeState() { if (this.body.heatMode !== undefined && this.body.heatMode !== null) { const heatModeNum = typeof this.body.heatMode === 'string' ? Number(this.body.heatMode) : this.body.heatMode; if (!isNaN(heatModeNum)) { this.platform.log.debug(`[${this.heater.name}] checkTemperatureState: Using available HTMODE=${heatModeNum} for heating detection`); if (heatModeNum === 9) { return this.platform.Characteristic.CurrentHeatingCoolingState.COOL; } else if (heatModeNum >= 1) { this.platform.log.debug(`[${this.heater.name}] checkTemperatureState: HTMODE=${heatModeNum} indicates active heating`); return this.platform.Characteristic.CurrentHeatingCoolingState.HEAT; } else { this.platform.log.debug(`[${this.heater.name}] checkTemperatureState: HTMODE=${heatModeNum} indicates heater idle`); return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } } } return null; } checkHeatSourceState(heatSource, heatMode) { // If we have no heatSource data, skip this check and fall back to heaterId/temperature logic if (!heatSource) { return null; } // If HTSRC is 00000, heater is completely OFF if (heatSource === '00000') { this.platform.log.debug(`[${this.heater.name}] HTSRC = ${heatSource}, heater is OFF`); return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } // If this heater is not the one assigned to the body, it's OFF if (heatSource !== this.heater.id) { this.platform.log.debug(`[${this.heater.name}] HTSRC = ${heatSource} != ${this.heater.id}, heater not selected`); return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } // Non-00000 HTSRC + valid HTMODE determines heating status if (heatMode !== undefined && heatMode !== null) { // Ensure heatMode is a number (defensive type conversion) const heatModeNum = typeof heatMode === 'string' ? Number(heatMode) : heatMode; if (!isNaN(heatModeNum)) { return this.getStateFromHeatMode(heatSource, heatModeNum); } } return null; // No valid HTMODE, fall back to temperature comparison } getStateFromHeatMode(heatSource, heatMode) { if (heatMode === 9) { this.platform.log.debug(`[${this.heater.name}] HTSRC = ${heatSource}, HTMODE = 9, actively cooling`); return this.platform.Characteristic.CurrentHeatingCoolingState.COOL; } else if (heatMode >= 1) { this.platform.log.debug(`[${this.heater.name}] HTSRC = ${heatSource}, HTMODE = ${heatMode}, actively heating`); return this.platform.Characteristic.CurrentHeatingCoolingState.HEAT; } else { this.platform.log.debug(`[${this.heater.name}] HTSRC = ${heatSource}, HTMODE = 0, heater idle`); return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } } checkTemperatureState() { // First check if this heater is even assigned to this body via heaterId if (this.body.heaterId !== this.heater.id) { this.platform.log.debug(`[${this.heater.name}] checkTemperatureState: Heater not assigned (${this.body.heaterId} != ${this.heater.id})`); return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } // If we have heatMode available, use it even without heatSource const heatModeState = this.checkHeatModeState(); if (heatModeState !== null) { return heatModeState; } if (!this.temperature) { this.platform.log.debug(`[${this.heater.name}] checkTemperatureState: No temperature data available`); return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } if (this.heater.coolingEnabled) { if (this.highTemperature && this.temperature > this.highTemperature) { return this.platform.Characteristic.CurrentHeatingCoolingState.COOL; } if (this.lowTemperature && this.temperature < this.lowTemperature) { return this.platform.Characteristic.CurrentHeatingCoolingState.HEAT; } return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } if (this.lowTemperature && this.temperature < this.lowTemperature) { return this.platform.Characteristic.CurrentHeatingCoolingState.HEAT; } return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } async setTargetTemperature(value) { const convertedValue = this.isFahrenheit ? Math.round((0, util_1.celsiusToFahrenheit)(value)) // Round to nearest 5 : value; this.platform.log.info(`Setting temperature ${value} converted/rounded to: ${convertedValue}` + `for heater ${this.heater.name}`); const command = { command: types_1.IntelliCenterRequestCommand.SetParamList, //Weirdly required. messageID: (0, uuid_1.v4)(), objectList: [ { objnam: this.body.id, params: { [constants_1.LOW_TEMP_KEY]: `${convertedValue}` }, }, ], }; this.platform.sendCommandNoWait(command); } async getCurrentTemperature() { return this.temperature || -1; } async getTargetTemperature() { return this.lowTemperature || this.minValue; } async setCoolingThresholdTemperature(value) { const convertedValue = this.isFahrenheit ? Math.round((0, util_1.celsiusToFahrenheit)(value)) : value; this.platform.log.info(`Setting cooling threshold temperature ${value} converted/rounded to: ${convertedValue} for heater ${this.heater.name}`); const command = { command: types_1.IntelliCenterRequestCommand.SetParamList, messageID: (0, uuid_1.v4)(), objectList: [ { objnam: this.body.id, params: { [constants_1.HIGH_TEMP_KEY]: `${convertedValue}` }, }, ], }; this.platform.sendCommandNoWait(command); } async getCoolingThresholdTemperature() { return this.highTemperature || this.maxValue; } async setHeatingThresholdTemperature(value) { const convertedValue = this.isFahrenheit ? Math.round((0, util_1.celsiusToFahrenheit)(value)) : value; this.platform.log.info(`Setting heating threshold temperature ${value} converted/rounded to: ${convertedValue} for heater ${this.heater.name}`); const command = { command: types_1.IntelliCenterRequestCommand.SetParamList, messageID: (0, uuid_1.v4)(), objectList: [ { objnam: this.body.id, params: { [constants_1.LOW_TEMP_KEY]: `${convertedValue}` }, }, ], }; this.platform.sendCommandNoWait(command); } async getHeatingThresholdTemperature() { return this.lowTemperature || this.minValue; } updateTemperatureRanges(body) { const oldLowTemp = this.lowTemperature; const oldHighTemp = this.highTemperature; const oldTemperature = this.temperature; this.lowTemperature = body.lowTemperature; this.highTemperature = body.highTemperature; // Update current body temperature and body data if (body.temperature !== undefined && body.temperature !== null) { this.temperature = this.isFahrenheit ? (0, util_1.fahrenheitToCelsius)(body.temperature) : body.temperature; this.body.temperature = body.temperature; } // Update body heating mode and heat source for HTMODE/HTSRC-based status if (body.heatMode !== undefined) { this.body.heatMode = body.heatMode; } if (body.heatSource !== undefined) { this.body.heatSource = body.heatSource; } if (this.isFahrenheit) { if (this.lowTemperature) { this.lowTemperature = (0, util_1.fahrenheitToCelsius)(this.lowTemperature); } if (this.highTemperature) { this.highTemperature = (0, util_1.fahrenheitToCelsius)(this.highTemperature); } } // Update HomeKit characteristic if current temperature changed if (this.temperature !== oldTemperature && this.temperature !== undefined) { this.service.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.temperature); this.platform.log.debug(`[${this.heater.name}] Updated current temperature: ` + `${body.temperature}${this.isFahrenheit ? 'F' : 'C'} -> ${this.temperature}C`); } // Update heating/cooling status based on new body data (including HTMODE) const newState = this.getCurrentHeatingCoolingState(); this.platform.log.debug(`[${this.heater.name}] updateTemperatureRanges: Updating CurrentHeatingCoolingState to ${newState}`); this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState, newState); // Update HomeKit characteristic props if ranges changed const rangeChanged = oldLowTemp !== this.lowTemperature || oldHighTemp !== this.highTemperature; if (rangeChanged) { this.updateCharacteristicProps(); } } updateCharacteristicProps() { // Use platform-configured min/max limits for the full allowed range // This ensures users can set any temperature within the configured bounds this.platform.log.debug(`[${this.heater.name}] Temperature range: ${this.minValue}°-${this.maxValue}°C ` + `(low setpoint: ${this.lowTemperature}, high setpoint: ${this.highTemperature})`); if (this.lowTemperature) { this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature).setProps({ minValue: this.minValue, maxValue: this.maxValue, minStep: constants_1.THERMOSTAT_STEP_VALUE, }); } if (this.heater.coolingEnabled && this.highTemperature) { this.service.getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature).setProps({ minValue: this.minValue, maxValue: this.maxValue, minStep: constants_1.THERMOSTAT_STEP_VALUE, }); this.service.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature).setProps({ minValue: this.minValue, maxValue: this.maxValue, minStep: constants_1.THERMOSTAT_STEP_VALUE, }); } } } exports.HeaterAccessory = HeaterAccessory; //# sourceMappingURL=heaterAccessory.js.map