diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index bd71d324d..2a610339d 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -35,6 +35,8 @@ - Read time from IVT-controller [#439](https://github.com/emsesp/EMS-ESP32/issues/439) - Hybrid Heatpump product-id 168 [#459](https://github.com/emsesp/EMS-ESP32/issues/459), thermostat settings - Junkers ISM2 and IPM in warm water mode [#437](https://github.com/emsesp/EMS-ESP32/issues/437) +- Added Shower Alert trigger time and cold shot time [#436](https://github.com/emsesp/EMS-ESP32/issues/436) +- Improved Table layout in Web UI (searching, filtering, sorting, exporting to CSV) ### Fixed @@ -53,6 +55,7 @@ - Burner selected max power can have a value higher than 100% [#314](https://github.com/emsesp/EMS-ESP32/issues/314) - some missing fahrenheit calculations - limited number of exclusions [#339](https://github.com/emsesp/EMS-ESP32/issues/339) +- MQTT sometimes would not reconnect after a WiFi outage ### Changed @@ -63,7 +66,7 @@ - Show Sensors quality in WebUI - Controller not shown in WebUI dashboard - renamed "Home Assistant Integration" to "MQTT Discovery" in MQTT Settings [#290](https://github.com/emsesp/EMS-ESP32/issues/290) -- Show ems tx reads and writes separatly +- Show ems tx reads and writes separately - Show ems device handlers separated for received, fetched and pending handlers. - Wired renamed to Ethernet - removed system/pin command, new commands in analogsensors diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 0ea86722b..353f1faf0 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -343,11 +343,6 @@ const SettingsApplication: FC = () => { label="Convert temperature values to Fahrenheit" disabled={saving} /> - } - label="Underclock CPU speed" - disabled={saving} - /> } label="Bypass Access Token authorization on API calls" @@ -358,6 +353,11 @@ const SettingsApplication: FC = () => { label="Enable Read only mode (blocks all outgoing EMS Tx write commands)" disabled={saving} /> + } + label="Underclock CPU speed" + disabled={saving} + /> } @@ -367,8 +367,36 @@ const SettingsApplication: FC = () => { } label="Enable Shower Alert" - disabled={saving} + disabled={!data.shower_timer} /> + {data.shower_alert && ( + <> + + + + + + + + )} Formatting Options diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index c78745e55..0d4d048b6 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -9,6 +9,8 @@ export interface Settings { master_thermostat: number; shower_timer: boolean; shower_alert: boolean; + shower_alert_coldshot: number; + shower_alert_trigger: number; rx_gpio: number; tx_gpio: number; telnet_enabled: boolean; @@ -41,8 +43,8 @@ export enum busConnectionStatus { export interface Stat { id: string; // name s: number; // success - f: number; // fail - q: number; // quality + f: number; // fail + q: number; // quality } export interface Status { status: busConnectionStatus; diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 88742d3e4..a626489d2 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -40,5 +40,9 @@ export const createSettingsValidator = (settings: Settings) => { required: true, message: 'Mark interval is required' }, { type: 'number', min: 0, max: 10, message: 'Port must be between 0 and 10' } ] + }), + ...(settings.shower_alert && { + shower_alert_trigger: [{ type: 'number', min: 1, max: 20, message: 'Time must be between 1 and 20 minutes' }], + shower_alert_coldshot: [{ type: 'number', min: 1, max: 10, message: 'Time must be between 1 and 10 seconds' }] }) }); diff --git a/lib_standalone/ESP8266React.h b/lib_standalone/ESP8266React.h index 600a1c74a..4b31d671c 100644 --- a/lib_standalone/ESP8266React.h +++ b/lib_standalone/ESP8266React.h @@ -19,39 +19,41 @@ class DummySettings { public: - uint8_t tx_mode = 1; - uint8_t ems_bus_id = 0x0B; - bool syslog_enabled = false; - int8_t syslog_level = 3; // uuid::log::Level - uint32_t syslog_mark_interval = 0; - String syslog_host = "192.168.1.4"; - uint16_t syslog_port = 514; - uint8_t master_thermostat = 0; - bool shower_timer = true; - bool shower_alert = false; - bool hide_led = false; - bool notoken_api = false; - bool readonly_mode = false; - uint8_t bool_format = 1; // using "on" and "off" - uint8_t enum_format = 1; - bool nosleep = false; - bool fahrenheit = false; - bool bandwidth20 = false; - bool telnet_enabled = false; - String board_profile = "CUSTOM"; - bool trace_raw = false; - bool analog_enabled = true; // analog is enabled - int8_t weblog_level = 1; - uint8_t weblog_buffer = 50; - bool weblog_compact = true; - uint8_t rx_gpio = 0; - uint8_t tx_gpio = 0; - uint8_t dallas_gpio = 16; // to ensure its enabled - bool dallas_parasite = false; - uint8_t led_gpio = 0; - bool low_clock = false; - uint8_t pbutton_gpio = false; - uint8_t solar_maxflow = 30; + uint8_t tx_mode = 1; + uint8_t ems_bus_id = 0x0B; + bool syslog_enabled = false; + int8_t syslog_level = 3; // uuid::log::Level + uint32_t syslog_mark_interval = 0; + String syslog_host = "192.168.1.4"; + uint16_t syslog_port = 514; + uint8_t master_thermostat = 0; + bool shower_timer = true; + bool shower_alert = false; + uint8_t shower_alert_coldshot = 10; + uint8_t shower_alert_trigger = 7; + bool hide_led = false; + bool notoken_api = false; + bool readonly_mode = false; + uint8_t bool_format = 1; // using "on" and "off" + uint8_t enum_format = 1; + bool nosleep = false; + bool fahrenheit = false; + bool bandwidth20 = false; + bool telnet_enabled = false; + String board_profile = "CUSTOM"; + bool trace_raw = false; + bool analog_enabled = true; // analog is enabled + int8_t weblog_level = 1; + uint8_t weblog_buffer = 50; + bool weblog_compact = true; + uint8_t rx_gpio = 0; + uint8_t tx_gpio = 0; + uint8_t dallas_gpio = 16; // to ensure its enabled + bool dallas_parasite = false; + uint8_t led_gpio = 0; + bool low_clock = false; + uint8_t pbutton_gpio = false; + uint8_t solar_maxflow = 30; // MQTT uint16_t publish_time = 10; diff --git a/mock-api/server.js b/mock-api/server.js index 898d644e0..9f3666962 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -316,7 +316,9 @@ settings = { syslog_port: 514, master_thermostat: 0, shower_timer: true, - shower_alert: false, + shower_alert: true, + shower_alert_trigger: 7, + shower_alert_coldshot: 10, rx_gpio: 23, tx_gpio: 5, phy_type: 0, diff --git a/src/default_settings.h b/src/default_settings.h index b70fef920..134ff4dc8 100644 --- a/src/default_settings.h +++ b/src/default_settings.h @@ -64,6 +64,14 @@ #define EMSESP_DEFAULT_SHOWER_ALERT false #endif +#ifndef EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER +#define EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER 7 +#endif + +#ifndef EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT +#define EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT 10 +#endif + #ifndef EMSESP_DEFAULT_HIDE_LED #define EMSESP_DEFAULT_HIDE_LED false #endif diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index fe52772a7..3e9c728ab 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -25,7 +25,7 @@ namespace emsesp { // this includes commands since they can also be entities and visible in the web UI uint8_t EMSdevice::count_entities() { uint8_t count = 0; - for (auto & dv : devicevalues_) { + for (const auto & dv : devicevalues_) { if (!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE) && dv.hasValue()) { count++; } @@ -338,15 +338,17 @@ char * EMSdevice::show_telegram_handlers(char * result, const size_t len, const strlcat(result, Helpers::hextoa(tf.telegram_type_id_, true).c_str(), len); } } + if (handlers == Handlers::ALL || handlers == Handlers::IGNORED) { i = 0; - for (auto handlers : handlers_ignored_) { + for (auto h : handlers_ignored_) { if (i++ > 0) { strlcat(result, " ", len); } - strlcat(result, Helpers::hextoa(handlers).c_str(), len); + strlcat(result, Helpers::hextoa(h).c_str(), len); } } + return result; } @@ -667,7 +669,7 @@ void EMSdevice::generate_values_web(JsonObject & output) { // handle Booleans (true, false), use strings, no native true/false) if (dv.type == DeviceValueType::BOOL) { - bool value_b = (bool)*(uint8_t *)(dv.value_p); + auto value_b = (bool)*(uint8_t *)(dv.value_p); if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) { obj["v"] = value_b ? 1 : 0; } else { @@ -786,7 +788,7 @@ void EMSdevice::generate_values_web_all(JsonArray & output) { if (dv.hasValue()) { // handle Booleans (true, false), use strings, no native true/false) if (dv.type == DeviceValueType::BOOL) { - bool value_b = (bool)*(uint8_t *)(dv.value_p); + auto value_b = (bool)*(uint8_t *)(dv.value_p); if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) { obj["v"] = value_b ? 1 : 0; } else { @@ -890,17 +892,17 @@ void EMSdevice::mask_entity(const std::string & entity_id) { // populate a string vector with entities that have masks set void EMSdevice::getMaskedEntities(std::vector & entity_ids) { - for (auto & dv : devicevalues_) { + for (const auto & dv : devicevalues_) { std::string entity_name = dv.tag < DeviceValueTAG::TAG_HC1 ? read_flash_string(dv.short_name) : tag_to_string(dv.tag) + "/" + read_flash_string(dv.short_name); uint8_t mask = dv.state >> 4; + if (mask) { entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name); } } } - // builds json for a specific device value / entity // cmd is the endpoint or name of the device entity // returns false if failed, otherwise true @@ -995,7 +997,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8 case DeviceValueType::BOOL: if (Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { - bool value_b = (bool)*(uint8_t *)(dv.value_p); + auto value_b = (bool)*(uint8_t *)(dv.value_p); if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) { json[value] = value_b; } else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) { @@ -1128,7 +1130,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c // handle Booleans if (dv.type == DeviceValueType::BOOL && Helpers::hasValue(*(uint8_t *)(dv.value_p), EMS_VALUE_BOOL)) { // see how to render the value depending on the setting - bool value_b = (bool)*(uint8_t *)(dv.value_p); + auto value_b = (bool)*(uint8_t *)(dv.value_p); if (Mqtt::ha_enabled() && (output_target == OUTPUT_TARGET::MQTT)) { char s[7]; json[name] = Helpers::render_boolean(s, value_b); // for HA always render as string diff --git a/src/helpers.cpp b/src/helpers.cpp index 09c8792ee..8059b9164 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -464,91 +464,142 @@ uint32_t Helpers::abs(const int32_t i) { } // for booleans, use isBool true (EMS_VALUE_BOOL) -bool Helpers::hasValue(const uint8_t & v, const uint8_t isBool) { +bool Helpers::hasValue(const uint8_t & value, const uint8_t isBool) { if (isBool == EMS_VALUE_BOOL) { - return (v != EMS_VALUE_BOOL_NOTSET); + return (value != EMS_VALUE_BOOL_NOTSET); } - return (v != EMS_VALUE_UINT_NOTSET); + return (value != EMS_VALUE_UINT_NOTSET); } -bool Helpers::hasValue(const int8_t & v) { - return (v != EMS_VALUE_INT_NOTSET); +bool Helpers::hasValue(const int8_t & value) { + return (value != EMS_VALUE_INT_NOTSET); } -bool Helpers::hasValue(const char * v) { - if ((v == nullptr) || (strlen(v) == 0)) { +bool Helpers::hasValue(const char * value) { + if ((value == nullptr) || (strlen(value) == 0)) { return false; } - return (v[0] != '\0'); + return (value[0] != '\0'); } // for short these are typically 0x8300, 0x7D00 and sometimes 0x8000 -bool Helpers::hasValue(const int16_t & v) { - return (abs(v) < EMS_VALUE_USHORT_NOTSET); +bool Helpers::hasValue(const int16_t & value) { + return (abs(value) < EMS_VALUE_USHORT_NOTSET); } -bool Helpers::hasValue(const uint16_t & v) { - return (v < EMS_VALUE_USHORT_NOTSET); +bool Helpers::hasValue(const uint16_t & value) { + return (value < EMS_VALUE_USHORT_NOTSET); } -bool Helpers::hasValue(const uint32_t & v) { - return (v != EMS_VALUE_ULONG_NOTSET); +bool Helpers::hasValue(const uint32_t & value) { + return (value != EMS_VALUE_ULONG_NOTSET); } // checks if we can convert a char string to an int value -bool Helpers::value2number(const char * v, int & value, const int min, const int max) { - if ((v == nullptr) || (strlen(v) == 0)) { - value = 0; +bool Helpers::value2number(const char * value, int & value_i, const int min, const int max) { + if ((value == nullptr) || (strlen(value) == 0)) { + value_i = 0; return false; } - value = atoi(v); - if (value >= min && value <= max) { + value_i = atoi(value); + if (value_i >= min && value_i <= max) { return true; } return false; } // checks if we can convert a char string to a float value -bool Helpers::value2float(const char * v, float & value) { - value = 0; - if ((v == nullptr) || (strlen(v) == 0)) { +bool Helpers::value2float(const char * value, float & value_f) { + value_f = 0; + if ((value == nullptr) || (strlen(value) == 0)) { return false; } - if (v[0] == '-' || v[0] == '.' || (v[0] >= '0' && v[0] <= '9')) { - value = atof(v); + if (value[0] == '-' || value[0] == '.' || (value[0] >= '0' && value[0] <= '9')) { + value_f = atof(value); return true; } - if (v[0] == '+' && (v[1] == '.' || (v[1] >= '0' && v[1] <= '9'))) { - value = atof(v + 1); + if (value[0] == '+' && (value[1] == '.' || (value[1] >= '0' && value[1] <= '9'))) { + value_f = atof(value + 1); return true; } return false; } -bool Helpers::value2temperature(const char * v, float & value, bool relative) { - if (value2float(v, value)) { +bool Helpers::value2temperature(const char * value, float & value_f, bool relative) { + if (value2float(value, value_f)) { if (EMSESP::system_.fahrenheit()) { - value = relative ? (value / 1.8) : (value - 32) / 1.8; + value_f = relative ? (value_f / 1.8) : (value_f - 32) / 1.8; } return true; } return false; } -bool Helpers::value2temperature(const char * v, int & value, const bool relative, const int min, const int max) { - if (value2number(v, value, min, max)) { +bool Helpers::value2temperature(const char * value, int & value_i, const bool relative, const int min, const int max) { + if (value2number(value, value_i, min, max)) { if (EMSESP::system_.fahrenheit()) { - value = relative ? (value / 1.8) : (value - 32) / 1.8; + value_i = relative ? (value_i / 1.8) : (value_i - 32) / 1.8; } return true; } return false; } +// checks if we can convert a char string to a lowercase string +bool Helpers::value2string(const char * value, std::string & value_s) { + if ((value == nullptr) || (strlen(value) == 0)) { + value_s = std::string{}; + return false; + } + + value_s = toLower(value); + return true; +} + +// checks to see if a string (usually a command or payload cmd) looks like a boolean +// on, off, true, false, 1, 0 +bool Helpers::value2bool(const char * value, bool & value_b) { + if ((value == nullptr) || (strlen(value) == 0)) { + return false; + } + + std::string bool_str = toLower(value); // convert to lower case + + if ((bool_str == read_flash_string(F_(on))) || (bool_str == "1") || (bool_str == "true")) { + value_b = true; + return true; // is a bool + } + + if ((bool_str == read_flash_string(F_(off))) || (bool_str == "0") || (bool_str == "false")) { + value_b = false; + return true; // is a bool + } + + return false; // not a bool +} + +// checks to see if a string is member of a vector and return the index, also allow true/false for on/off +bool Helpers::value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const * strs) { + if ((value == nullptr) || (strlen(value) == 0)) { + return false; + } + std::string str = toLower(value); + + for (value_ui = 0; strs[value_ui]; value_ui++) { + std::string str1 = toLower(read_flash_string(strs[value_ui])); + if ((str1 != "") + && ((str1 == read_flash_string(F_(off)) && str == "false") || (str1 == read_flash_string(F_(on)) && str == "true") || (str == str1) + || (value[0] == ('0' + value_ui) && value[1] == '\0'))) { + return true; + } + } + return false; +} + // https://stackoverflow.com/questions/313970/how-to-convert-stdstring-to-lower-case std::string Helpers::toLower(std::string const & s) { std::string lc = s; @@ -562,55 +613,6 @@ std::string Helpers::toUpper(std::string const & s) { return lc; } -// checks if we can convert a char string to a lowercase string -bool Helpers::value2string(const char * v, std::string & value) { - if ((v == nullptr) || (strlen(v) == 0)) { - value = std::string{}; - return false; - } - value = toLower(v); - return true; -} - -// checks to see if a string (usually a command or payload cmd) looks like a boolean -// on, off, true, false, 1, 0 -bool Helpers::value2bool(const char * v, bool & value) { - if ((v == nullptr) || (strlen(v) == 0)) { - return false; - } - - std::string bool_str = toLower(v); // convert to lower case - - if ((bool_str == read_flash_string(F_(on))) || (bool_str == "1") || (bool_str == "true")) { - value = true; - return true; // is a bool - } - - if ((bool_str == read_flash_string(F_(off))) || (bool_str == "0") || (bool_str == "false")) { - value = false; - return true; // is a bool - } - - return false; // not a bool -} - -// checks to see if a string is member of a vector and return the index, also allow true/false for on/off -bool Helpers::value2enum(const char * v, uint8_t & value, const __FlashStringHelper * const * strs) { - if ((v == nullptr) || (strlen(v) == 0)) { - return false; - } - std::string str = toLower(v); - for (value = 0; strs[value]; value++) { - std::string str1 = toLower(read_flash_string(strs[value])); - if ((str1 != "") - && ((str1 == read_flash_string(F_(off)) && str == "false") || (str1 == read_flash_string(F_(on)) && str == "true") || (str == str1) - || (v[0] == ('0' + value) && v[1] == '\0'))) { - return true; - } - } - return false; -} - // replace char in char string void Helpers::replace_char(char * str, char find, char replace) { int i = 0; diff --git a/src/helpers.h b/src/helpers.h index 7068954fe..7ee8d9200 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -57,20 +57,20 @@ class Helpers { static std::string toUpper(std::string const & s); static void replace_char(char * str, char find, char replace); - static bool hasValue(const uint8_t & v, const uint8_t isBool = 0); - static bool hasValue(const int8_t & v); - static bool hasValue(const int16_t & v); - static bool hasValue(const uint16_t & v); - static bool hasValue(const uint32_t & v); - static bool hasValue(const char * v); + static bool hasValue(const uint8_t & value, const uint8_t isBool = 0); + static bool hasValue(const int8_t & value); + static bool hasValue(const int16_t & value); + static bool hasValue(const uint16_t & value); + static bool hasValue(const uint32_t & value); + static bool hasValue(const char * value); - static bool value2number(const char * v, int & value, const int min = -2147483648, const int max = 2147483647); - static bool value2float(const char * v, float & value); - static bool value2bool(const char * v, bool & value); - static bool value2string(const char * v, std::string & value); - static bool value2enum(const char * v, uint8_t & value, const __FlashStringHelper * const * strs); - static bool value2temperature(const char * v, float & value, bool relative = false); - static bool value2temperature(const char * v, int & value, const bool relative = false, const int min = -2147483648, const int max = 2147483647); + static bool value2number(const char * value, int & value_i, const int min = -2147483648, const int max = 2147483647); + static bool value2float(const char * value, float & value_f); + static bool value2bool(const char * value, bool & value_b); + static bool value2string(const char * value, std::string & value_s); + static bool value2enum(const char * value, uint8_t & value_ui, const __FlashStringHelper * const * strs); + static bool value2temperature(const char * value, float & value_f, bool relative = false); + static bool value2temperature(const char * value, int & value_i, const bool relative = false, const int min = -2147483648, const int max = 2147483647); #ifdef EMSESP_STANDALONE static char * ultostr(char * ptr, uint32_t value, const uint8_t base); diff --git a/src/shower.cpp b/src/shower.cpp index f9c8a2e8e..8f024dfa4 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -24,8 +24,10 @@ uuid::log::Logger Shower::logger_{F_(shower), uuid::log::Facility::CONSOLE}; void Shower::start() { EMSESP::webSettingsService.read([&](WebSettings & settings) { - shower_timer_ = settings.shower_timer; - shower_alert_ = settings.shower_alert; + shower_timer_ = settings.shower_timer; + shower_alert_ = settings.shower_alert; + shower_alert_trigger_ = settings.shower_alert_trigger * 60000; // convert from minutes + shower_alert_coldshot_ = settings.shower_alert_coldshot * 1000; // convert from seconds }); set_shower_state(false, true); // turns shower to off and creates HA topic if not already done @@ -59,7 +61,7 @@ void Shower::loop() { LOG_DEBUG(F("[Shower] hot water still running, starting shower timer")); } // check if the shower has been on too long - else if ((time_now - timer_start_) > SHOWER_MAX_DURATION) { + else if ((time_now - timer_start_) > shower_alert_trigger_) { shower_alert_start(); } } @@ -95,7 +97,7 @@ void Shower::loop() { // at this point we're in the shower cold shot (doing_cold_shot_ == true) // keep repeating until the time is up - if ((time_now - alert_timer_start_) > SHOWER_COLDSHOT_DURATION) { + if ((time_now - alert_timer_start_) > shower_alert_coldshot_) { shower_alert_stop(); } } diff --git a/src/shower.h b/src/shower.h index dc9dafda4..809a18031 100644 --- a/src/shower.h +++ b/src/shower.h @@ -49,11 +49,9 @@ class Shower { private: static uuid::log::Logger logger_; - static constexpr uint32_t SHOWER_PAUSE_TIME = 15000; // in ms. 15 seconds, max time if water is switched off & on during a shower - static constexpr uint32_t SHOWER_MIN_DURATION = 120000; // in ms. 2 minutes, before recognizing its a shower - static constexpr uint32_t SHOWER_OFFSET_TIME = 5000; // in ms. 5 seconds grace time, to calibrate actual time under the shower - static constexpr uint32_t SHOWER_COLDSHOT_DURATION = 10000; // 10 seconds for cold water before turning back hot water - static constexpr uint32_t SHOWER_MAX_DURATION = 420000; // in ms. 7 minutes, before trigger a shot of cold water + static constexpr uint32_t SHOWER_PAUSE_TIME = 15000; // in ms. 15 seconds, max time if water is switched off & on during a shower + static constexpr uint32_t SHOWER_MIN_DURATION = 120000; // in ms. 2 minutes, before recognizing its a shower + static constexpr uint32_t SHOWER_OFFSET_TIME = 5000; // in ms. 5 seconds grace time, to calibrate actual time under the shower void publish_shower_data() const; void shower_alert_start(); @@ -61,6 +59,8 @@ class Shower { bool shower_timer_; // true if we want to report back on shower times bool shower_alert_; // true if we want the alert of cold water + uint32_t shower_alert_trigger_; // default 7 minutes, before trigger a shot of cold water + uint32_t shower_alert_coldshot_; // default 10 seconds for cold water before turning back hot water bool ha_configdone_ = false; // for HA MQTT Discovery bool shower_state_; uint32_t timer_start_; // ms diff --git a/src/system.cpp b/src/system.cpp index 6843b8a34..52008992d 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -998,6 +998,10 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject & node["shower_timer"] = settings.shower_timer; node["shower_alert"] = settings.shower_alert; + if (settings.shower_alert) { + node["shower_alert_coldshot"] = settings.shower_alert_coldshot / 1000; // seconds + node["shower_alert_trigger"] = settings.shower_alert_trigger / 60000; // minutes + } node["rx_gpio"] = settings.rx_gpio; node["tx_gpio"] = settings.tx_gpio; diff --git a/src/version.h b/src/version.h index 7cc3e2109..879950758 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.4.0b14a0" +#define EMSESP_APP_VERSION "3.4.0b15idf4" diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 0d9347ce5..c32bed325 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -137,8 +137,9 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) { for (const auto & sensor : EMSESP::analogsensor_.sensors()) { // don't send if it's marked for removal if (sensor.type() != AnalogSensor::AnalogType::MARK_DELETED) { + count++; JsonObject obj = analogs.createNestedObject(); - obj["id"] = Helpers::smallitoa(buffer, ++count); // needed for sorting table + obj["id"] = Helpers::smallitoa(buffer, count); // needed for sorting table obj["g"] = sensor.gpio(); obj["n"] = sensor.name(); obj["u"] = sensor.uom(); diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index a5d9ef98d..dca3ab72f 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -37,41 +37,43 @@ WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, Securit } void WebSettings::read(WebSettings & settings, JsonObject & root) { - root["tx_mode"] = settings.tx_mode; - root["ems_bus_id"] = settings.ems_bus_id; - root["syslog_enabled"] = settings.syslog_enabled; - root["syslog_level"] = settings.syslog_level; - root["trace_raw"] = settings.trace_raw; - root["syslog_mark_interval"] = settings.syslog_mark_interval; - root["syslog_host"] = settings.syslog_host; - root["syslog_port"] = settings.syslog_port; - root["master_thermostat"] = settings.master_thermostat; - root["shower_timer"] = settings.shower_timer; - root["shower_alert"] = settings.shower_alert; - root["rx_gpio"] = settings.rx_gpio; - root["tx_gpio"] = settings.tx_gpio; - root["dallas_gpio"] = settings.dallas_gpio; - root["dallas_parasite"] = settings.dallas_parasite; - root["led_gpio"] = settings.led_gpio; - root["hide_led"] = settings.hide_led; - root["low_clock"] = settings.low_clock; - root["telnet_enabled"] = settings.telnet_enabled; - root["notoken_api"] = settings.notoken_api; - root["readonly_mode"] = settings.readonly_mode; - root["analog_enabled"] = settings.analog_enabled; - root["pbutton_gpio"] = settings.pbutton_gpio; - root["solar_maxflow"] = settings.solar_maxflow; - root["board_profile"] = settings.board_profile; - root["fahrenheit"] = settings.fahrenheit; - root["bool_format"] = settings.bool_format; - root["enum_format"] = settings.enum_format; - root["weblog_level"] = settings.weblog_level; - root["weblog_buffer"] = settings.weblog_buffer; - root["weblog_compact"] = settings.weblog_compact; - root["phy_type"] = settings.phy_type; - root["eth_power"] = settings.eth_power; - root["eth_phy_addr"] = settings.eth_phy_addr; - root["eth_clock_mode"] = settings.eth_clock_mode; + root["tx_mode"] = settings.tx_mode; + root["ems_bus_id"] = settings.ems_bus_id; + root["syslog_enabled"] = settings.syslog_enabled; + root["syslog_level"] = settings.syslog_level; + root["trace_raw"] = settings.trace_raw; + root["syslog_mark_interval"] = settings.syslog_mark_interval; + root["syslog_host"] = settings.syslog_host; + root["syslog_port"] = settings.syslog_port; + root["master_thermostat"] = settings.master_thermostat; + root["shower_timer"] = settings.shower_timer; + root["shower_alert"] = settings.shower_alert; + root["shower_alert_coldshot"] = settings.shower_alert_coldshot; + root["shower_alert_trigger"] = settings.shower_alert_trigger; + root["rx_gpio"] = settings.rx_gpio; + root["tx_gpio"] = settings.tx_gpio; + root["dallas_gpio"] = settings.dallas_gpio; + root["dallas_parasite"] = settings.dallas_parasite; + root["led_gpio"] = settings.led_gpio; + root["hide_led"] = settings.hide_led; + root["low_clock"] = settings.low_clock; + root["telnet_enabled"] = settings.telnet_enabled; + root["notoken_api"] = settings.notoken_api; + root["readonly_mode"] = settings.readonly_mode; + root["analog_enabled"] = settings.analog_enabled; + root["pbutton_gpio"] = settings.pbutton_gpio; + root["solar_maxflow"] = settings.solar_maxflow; + root["board_profile"] = settings.board_profile; + root["fahrenheit"] = settings.fahrenheit; + root["bool_format"] = settings.bool_format; + root["enum_format"] = settings.enum_format; + root["weblog_level"] = settings.weblog_level; + root["weblog_buffer"] = settings.weblog_buffer; + root["weblog_compact"] = settings.weblog_compact; + root["phy_type"] = settings.phy_type; + root["eth_power"] = settings.eth_power; + root["eth_phy_addr"] = settings.eth_phy_addr; + root["eth_clock_mode"] = settings.eth_clock_mode; } // call on initialization and also when settings are updated via web or console @@ -152,6 +154,12 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) prev = settings.shower_alert; settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT; check_flag(prev, settings.shower_alert, ChangeFlags::SHOWER); + prev = settings.shower_alert_trigger; + settings.shower_alert_trigger = root["shower_alert_trigger"] | EMSESP_DEFAULT_SHOWER_ALERT_TRIGGER; + check_flag(prev, settings.shower_alert_trigger, ChangeFlags::SHOWER); + prev = settings.shower_alert_coldshot; + settings.shower_alert_coldshot = root["shower_alert_coldshot"] | EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT; + check_flag(prev, settings.shower_alert_coldshot, ChangeFlags::SHOWER); // led prev = settings.led_gpio; diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index 5ff6ef1c9..16bf3aa60 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -34,6 +34,8 @@ class WebSettings { uint8_t master_thermostat; bool shower_timer; bool shower_alert; + uint8_t shower_alert_trigger; + uint8_t shower_alert_coldshot; bool syslog_enabled; int8_t syslog_level; // uuid::log::Level uint32_t syslog_mark_interval;