custom min/max applied when limits read

This commit is contained in:
proddy
2026-10-17 13:39:20 +02:00
parent caedc0709c
commit 66d30570ac
17 changed files with 93 additions and 79 deletions
+1 -6
View File
@@ -23,7 +23,6 @@ import {
TextField,
ToggleButton,
ToggleButtonGroup,
Tooltip,
Typography
} from '@mui/material';
@@ -295,11 +294,7 @@ const Customizations = () => {
function formatLimit(value: number | null | undefined) {
if (value === null) {
return (
<Tooltip title={LL.CUSTOMIZATIONS_LIMITS()}>
<WarningIcon color="warning" sx={{ fontSize: 16 }} />
</Tooltip>
);
return '';
}
return formatValue(value);
}
@@ -4,7 +4,6 @@ import CancelIcon from '@mui/icons-material/Cancel';
import CloseIcon from '@mui/icons-material/Close';
import DoneIcon from '@mui/icons-material/Done';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
Button,
@@ -13,9 +12,7 @@ import {
DialogContent,
DialogTitle,
Grid,
InputAdornment,
TextField,
Tooltip,
Typography
} from '@mui/material';
@@ -125,15 +122,6 @@ const CustomizationsDialog = ({
onSave(next);
};
const limitAdornment = (cleared: boolean) =>
cleared ? (
<InputAdornment position="end">
<Tooltip title={LL.CUSTOMIZATIONS_LIMITS()}>
<WarningIcon color="warning" sx={{ fontSize: 16 }} />
</Tooltip>
</InputAdornment>
) : undefined;
return (
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
<DialogTitle>{`${LL.EDIT()} ${LL.ENTITY()}`}</DialogTitle>
@@ -180,9 +168,6 @@ const CustomizationsDialog = ({
sx={{ width: '11ch' }}
type="number"
onChange={updateFormValue}
slotProps={{
input: { endAdornment: limitAdornment(editItem.mi === null) }
}}
/>
</Grid>
<Grid>
@@ -195,9 +180,6 @@ const CustomizationsDialog = ({
sx={{ width: '11ch' }}
type="number"
onChange={updateFormValue}
slotProps={{
input: { endAdornment: limitAdornment(editItem.ma === null) }
}}
/>
</Grid>
</>
-1
View File
@@ -134,7 +134,6 @@ const cs: Translation = {
CUSTOMIZATIONS_RESTART: 'Všechna přizpůsobení byla odstraněna. Restartování...',
CUSTOMIZATIONS_FULL: 'Vybrané entity překročily limit. Ukládejte je postupně',
CUSTOMIZATIONS_SAVED: 'Přizpůsobení uloženo',
CUSTOMIZATIONS_LIMITS: 'Původní limity se vrátí po restartu',
CUSTOMIZATIONS_HELP_1: 'Vyberte zařízení a přizpůsobte možnosti entit nebo klikněte pro přejmenování',
CUSTOMIZATIONS_HELP_2: 'označit jako oblíbené',
CUSTOMIZATIONS_HELP_3: 'zakázat akci zápisu',
-1
View File
@@ -134,7 +134,6 @@ const de: Translation = {
CUSTOMIZATIONS_RESTART: 'Alle Anpassungen wurden entfernt. Neustart...',
CUSTOMIZATIONS_FULL: 'Ausgewählte Entitäten haben das Limit überschritten. Bitte stapelweise speichern!',
CUSTOMIZATIONS_SAVED: 'Anpassungen gespeichert',
CUSTOMIZATIONS_LIMITS: 'Originalgrenzen gelten nach einem Neustart',
CUSTOMIZATIONS_HELP_1: 'Wählen Sie ein Gerät aus und passen Sie die Entitäten mithilfe der Optionen an',
CUSTOMIZATIONS_HELP_2: 'Als Favorit markieren',
CUSTOMIZATIONS_HELP_3: 'Schreibaktion deaktivieren',
-1
View File
@@ -134,7 +134,6 @@ const en: Translation = {
CUSTOMIZATIONS_RESTART: 'All customizations have been removed. Restarting...',
CUSTOMIZATIONS_FULL: 'Selected entities exceeded limit. Please save in batches',
CUSTOMIZATIONS_SAVED: 'Customizations saved',
CUSTOMIZATIONS_LIMITS: 'Original limits return after restart',
CUSTOMIZATIONS_HELP_1: 'Select a device and customize the entities options',
CUSTOMIZATIONS_HELP_2: 'mark as favorite',
CUSTOMIZATIONS_HELP_3: 'disable write action',
-1
View File
@@ -134,7 +134,6 @@ const fr: Translation = {
CUSTOMIZATIONS_RESTART: 'Toutes les personnalisations ont été supprimées. Redémarrage...',
CUSTOMIZATIONS_FULL: 'Les entités sélectionnées ont dépassé la limite. Veuillez sauvegarder par lots',
CUSTOMIZATIONS_SAVED: 'Personnalisations enregistrées',
CUSTOMIZATIONS_LIMITS: "Les limites d'origine reviennent après redémarrage",
CUSTOMIZATIONS_HELP_1: 'Sélectionnez un appareil et personnalisez les options des entités ou cliquez pour renommer',
CUSTOMIZATIONS_HELP_2: 'marquer comme favori',
CUSTOMIZATIONS_HELP_3: "désactiver l'action d'écriture",
-1
View File
@@ -134,7 +134,6 @@ const it: Translation = {
CUSTOMIZATIONS_RESTART: 'Tutte le personalizzazioni sono state rimosse. Riavvio ...',
CUSTOMIZATIONS_FULL: 'Le entità selezionate hanno superato il limite. Si prega di salvare in batch',
CUSTOMIZATIONS_SAVED: 'Personalizzazioni salvate',
CUSTOMIZATIONS_LIMITS: 'I limiti originali tornano dopo il riavvio',
CUSTOMIZATIONS_HELP_1: 'Seleziona un dispositivo e personalizza le opzioni delle entità o fai clic per rinominarlo',
CUSTOMIZATIONS_HELP_2: 'seleziona come preferito',
CUSTOMIZATIONS_HELP_3: 'disabilita azione scrittura',
-1
View File
@@ -134,7 +134,6 @@ const nl: Translation = {
CUSTOMIZATIONS_RESTART: 'Alle custom profielen worden verwijderd. Herstarten...',
CUSTOMIZATIONS_FULL: 'Te veel entiteiten geselecteerd. Sla op in delen aub',
CUSTOMIZATIONS_SAVED: 'Custom aanpassingen opgeslagen',
CUSTOMIZATIONS_LIMITS: 'Originele limieten gelden na herstart',
CUSTOMIZATIONS_HELP_1: 'Selecteer een apparaat en pas de entiteiten aan door middel van de opties',
CUSTOMIZATIONS_HELP_2: 'Markeer als favoriet',
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
-1
View File
@@ -134,7 +134,6 @@ const no: Translation = {
CUSTOMIZATIONS_RESTART: 'Alle tilpasninger har blitt slettet. Restarter...',
CUSTOMIZATIONS_FULL: 'Antall valgte objekter for høyt. Largre i mindre antall om gangen',
CUSTOMIZATIONS_SAVED: 'Tilpasninger lagret',
CUSTOMIZATIONS_LIMITS: 'Opprinnelige grenser gjelder etter omstart',
CUSTOMIZATIONS_HELP_1: 'Velg en enhet og tilpass underenheter med hjelp av alternativer eller velg å gi nytt navn',
CUSTOMIZATIONS_HELP_2: 'merk som favoritt',
CUSTOMIZATIONS_HELP_3: 'inaktiviser skriving',
-1
View File
@@ -134,7 +134,6 @@ const pl: BaseTranslation = {
CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...',
CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadzaj zmiany w mniejszych partiach.',
CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.',
CUSTOMIZATIONS_LIMITS: 'Oryginalne limity wrócą po restarcie',
CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, a następnie dostosuj opcje lub kliknij na nazwie encji by tę nazwę zmienić',
CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną',
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
-1
View File
@@ -134,7 +134,6 @@ const sk: Translation = {
CUSTOMIZATIONS_RESTART: 'Ste si istí, že chcete reštartovať EMS-ESP?',
CUSTOMIZATIONS_FULL: 'Vybrané subjekty prekročili limit. Prosím, ukladajte v dávkach',
CUSTOMIZATIONS_SAVED: 'Uložené prispôsobenia',
CUSTOMIZATIONS_LIMITS: 'Pôvodné limity sa vrátia po reštarte',
CUSTOMIZATIONS_HELP_1: 'Vyberte zariadenie a prispôsobte možnosti entít alebo kliknutím premenujte',
CUSTOMIZATIONS_HELP_2: 'označiť ako obľúbené',
CUSTOMIZATIONS_HELP_3: 'zakázať akciu zápisu',
-1
View File
@@ -134,7 +134,6 @@ const sv: Translation = {
CUSTOMIZATIONS_RESTART: 'Alla anpassningar har raderats. Startar om...',
CUSTOMIZATIONS_FULL: 'För många valda enheter. Vänligen spara ett mindre antal åt gången.',
CUSTOMIZATIONS_SAVED: 'Anpassningarna är sparade',
CUSTOMIZATIONS_LIMITS: 'Originalgränserna gäller efter omstart',
CUSTOMIZATIONS_HELP_1: 'Välj en enhet och anpassa entiteter med hjälp av alternativen',
CUSTOMIZATIONS_HELP_2: 'Favorit',
CUSTOMIZATIONS_HELP_3: 'Skrivskyddad',
-1
View File
@@ -134,7 +134,6 @@ const tr: Translation = {
CUSTOMIZATIONS_RESTART: 'Bütün özelleştirmeler kaldırıldı. Yeniden başlatılıyor...',
CUSTOMIZATIONS_FULL: 'Seçilen varlıklar sınırı aşmaktadır. Lütfen parçalar halinde kaydedin',
CUSTOMIZATIONS_SAVED: 'Özelleştirmeler kaydedildi',
CUSTOMIZATIONS_LIMITS: 'Orijinal sınırlar yeniden başlatınca döner',
CUSTOMIZATIONS_HELP_1: 'Bir cihaz seçip varlıkların seçeneklerini özelleştirin veya yeniden adlandırmak için tıklayın',
CUSTOMIZATIONS_HELP_2: 'favori olarak işaretle',
CUSTOMIZATIONS_HELP_3: 'yazma işlemini devre dışı bırak',
+34 -20
View File
@@ -877,7 +877,6 @@ void EMSdevice::set_minmax(const void * value_p, int16_t min, uint32_t max) {
if (dv.value_p == value_p) {
dv.min = min;
dv.max = max;
dv.set_custom_minmax(); // custom priority
return;
}
}
@@ -1340,6 +1339,7 @@ void EMSdevice::setCustomizationEntity(const std::string & entity_id) {
// check the masks
uint8_t current_mask = dv.state >> 4;
uint8_t new_mask = Helpers::hextoint(entity_id.substr(0, 2).c_str()); // first character contains mask flags
bool had_limit = dv.custom_fullname().find('>') != std::string::npos || dv.custom_fullname().find('<') != std::string::npos;
// if it's a new mask, reconfigure HA
if (Mqtt::ha_enabled() && (has_custom_name || ((current_mask ^ new_mask) & (DeviceValueState::DV_READONLY >> 4)))) {
@@ -1358,14 +1358,19 @@ void EMSdevice::setCustomizationEntity(const std::string & entity_id) {
dv.set_custom_fullname("");
}
auto min = dv.min;
auto max = dv.max;
// set the min / max
dv.set_custom_minmax();
if (Mqtt::ha_enabled() && dv.tag <= DeviceValueTAG::TAG_HC8 && !strcmp(dv.short_name, FL_(selRoomTemp)[0]) && (min != dv.min || max != dv.max)) {
set_climate_minmax(dv.tag, dv.min, dv.max);
// Climate discovery reads its own min/max. selRoomTemp keeps the register limits.
// A custom range is copied across; clearing it restores the limits haclimate was registered with (5..30).
if (Mqtt::ha_enabled() && dv.tag <= DeviceValueTAG::TAG_HC8 && !strcmp(dv.short_name, FL_(selRoomTemp)[0])) {
int16_t eff_min = dv.min;
uint32_t eff_max = dv.max;
bool min_custom = false;
bool max_custom = false;
dv.apply_custom_minmax(eff_min, eff_max, &min_custom, &max_custom);
if (min_custom || max_custom) {
set_climate_minmax(dv.tag, eff_min, eff_max);
} else if (had_limit) {
set_climate_minmax(dv.tag, 5, 30);
}
}
return;
@@ -2218,18 +2223,27 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
// check for value outside min/max range and adapt the limits to avoid HA complains
// Should this also check for api output?
if ((output_target == OUTPUT_TARGET::MQTT) && (dv.min != 0 || dv.max != 0)) {
int v = Helpers::atoint(val);
if (fahrenheit) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
// Only the register limits are widened. A custom limit stays in the name so a reset can restore the original.
{
int16_t eff_min = dv.min;
uint32_t eff_max = dv.max;
bool min_custom = false;
bool max_custom = false;
dv.apply_custom_minmax(eff_min, eff_max, &min_custom, &max_custom);
if (v < dv.min) {
dv.min = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
} else if (v > 0 && (uint32_t)v > dv.max) {
dv.max = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
if ((output_target == OUTPUT_TARGET::MQTT) && (eff_min != 0 || eff_max != 0)) {
int v = Helpers::atoint(val);
if (fahrenheit) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (v < eff_min && !min_custom) {
dv.min = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
} else if (v > 0 && (uint32_t)v > eff_max && !max_custom) {
dv.max = v;
dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
}
}
}
}
+48 -20
View File
@@ -66,9 +66,6 @@ DeviceValue::DeviceValue(uint8_t device_type,
custom_fullname_ = std::make_unique<std::string>(custom_fullname);
}
// set the min/max (reads back the custom name set above)
set_custom_minmax();
/*
#ifdef EMSESP_STANDALONE
// only added for debugging
@@ -296,11 +293,15 @@ bool DeviceValue::has_tag() const {
bool DeviceValue::get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max) {
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
// if we have individual limits set already, just do the conversion
// Register limits stay in min/max. Custom limits, if any, are applied only here.
// limits are not scaled with num operator and temperatures are °C
if (min != 0 || max != 0) {
dv_set_min = Helpers::transformNumFloat(min, 0, fahrenheit);
dv_set_max = Helpers::transformNumFloat(max, 0, fahrenheit);
int16_t eff_min = min;
uint32_t eff_max = max;
apply_custom_minmax(eff_min, eff_max);
if (eff_min != 0 || eff_max != 0) {
dv_set_min = Helpers::transformNumFloat(eff_min, 0, fahrenheit);
dv_set_max = Helpers::transformNumFloat(eff_max, 0, fahrenheit);
return true;
}
@@ -350,8 +351,8 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max) {
return false; // nothing changed, not supported
}
// extract custom min from custom_fullname
bool DeviceValue::get_custom_min(int16_t & val) {
// extract custom min from custom_fullname, in °C. Does not range-check against the register limits.
bool DeviceValue::get_custom_min(int16_t & val) const {
const auto & cf = custom_fullname();
auto min_pos = cf.find('>');
bool has_min = (min_pos != std::string::npos);
@@ -362,18 +363,14 @@ bool DeviceValue::get_custom_min(int16_t & val) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (max > 0 && v > 0 && (uint32_t)v > max) {
return false;
}
val = v;
}
return has_min;
}
// extract custom max from custom_fullname
bool DeviceValue::get_custom_max(uint32_t & val) {
// extract custom max from custom_fullname, in °C. Does not range-check against the register limits.
bool DeviceValue::get_custom_max(uint32_t & val) const {
const auto & cf = custom_fullname();
auto max_pos = cf.find('<');
bool has_max = (max_pos != std::string::npos);
@@ -384,7 +381,7 @@ bool DeviceValue::get_custom_max(uint32_t & val) {
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
}
if (v < 0 || v < (int32_t)min) {
if (v < 0) {
return false;
}
@@ -394,10 +391,41 @@ bool DeviceValue::get_custom_max(uint32_t & val) {
return has_max;
}
// sets min max to stored custom values (if set)
void DeviceValue::set_custom_minmax() {
get_custom_min(min);
get_custom_max(max);
// Overlay custom limits onto copies of the register min/max. A custom min is checked against
// the custom max when both are set, so a range outside the register limits is still accepted.
void DeviceValue::apply_custom_minmax(int16_t & eff_min, uint32_t & eff_max, bool * min_custom, bool * max_custom) const {
int16_t cmin = 0;
uint32_t cmax = 0;
bool has_cmin = get_custom_min(cmin);
bool has_cmax = get_custom_max(cmax);
if (has_cmin) {
uint32_t bound = has_cmax ? cmax : eff_max;
if (bound > 0 && cmin > 0 && (uint32_t)cmin > bound) {
has_cmin = false;
}
}
if (has_cmax) {
int32_t bound = has_cmin ? cmin : eff_min;
if ((int32_t)cmax < bound) {
has_cmax = false;
}
}
if (has_cmin) {
eff_min = cmin;
}
if (has_cmax) {
eff_max = cmax;
}
if (min_custom) {
*min_custom = has_cmin;
}
if (max_custom) {
*max_custom = has_cmax;
}
}
// raw stored custom name (empty string if none was set)
+6 -3
View File
@@ -218,9 +218,12 @@ class DeviceValue {
bool has_tag() const;
bool get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max);
void set_custom_minmax();
bool get_custom_min(int16_t & val);
bool get_custom_max(uint32_t & val);
// Overlay a custom >min/<max from the name onto eff_min/eff_max.
// The stored register limits (min/max) are left unchanged.
// min_custom/max_custom report whether that side came from the custom name.
void apply_custom_minmax(int16_t & eff_min, uint32_t & eff_max, bool * min_custom = nullptr, bool * max_custom = nullptr) const;
bool get_custom_min(int16_t & val) const;
bool get_custom_max(uint32_t & val) const;
std::string get_custom_fullname() const;
std::string get_fullname() const;
static std::string get_name(const std::string & entity);
+4 -1
View File
@@ -458,7 +458,10 @@ int Modbus::getRegisterCount(const DeviceValue & dv) {
case DeviceValue::CMD: {
// calculate a sensible register size from min, max and numeric_operator
uint32_t num_values = std::max(dv.max, (uint32_t)abs(dv.min));
int16_t eff_min = dv.min;
uint32_t eff_max = dv.max;
dv.apply_custom_minmax(eff_min, eff_max);
uint32_t num_values = std::max(eff_max, (uint32_t)abs(eff_min));
int num_registers = 0;
if (num_values < (1L << 16))