Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .homeycompose/drivers/pair/select_groupaddresses/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@
<select id="fan_auto_mode_status_ga_dropdown" class="knx-dropdown right"></select>
<input id="fan_auto_mode_status_address" type="text" class="knx-text" placeholder="e.g. 0/1/2" />
</div>
<div class="knx-entry">
<label for="temperature_min_address" class="left"
data-i18n="settings.groupaddresses.thermostat.temperature_min"></label>
</div>
<div class="knx-entry">
<select id="temperature_min_ga_dropdown" class="knx-dropdown right"></select>
<input id="temperature_min_address" type="text" class="knx-text" placeholder="e.g. 0/1/2" />
</div>
<div class="knx-entry">
<label for="temperature_max_address" class="left"
data-i18n="settings.groupaddresses.thermostat.temperature_max"></label>
</div>
<div class="knx-entry">
<select id="temperature_max_ga_dropdown" class="knx-dropdown right"></select>
<input id="temperature_max_address" type="text" class="knx-text" placeholder="e.g. 0/1/2" />
</div>
</div>
</fieldset>
<fieldset id="knx-windowcovering-fieldset" style="display: none">
Expand Down Expand Up @@ -590,6 +606,8 @@
'ga_fan_speed_status': $('#fan_speed_status_address').val(),
'ga_fan_auto_mode': $('#fan_auto_mode_address').val(),
'ga_fan_auto_mode_status': $('#fan_auto_mode_status_address').val(),
'ga_temperature_min': $('#temperature_min_address').val(),
'ga_temperature_max': $('#temperature_max_address').val(),
'fan_speed_steps': parseInt($('#fan_speed_steps').val()),
'macAddress': interfaceMAC,
};
Expand Down
135 changes: 135 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -5109,6 +5109,75 @@
}
]
},
{
"type": "group",
"label": {
"en": "Temperature limits",
"fr": "Limites de température",
"de": "Temperaturgrenzen",
"nl": "Temperatuurgrenzen",
"da": "Temperaturgrænser",
"es": "Límites de temperatura",
"it": "Limiti di temperatura",
"no": "Temperaturgrenser",
"sv": "Temperaturgränser",
"pl": "Limity temperatury",
"ru": "Пределы температуры",
"ko": "온도 제한"
},
"children": [
{
"id": "min_temperature",
"type": "number",
"label": {
"en": "Minimum target temperature",
"fr": "Température cible minimale",
"de": "Minimale Zieltemperatur",
"nl": "Minimale doeltemperatuur",
"da": "Minimum måltemperatur",
"es": "Temperatura objetivo mínima",
"it": "Temperatura target minima",
"no": "Minimum måltemperatur",
"sv": "Minsta måltemperatur",
"pl": "Minimalna temperatura docelowa",
"ru": "Минимальная целевая температура",
"ko": "최소 목표 온도"
},
"hint": {
"en": "Minimum value for the target temperature slider (°C). Default: 4",
"fr": "Valeur minimale du curseur de température cible (°C). Défaut : 4"
},
"value": 4,
"min": -50,
"max": 100
},
{
"id": "max_temperature",
"type": "number",
"label": {
"en": "Maximum target temperature",
"fr": "Température cible maximale",
"de": "Maximale Zieltemperatur",
"nl": "Maximale doeltemperatuur",
"da": "Maksimal måltemperatur",
"es": "Temperatura objetivo máxima",
"it": "Temperatura target massima",
"no": "Maksimum måltemperatur",
"sv": "Högsta måltemperatur",
"pl": "Maksymalna temperatura docelowa",
"ru": "Максимальная целевая температура",
"ko": "최대 목표 온도"
},
"hint": {
"en": "Maximum value for the target temperature slider (°C). Default: 35",
"fr": "Valeur maximale du curseur de température cible (°C). Défaut : 35"
},
"value": 35,
"min": -50,
"max": 100
}
]
},
{
"type": "group",
"label": {
Expand Down Expand Up @@ -5336,6 +5405,72 @@
"ru": "Введите групповой адрес",
"ko": "그룹 주소 입력"
}
},
{
"id": "ga_temperature_min",
"type": "text",
"label": {
"en": "Minimum temperature limit address",
"fr": "Adresse de limite de température minimale",
"de": "Adresse für minimale Temperaturgrenze",
"nl": "Adres voor minimale temperatuurlimiet",
"da": "Adresse til minimumstemperaturgrænse",
"es": "Dirección de límite de temperatura mínima",
"it": "Indirizzo limite temperatura minima",
"no": "Adresse for minimumstemperaturgrense",
"sv": "Adress för minimitemperaturgräns",
"pl": "Adres limitu minimalnej temperatury",
"ru": "Адрес минимального предела температуры",
"ko": "최소 온도 제한 주소"
},
"value": "",
"hint": {
"en": "Enter groupaddress",
"nl": "Vul groepadres in",
"da": "Indtast gruppeadresse",
"de": "Gruppenadresse eingeben",
"es": "Ingrese la dirección de grupo",
"fr": "Entrez l'adresse de groupe",
"it": "Inserisci l'indirizzo di gruppo",
"no": "Angi gruppadresse",
"sv": "Ange gruppadress",
"pl": "Wpisz adres grupowy",
"ru": "Введите групповой адрес",
"ko": "그룹 주소 입력"
}
},
{
"id": "ga_temperature_max",
"type": "text",
"label": {
"en": "Maximum temperature limit address",
"fr": "Adresse de limite de température maximale",
"de": "Adresse für maximale Temperaturgrenze",
"nl": "Adres voor maximale temperatuurlimiet",
"da": "Adresse til maksimumstemperaturgrænse",
"es": "Dirección de límite de temperatura máxima",
"it": "Indirizzo limite temperatura massima",
"no": "Adresse for maksimumstemperaturgrense",
"sv": "Adress för maximitemperaturgräns",
"pl": "Adres limitu maksymalnej temperatury",
"ru": "Адрес максимального предела температуры",
"ko": "최대 온도 제한 주소"
},
"value": "",
"hint": {
"en": "Enter groupaddress",
"nl": "Vul groepadres in",
"da": "Indtast gruppeadresse",
"de": "Gruppenadresse eingeben",
"es": "Ingrese la dirección de grupo",
"fr": "Entrez l'adresse de groupe",
"it": "Inserisci l'indirizzo di gruppo",
"no": "Angi gruppadresse",
"sv": "Ange gruppadress",
"pl": "Wpisz adres grupowy",
"ru": "Введите групповой адрес",
"ko": "그룹 주소 입력"
}
}
]
},
Expand Down
94 changes: 94 additions & 0 deletions drivers/knx_thermostat/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const DatapointTypeParser = require('../../lib/DatapointTypeParser');

