homebridge-pentair-intellicenter-ai
Version:
Homebridge plugin for the Pentair IntelliCenter pool control system (maintained with AI assistance)
415 lines • 21.1 kB
JavaScript
"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