diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx
index c2895e291..f3d36fcbb 100644
--- a/interface/src/project/SettingsCustomization.tsx
+++ b/interface/src/project/SettingsCustomization.tsx
@@ -137,7 +137,7 @@ const SettingsCustomization: FC = () => {
}, [LL]);
const setInitialMask = (data: DeviceEntity[]) => {
- setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn })));
+ setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })));
};
const fetchDeviceEntities = async (unique_id: number) => {
@@ -249,8 +249,14 @@ const SettingsCustomization: FC = () => {
const saveCustomization = async () => {
if (devices && deviceEntities && selectedDevice !== -1) {
const masked_entities = deviceEntities
- .filter((de) => de.m !== de.o_m || de.cn !== de.o_cn)
- .map((new_de) => new_de.m.toString(16).padStart(2, '0') + new_de.id + (new_de.cn ? '|' + new_de.cn : ''));
+ .filter((de) => de.m !== de.o_m || de.cn !== de.o_cn || de.ma !== de.o_ma || de.mi !== de.o_mi)
+ .map((new_de) =>
+ new_de.m.toString(16).padStart(2, '0') +
+ new_de.id +
+ ((new_de.cn || new_de.mi || new_de.ma) ? '|' : '') +
+ (new_de.cn ? new_de.cn : '') +
+ (new_de.mi ? '>' + new_de.mi : '') +
+ (new_de.ma ? '<' + new_de.ma : ''));
// check size in bytes to match buffer in CPP, which is 4096
const bytes = new TextEncoder().encode(JSON.stringify(masked_entities)).length;
@@ -331,7 +337,7 @@ const SettingsCustomization: FC = () => {
setDeviceEntities((prevState) => {
const newState = prevState.map((obj) => {
if (obj.id === deviceEntity.id) {
- return { ...obj, cn: deviceEntity.cn };
+ return { ...obj, cn: deviceEntity.cn, mi: deviceEntity.mi, ma: deviceEntity.ma };
}
return obj;
});
@@ -627,6 +633,28 @@ const SettingsCustomization: FC = () => {
onChange={updateValue(setDeviceEntity)}
/>
+ {typeof de.v === 'number' && de.w && (
+ <>
+
+
+
+
+
+
+ >
+ )}
diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts
index 7cac14bd7..5241cb315 100644
--- a/interface/src/project/types.ts
+++ b/interface/src/project/types.ts
@@ -142,6 +142,10 @@ export interface DeviceEntity {
o_m?: number; // original mask before edits
o_cn?: string; // original cn before edits
w: boolean; // writeable
+ mi?: string; // min value
+ ma?: string; // max value
+ o_mi?: string;
+ o_ma?: string;
}
export interface CustomEntities {
diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp
index 3c4ab5170..6e3546e61 100644
--- a/src/emsdevice.cpp
+++ b/src/emsdevice.cpp
@@ -472,6 +472,7 @@ void EMSdevice::add_device_value(uint8_t tag,
// add the device entity
devicevalues_.emplace_back(
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);
+ devicevalues_.back().set_custom_minmax();
// add a new command if it has a function attached
if (!has_cmd) {
@@ -873,7 +874,7 @@ void EMSdevice::generate_values_web(JsonObject & output) {
// as generate_values_web() but stripped down to only show all entities and their state
// this is used only for WebCustomizationService::device_entities()
void EMSdevice::generate_values_web_customization(JsonArray & output) {
- for (const auto & dv : devicevalues_) {
+ for (auto & dv : devicevalues_) {
// also show commands and entities that have an empty full name
JsonObject obj = output.createNestedObject();
@@ -958,8 +959,9 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
}
// add the custom name, is optional
- if (!dv.custom_fullname.empty()) {
- obj["cn"] = dv.custom_fullname;
+ std::string custom_fullname = dv.get_custom_fullname();
+ if (!custom_fullname.empty()) {
+ obj["cn"] = custom_fullname;
}
} else {
// it's a command
@@ -968,6 +970,16 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) {
obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble
obj["w"] = dv.has_cmd; // if writable
+
+ // set the custom min and max values if there are any
+ int16_t dv_set_min;
+ uint16_t dv_set_max;
+ if (dv.get_custom_min(dv_set_min)) {
+ obj["mi"] = dv_set_min;
+ }
+ if (dv.get_custom_max(dv_set_max)) {
+ obj["ma"] = dv_set_max;
+ }
}
}
@@ -1009,7 +1021,7 @@ void EMSdevice::setCustomEntity(const std::string & entity_id) {
} else {
dv.custom_fullname = "";
}
-
+ dv.set_custom_minmax();
return;
}
}
@@ -1405,6 +1417,24 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c
// else if (dv.type == DeviceValueType::CMD && output_target != EMSdevice::OUTPUT_TARGET::MQTT) {
// json[name] = "";
// }
+
+ // 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)) {
+ if (json[name].is() || json[name].is()) {
+ int v = json[name];
+ if (fahrenheit) {
+ v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
+ }
+ if (v < dv.min) {
+ dv.min = v;
+ dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
+ } else if (v > dv.max) {
+ dv.max = v;
+ dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED);
+ }
+ }
+ }
}
}
}
diff --git a/src/emsdevicevalue.cpp b/src/emsdevicevalue.cpp
index 649d4fff8..72087864c 100644
--- a/src/emsdevicevalue.cpp
+++ b/src/emsdevicevalue.cpp
@@ -326,13 +326,58 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, int16_t & dv_set_max) {
return false; // nothing changed, not supported
}
-// returns the translated fullname or the custom fullname (if provided)
-// always returns a std::string
-std::string DeviceValue::get_fullname() const {
- if (custom_fullname.empty()) {
- return Helpers::translated_word(fullname);
+// extract custom min from custom_fullname
+bool DeviceValue::get_custom_min(int16_t & v) {
+ auto min_pos = custom_fullname.find('>');
+ bool has_min = (min_pos != std::string::npos);
+ uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
+ if (has_min) {
+ v = Helpers::atoint(custom_fullname.substr(min_pos + 1).c_str());
+ if (fahrenheit) {
+ v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
+ }
+ }
+ return has_min;
+}
+
+// extract custom max from custom_fullname
+bool DeviceValue::get_custom_max(uint16_t & v) {
+ auto max_pos = custom_fullname.find('<');
+ bool has_max = (max_pos != std::string::npos);
+ uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
+ if (has_max) {
+ v = Helpers::atoint(custom_fullname.substr(max_pos + 1).c_str());
+ if (fahrenheit) {
+ v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
+ }
+ }
+ 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);
+}
+
+std::string DeviceValue::get_custom_fullname() const {
+ auto min_pos = custom_fullname.find('>');
+ auto max_pos = custom_fullname.find('<');
+ auto minmax_pos = min_pos < max_pos ? min_pos : max_pos;
+ if (minmax_pos != std::string::npos) {
+ return custom_fullname.substr(0, minmax_pos);
}
return custom_fullname;
}
+// returns the translated fullname or the custom fullname (if provided)
+// always returns a std::string
+std::string DeviceValue::get_fullname() const {
+ std::string customname = get_custom_fullname();
+ if (customname.empty()) {
+ return Helpers::translated_word(fullname);
+ }
+ return customname;
+}
+
} // namespace emsesp
diff --git a/src/emsdevicevalue.h b/src/emsdevicevalue.h
index 6420692fa..269a476d4 100644
--- a/src/emsdevicevalue.h
+++ b/src/emsdevicevalue.h
@@ -179,6 +179,10 @@ class DeviceValue {
bool hasValue() const;
bool get_min_max(int16_t & dv_set_min, int16_t & dv_set_max);
+ void set_custom_minmax();
+ bool get_custom_min(int16_t & v);
+ bool get_custom_max(uint16_t & v);
+ std::string get_custom_fullname() const;
std::string get_fullname() const;
// dv state flags