class KNXThermostat extends KNXGenericDevice {

static DEFAULT_TEMPERATURE_MIN = 4;
static DEFAULT_TEMPERATURE_MAX = 35;

async onInit() {
this.registerCapabilityListener('target_temperature', this.onCapabilityTargetTemperature.bind(this));
if (typeof this.settings.ga_hvac_operating_mode === 'string' && this.settings.ga_hvac_operating_mode !== '') {
Expand All @@ -22,6 +25,8 @@ class KNXThermostat extends KNXGenericDevice {
} else {
await this.removeCapabilityIfExists('knx_fan_auto_mode');
}
// Must run before super.onInit() which calls addKNXEventListeners (checks hasCapability)
await this.initTemperatureLimitsCapability();
super.onInit();
}

Expand Down Expand Up @@ -57,6 +62,24 @@ class KNXThermostat extends KNXGenericDevice {
await this.removeCapability('knx_fan_auto_mode').catch(this.error);
}
}
if (changedKeys.includes('min_temperature') && !newSettings.ga_temperature_min) {
const min = typeof newSettings.min_temperature === 'number' ? newSettings.min_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MIN;
const max = this.getCapabilityValue('target_temperature_max')
?? (typeof newSettings.max_temperature === 'number' ? newSettings.max_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MAX);
await this.applyTemperatureLimits(min, max);
}
if (changedKeys.includes('max_temperature') && !newSettings.ga_temperature_max) {
const max = typeof newSettings.max_temperature === 'number' ? newSettings.max_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MAX;
const min = this.getCapabilityValue('target_temperature_min')
?? (typeof newSettings.min_temperature === 'number' ? newSettings.min_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MIN);
await this.applyTemperatureLimits(min, max);
}
if (changedKeys.includes('ga_temperature_min') && newSettings.ga_temperature_min) {
this.getTemperatureMin();
}
if (changedKeys.includes('ga_temperature_max') && newSettings.ga_temperature_max) {
this.getTemperatureMax();
}
await super.onSettings({ oldSettings, newSettings, changedKeys });
}

Expand Down Expand Up @@ -92,6 +115,43 @@ class KNXThermostat extends KNXGenericDevice {
this.registerCapabilityListener('knx_fan_auto_mode', this.onCapabilityFanAutoMode.bind(this));
}

async initTemperatureLimitsCapability() {
await this.addCapabilityIfNotExists('target_temperature_min');
await this.addCapabilityIfNotExists('target_temperature_max');
// Register listeners required by Homey for setable capabilities
this.registerCapabilityListener('target_temperature_min', async (value) => {
const max = this.getCapabilityValue('target_temperature_max') ?? KNXThermostat.DEFAULT_TEMPERATURE_MAX;
await this.applyTemperatureLimits(value, max);
});
this.registerCapabilityListener('target_temperature_max', async (value) => {
const min = this.getCapabilityValue('target_temperature_min') ?? KNXThermostat.DEFAULT_TEMPERATURE_MIN;
await this.applyTemperatureLimits(min, value);
});
// On restart, prefer persisted capability value; fall back to settings (or default) on first install
const savedMin = this.getCapabilityValue('target_temperature_min');
const savedMax = this.getCapabilityValue('target_temperature_max');
const settingsMin = typeof this.settings.min_temperature === 'number' ? this.settings.min_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MIN;
const settingsMax = typeof this.settings.max_temperature === 'number' ? this.settings.max_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MAX;
const min = typeof savedMin === 'number' ? savedMin : settingsMin;
const max = typeof savedMax === 'number' ? savedMax : settingsMax;
await this.applyTemperatureLimits(min, max);
}

async applyTemperatureLimits(min, max) {
if (typeof min !== 'number' || typeof max !== 'number' || min >= max) {
this.error(`Invalid temperature limits: min=${min}, max=${max}`);
return;
}
this.log(`applyTemperatureLimits: min=${min}, max=${max}`);
// Expand the allowed range of limit capabilities first (built-in caps are capped at 4–35)
await this.setCapabilityOptions('target_temperature_min', { min: -50, max: 100 }).catch(this.error);
await this.setCapabilityOptions('target_temperature_max', { min: -50, max: 100 }).catch(this.error);
// Update the thermostat slider range before setting values
await this.setCapabilityOptions('target_temperature', { min, max }).catch(this.error);
await this.setCapabilityValue('target_temperature_min', min).catch(this.error);
await this.setCapabilityValue('target_temperature_max', max).catch(this.error);
}

onKNXEvent(groupaddress, data) {
super.onKNXEvent(groupaddress, data);
if (groupaddress === this.getStatusAddress('ga_temperature_target')) {
Expand Down Expand Up @@ -131,6 +191,22 @@ class KNXThermostat extends KNXGenericDevice {
this.error('Set fan auto mode error', knxerror);
});
}
if (groupaddress === this.settings.ga_temperature_min) {
const min = DatapointTypeParser.dpt9(data);
if (min !== null) {
const max = this.getCapabilityValue('target_temperature_max')
?? (typeof this.settings.max_temperature === 'number' ? this.settings.max_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MAX);
this.applyTemperatureLimits(min, max).catch(this.error);
}
}
if (groupaddress === this.settings.ga_temperature_max) {
const max = DatapointTypeParser.dpt9(data);
if (max !== null) {
const min = this.getCapabilityValue('target_temperature_min')
?? (typeof this.settings.min_temperature === 'number' ? this.settings.min_temperature : KNXThermostat.DEFAULT_TEMPERATURE_MIN);
this.applyTemperatureLimits(min, max).catch(this.error);
}
}
}

onKNXConnection(connectionStatus) {
Expand All @@ -146,6 +222,8 @@ class KNXThermostat extends KNXGenericDevice {
this.getHVACOperatingMode();
this.getFanAutoMode();
this.getFanSpeed();
this.getTemperatureMin();
this.getTemperatureMax();
}

getTargetTemperature() {
Expand Down Expand Up @@ -234,6 +312,22 @@ class KNXThermostat extends KNXGenericDevice {
.catch(this.error);
}

getTemperatureMin() {
if (!this.knxInterface || !this.settings.ga_temperature_min) {
return;
}
this.knxInterface.readKNXGroupAddress(this.settings.ga_temperature_min)
.catch(this.error);
}

getTemperatureMax() {
if (!this.knxInterface || !this.settings.ga_temperature_max) {
return;
}
this.knxInterface.readKNXGroupAddress(this.settings.ga_temperature_max)
.catch(this.error);
}

onCapabilityFanAutoMode(value) {
if (!this.knxInterface || !this.settings.ga_fan_auto_mode) {
return null;
Expand Down
Loading
Loading