diff --git a/src/core/system.cpp b/src/core/system.cpp index 44ae46812..49af7ca33 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -19,7 +19,6 @@ #include "system.h" #include "emsesp.h" // for send_raw_telegram() command - #ifndef EMSESP_STANDALONE #include "esp_ota_ops.h" #endif @@ -407,51 +406,47 @@ void System::syslog_init() { } // read specific major system settings to store locally for faster access -// this also verifies all the assigned GPIOs are valid -void System::get_settings() { - EMSESP::webSettingsService.read([&](WebSettings & settings) { - version_ = settings.version; +void System::store_settings(WebSettings & settings) { + version_ = settings.version; - // GPIOs - rx_gpio_ = settings.rx_gpio; - tx_gpio_ = settings.tx_gpio; - pbutton_gpio_ = settings.pbutton_gpio; - dallas_gpio_ = settings.dallas_gpio; - led_gpio_ = settings.led_gpio; + rx_gpio_ = settings.rx_gpio; + tx_gpio_ = settings.tx_gpio; + pbutton_gpio_ = settings.pbutton_gpio; + dallas_gpio_ = settings.dallas_gpio; + led_gpio_ = settings.led_gpio; - analog_enabled_ = settings.analog_enabled; - low_clock_ = settings.low_clock; - hide_led_ = settings.hide_led; - led_type_ = settings.led_type; - board_profile_ = settings.board_profile; - telnet_enabled_ = settings.telnet_enabled; + analog_enabled_ = settings.analog_enabled; + low_clock_ = settings.low_clock; + hide_led_ = settings.hide_led; + led_type_ = settings.led_type; + board_profile_ = settings.board_profile; + telnet_enabled_ = settings.telnet_enabled; - modbus_enabled_ = settings.modbus_enabled; - modbus_port_ = settings.modbus_port; - modbus_max_clients_ = settings.modbus_max_clients; - modbus_timeout_ = settings.modbus_timeout; + modbus_enabled_ = settings.modbus_enabled; + modbus_port_ = settings.modbus_port; + modbus_max_clients_ = settings.modbus_max_clients; + modbus_timeout_ = settings.modbus_timeout; - tx_mode_ = settings.tx_mode; - syslog_enabled_ = settings.syslog_enabled; - syslog_level_ = settings.syslog_level; - syslog_mark_interval_ = settings.syslog_mark_interval; - syslog_host_ = settings.syslog_host; - syslog_port_ = settings.syslog_port; + tx_mode_ = settings.tx_mode; + syslog_enabled_ = settings.syslog_enabled; + syslog_level_ = settings.syslog_level; + syslog_mark_interval_ = settings.syslog_mark_interval; + syslog_host_ = settings.syslog_host; + syslog_port_ = settings.syslog_port; - fahrenheit_ = settings.fahrenheit; - bool_format_ = settings.bool_format; - bool_dashboard_ = settings.bool_dashboard; - enum_format_ = settings.enum_format; - readonly_mode_ = settings.readonly_mode; + fahrenheit_ = settings.fahrenheit; + bool_format_ = settings.bool_format; + bool_dashboard_ = settings.bool_dashboard; + enum_format_ = settings.enum_format; + readonly_mode_ = settings.readonly_mode; - phy_type_ = settings.phy_type; - eth_power_ = settings.eth_power; - eth_phy_addr_ = settings.eth_phy_addr; - eth_clock_mode_ = settings.eth_clock_mode; + phy_type_ = settings.phy_type; + eth_power_ = settings.eth_power; + eth_phy_addr_ = settings.eth_phy_addr; + eth_clock_mode_ = settings.eth_clock_mode; - locale_ = settings.locale; - developer_mode_ = settings.developer_mode; - }); + locale_ = settings.locale; + developer_mode_ = settings.developer_mode; } // Starts up the UART Serial bridge @@ -1075,7 +1070,7 @@ void System::show_system(uuid::console::Shell & shell) { shell.printfln(" BSSID: %s", WiFi.BSSIDstr().c_str()); shell.printfln(" RSSI: %d dBm (%d %%)", WiFi.RSSI(), wifi_quality(WiFi.RSSI())); char result[10]; - shell.printfln(" TxPower: %s dBm", emsesp::Helpers::render_value(result, (double)(WiFi.getTxPower() / 4), 1)); + shell.printfln(" TxPower: %s dBm", Helpers::render_value(result, (double)(WiFi.getTxPower() / 4), 1)); shell.printfln(" MAC address: %s", WiFi.macAddress().c_str()); shell.printfln(" Hostname: %s", WiFi.getHostname()); shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str()); @@ -1235,7 +1230,7 @@ bool System::check_upgrade(bool factory_settings) { version::Semver200_version settings_version(settingsVersion); if (!missing_version) { - LOG_DEBUG("Checking for version upgrades (settings file is from v%d.%d.%d-%s)", + LOG_DEBUG("Checking for version upgrades (settings file is v%d.%d.%d-%s)", settings_version.major(), settings_version.minor(), settings_version.patch(), @@ -1905,15 +1900,12 @@ bool System::command_test(const char * value, const int8_t id) { // takes a board profile and populates a data array with GPIO configurations // returns false if profile is unknown // -// data = led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode, led_type +// 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type // -// clock modes: -// 0 = RMII clock input to GPIO0 -// 1 = RMII clock output from GPIO0 -// 2 = RMII clock output from GPIO16 -// 3 = RMII clock output from GPIO17, for 50hz inverted clock bool System::load_board_profile(std::vector & data, const std::string & board_profile) { - if (board_profile == "S32") { + if (board_profile == EMSESP_DEFAULT_BOARD_PROFILE) { + return false; // unknown, return false + } else if (board_profile == "S32") { data = {2, 18, 23, 5, 0, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0}; // BBQKees Gateway S32 } else if (board_profile == "E32") { data = {2, 4, 5, 17, 33, PHY_type::PHY_TYPE_LAN8720, 16, 1, 0, 0}; // BBQKees Gateway E32 @@ -1951,16 +1943,31 @@ bool System::load_board_profile(std::vector & data, const std::string & (int8_t)EMSESP::system_.tx_gpio_, (int8_t)EMSESP::system_.pbutton_gpio_, (int8_t)EMSESP::system_.phy_type_, - EMSESP::system_.eth_power_, + (int8_t)EMSESP::system_.eth_power_, (int8_t)EMSESP::system_.eth_phy_addr_, (int8_t)EMSESP::system_.eth_clock_mode_, (int8_t)EMSESP::system_.led_type_}; } else { - LOG_DEBUG("Couldn't identify board profile %s", board_profile.c_str()); return false; // unknown, return false } - // LOG_DEBUG("Found data for board profile %s", board_profile.c_str()); + // print out data to log with descriptions + LOG_DEBUG( + "load_board_profile: %s, led_gpio_=%d, dallas_gpio_=%d, rx_gpio_=%d, tx_gpio_=%d, pbutton_gpio_=%d, phy_type_=%d, eth_power_=%d, eth_phy_addr_=%d, " + "eth_clock_mode_=%d, " + "led_type_=%d", + board_profile.c_str(), + data[0], + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + data[8], + data[9]); + return true; } @@ -2190,7 +2197,6 @@ bool System::uploadFirmwareURL(const char * url) { // we're about to start the upload, set the status so the Web System Monitor spots it EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING); - // TODO do we need to stop the UART first with EMSuart::stop() ? // set a callback so we can monitor progress in the WebUI Update.onProgress([](size_t progress, size_t total) { EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING + (progress * 100 / total)); }); @@ -2285,7 +2291,7 @@ bool System::command_read(const char * value, const int8_t id) { // set the system status code - SYSTEM_STATUS in system.h void System::systemStatus(uint8_t status_code) { systemStatus_ = status_code; - // LOG_DEBUG("Setting System status code %d", status_code); + LOG_DEBUG("Setting System status code %d", status_code); } uint8_t System::systemStatus() { @@ -2330,10 +2336,10 @@ std::vector System::string_range_to_vector(const std::string & range) { // initialize a list of valid GPIOs based on the ESP32 board // notes: -// - we allow 0, which is used on some board for the button +// - we always allow 0, which is used to indicate Dallas or LED is disabled // - we also allow input only pins are accepted (34-39) on some boards // - and allow pins 33-38 for octal SPI for 32M vchip version on some boards -void System::set_valid_system_gpios(bool exclude_used) { +void System::set_valid_system_gpios() { valid_system_gpios_.clear(); // reset system list used_gpios_.clear(); // reset used list @@ -2350,9 +2356,9 @@ void System::set_valid_system_gpios(bool exclude_used) { #else #endif + // if psram is enabled remove pins 16 and 17 from the list, if set #if CONFIG_IDF_TARGET_ESP32 if (ESP.getPsramSize() > 0) { - // if psram is enabled remove pins 16 and 17 from the list, if set valid_system_gpios_.erase(std::remove(valid_system_gpios_.begin(), valid_system_gpios_.end(), 16), valid_system_gpios_.end()); valid_system_gpios_.erase(std::remove(valid_system_gpios_.begin(), valid_system_gpios_.end(), 17), valid_system_gpios_.end()); } @@ -2360,26 +2366,31 @@ void System::set_valid_system_gpios(bool exclude_used) { } // check if a pin is valid ESP32 pin and if not already used, add to the used gpio list -bool System::check_valid_gpio(uint8_t pin, const char * source_name) { - bool ok = false; - // check if we're allowed to use this pin +// return false if not allowed or already used +bool System::add_gpio(uint8_t pin, const char * source_name) { + // check if this is a valid user GPIO if (std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin) != valid_system_gpios_.end()) { - // It's OK, now check if it's already in the used list + // It's valid now check if it's already in the used list if (std::find(used_gpios_.begin(), used_gpios_.end(), pin) != used_gpios_.end()) { - LOG_DEBUG("GPIO %d for %s is already used", pin, source_name); - ok = false; // Pin is already used, not OK - - } else { - ok = true; + LOG_DEBUG("GPIO %d for %s is already in use", pin, source_name); + return false; // Pin is already used } - } - if (ok) { - LOG_DEBUG("Adding GPIO %d for %s to used list", pin, source_name); - used_gpios_.push_back(pin); // add to used list } else { + // not valid LOG_DEBUG("GPIO %d for %s is not valid", pin, source_name); + return false; } - return ok; + LOG_DEBUG("Adding GPIO %d for %s to used gpio list", pin, source_name); + used_gpios_.push_back(pin); // add to used list + + return true; +} + +// remove a gpio from both valid and used lists +void System::remove_gpio(uint8_t pin) { + LOG_DEBUG("Removing GPIO %d from valid and used gpio lists", pin); + valid_system_gpios_.erase(std::remove(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin), valid_system_gpios_.end()); + used_gpios_.erase(std::remove(used_gpios_.begin(), used_gpios_.end(), pin), used_gpios_.end()); } // return a list of valid and unused GPIOs still available for use @@ -2394,37 +2405,3 @@ std::vector System::valid_gpio_list() { } } // namespace emsesp - - -/* - - // if ethernet is enabled, remove pins 21 and 22 (I2C) and 1 (ETH.power) - if ((EMSESP::system_.ethernet_connected() || EMSESP::system_.phy_type_ != PHY_type::PHY_TYPE_NONE)) { - gpios_to_remove.push_back(21); - gpios_to_remove.push_back(22); - gpios_to_remove.push_back(1); - } - - // filter out GPIOs already used in application settings and analog sensors, if enabled - // if dallas_gpio or led_gpio is disabled (0), don't remove it from the list (as it could be gpio 0 and valid) - if (exclude_used) { - // application settings - gpios_to_remove.push_back(EMSESP::system_.pbutton_gpio_); - if (EMSESP::system_.led_gpio_ != 0) { - gpios_to_remove.push_back(EMSESP::system_.led_gpio_); - } - if (EMSESP::system_.dallas_gpio_ != 0) { - gpios_to_remove.push_back(EMSESP::system_.dallas_gpio_); - } - gpios_to_remove.push_back(EMSESP::system_.rx_gpio_); - gpios_to_remove.push_back(EMSESP::system_.tx_gpio_); - - // analog sensors - for (const auto & sensor : EMSESP::analogsensor_.sensors()) { - if (std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), sensor.gpio()) != valid_system_gpios_.end()) { - gpios_to_remove.push_back(sensor.gpio()); - } - } - } - -*/ diff --git a/src/core/system.h b/src/core/system.h index 1284e8536..51306de40 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -104,11 +104,12 @@ class System { void system_restart(const char * partition = nullptr); void show_mem(const char * note); - void get_settings(); + void store_settings(class WebSettings & settings); void syslog_init(); bool check_upgrade(bool factory_settings); bool check_restore(); void heartbeat_json(JsonObject output); + void send_heartbeat(); void send_info_mqtt(); @@ -142,7 +143,7 @@ class System { static void extractSettings(const char * filename, const char * section, JsonObject output); static bool saveSettings(const char * filename, const char * section, JsonObject input); - static bool check_valid_gpio(uint8_t pin, const char * source_name); + static bool add_gpio(uint8_t pin, const char * source_name); static std::vector valid_gpio_list(); static bool load_board_profile(std::vector & data, const std::string & board_profile); @@ -344,7 +345,7 @@ class System { test_set_all_active_ = n; } - static void set_valid_system_gpios(bool exclude_used = false); + static void set_valid_system_gpios(); #if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 float temperature() { @@ -352,6 +353,8 @@ class System { } #endif + static void remove_gpio(uint8_t pin); // remove a gpio from both valid and used lists + private: static uuid::log::Logger logger_; diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 172c9f779..3f5e6d0eb 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -118,7 +118,7 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c for (const JsonObject analogJson : analogJsons) { // create each of the sensor, overwriting any previous settings // if the gpio is invalid skip the sensor - if (!EMSESP::system_.check_valid_gpio(analogJson["gpio"].as(), "Analog Sensor")) { + if (!EMSESP::system_.add_gpio(analogJson["gpio"].as(), "Analog Sensor")) { EMSESP::logger().warning("Analog sensor: Invalid GPIO %d for %s. Skipping.", analogJson["gpio"].as(), analogJson["name"].as().c_str()); @@ -176,8 +176,7 @@ void WebCustomizationService::reset_customization(AsyncWebServerRequest * reques AsyncWebServerResponse * response = request->beginResponse(200); request->send(response); - emsesp::EMSESP::system_.systemStatus( - emsesp::SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor + EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_PENDING_RESTART); // will be handled by the main loop. We use pending for the Web's SystemMonitor return; #endif diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index f91d5789d..cf0552d03 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -85,277 +85,173 @@ void WebSettings::read(WebSettings & settings, JsonObject root) { root["developer_mode"] = settings.developer_mode; } -// call on initialization and also when settings are updated via web or console +// call on initialization and also when settings are updated/saved via web or console StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) { - // load the version from the settings config. This can be blank and later used in System::check_upgrade() - settings.version = root["version"] | EMSESP_DEFAULT_VERSION; - -#if defined(EMSESP_DEBUG) - EMSESP::logger().debug("NVS boot value=[%s], board profile=[%s], EMSESP_DEFAULT_BOARD_PROFILE=[%s]", - EMSESP::nvs_.getString("boot").c_str(), - root["board_profile"] | "", - EMSESP_DEFAULT_BOARD_PROFILE); -#endif - - // get the current board profile saved in the settings file - String org_board_profile = settings.board_profile; - settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE; // this is set at compile time in platformio.ini, other it's "default" - String old_board_profile = settings.board_profile; - - // The optional NVS boot value has priority and overrides any board_profile setting. - // We only do this for BBQKees boards - // Note 1: we never set the NVS boot value in the code - this is done on initial pre-loading - // Note 2: The board profile is dynamically changed for the session, but the value in the settings file on the FS remains untouched - if (EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::MFG).startsWith("BBQKees")) { - String bbq_board = EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::BOARD); - if (!bbq_board.isEmpty()) { -#if defined(EMSESP_DEBUG) - EMSESP::logger().info("Overriding board profile with fuse value %s", bbq_board.c_str()); -#endif - settings.board_profile = bbq_board; - } - } - - // load the board profile from the settings, if it's not "default" - std::vector data; // // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode - if (settings.board_profile != "default") { - if (System::load_board_profile(data, settings.board_profile.c_str())) { - if (settings.board_profile == "CUSTOM") { // read pins, fallback to S32 values - data = {(int8_t)(root["led_gpio"] | 2), - (int8_t)(root["dallas_gpio"] | 18), - (int8_t)(root["rx_gpio"] | 23), - (int8_t)(root["tx_gpio"] | 5), - (int8_t)(root["pbutton_gpio"] | 0), - (int8_t)(root["phy_type"] | PHY_type::PHY_TYPE_NONE), - (int8_t)(root["eth_power"] | 0), - (int8_t)(root["eth_phy_addr"] | 0), - (int8_t)(root["eth_clock_mode"] | 0), -#if defined(ARDUINO_LOLIN_C3_MINI) && !defined(BOARD_C3_MINI_V1) - (int8_t)(root["led_type"] | 1)}; -#else - (int8_t)(root["led_type"] | 0)}; // 0 = LED, 1 = RGB-LED -#endif - } - // check valid pins for this board profile - if (!EMSESP::system_.check_valid_gpio(data[0], "LED") || !EMSESP::system_.check_valid_gpio(data[1], "Dallas") - || !EMSESP::system_.check_valid_gpio(data[2], "UART Rx") || !EMSESP::system_.check_valid_gpio(data[3], "UART Tx") - || !EMSESP::system_.check_valid_gpio(data[4], "Button") || !EMSESP::system_.check_valid_gpio(data[6], "Ethernet")) { - // set status - EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO); - settings.board_profile = "default"; // reset to factory default - } - } else { - settings.board_profile = "default"; // can't find profile, use "default" - } - } - - // still don't have a valid board profile. Let's see if we can determine one - if (settings.board_profile == "default") { -#if defined(EMSESP_DEBUG) - EMSESP::logger().debug("Trying to detect board and set board profile..."); -#endif - -#if defined(EMSESP_STANDALONE) - settings.board_profile = "S32"; -#elif CONFIG_IDF_TARGET_ESP32 - // check for no PSRAM, could be a E32 or S32? - if (!ESP.getPsramSize()) { -#if ESP_ARDUINO_VERSION_MAJOR < 3 - if (ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN)) { -#else - if (ETH.begin(ETH_PHY_LAN8720, 1, 23, 18, 16, ETH_CLOCK_GPIO0_IN)) { -#endif - settings.board_profile = "E32"; // Ethernet without PSRAM - } else { - settings.board_profile = "S32"; // ESP32 standard WiFi without PSRAM - } - } else { -// check for boards with PSRAM, could be a E32V2 otherwise default back to the S32 -#if ESP_ARDUINO_VERSION_MAJOR < 3 - if (ETH.begin(0, 15, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_OUT)) { -#else - if (ETH.begin(ETH_PHY_LAN8720, 0, 23, 18, 15, ETH_CLOCK_GPIO0_OUT)) { -#endif - - if (analogReadMilliVolts(39) > 700) { // core voltage > 2.6V - settings.board_profile = "E32V2_2"; // Ethernet, PSRAM, internal sensors - } else { - settings.board_profile = "E32V2"; // Ethernet and PSRAM - } - } else { - settings.board_profile = "S32"; // ESP32 standard WiFi with PSRAM - } - } - - // override if we know the target from the build config like C3, S2, S3 etc.. -#elif CONFIG_IDF_TARGET_ESP32C3 - settings.board_profile = "C3MINI"; -#elif CONFIG_IDF_TARGET_ESP32S2 - settings.board_profile = "S2MINI"; -#elif CONFIG_IDF_TARGET_ESP32S3 - settings.board_profile = "S32S3"; // BBQKees Gateway S3 -#endif - // apply the new board profile setting - System::load_board_profile(data, settings.board_profile.c_str()); - } - - int prev; + // make a copy of the settings to compare to later + WebSettings original_settings = settings; reset_flags(); - // check if board profile has changed - if (org_board_profile != settings.board_profile) { - if (org_board_profile.isEmpty()) { - EMSESP::logger().info("Setting board profile to %s", settings.board_profile.c_str()); - } else { - EMSESP::logger().info("Setting board profile to %s (was %s)", settings.board_profile.c_str(), org_board_profile.c_str()); - } + settings.version = root["version"] | EMSESP_DEFAULT_VERSION; // save the version, we use it later in System::check_upgrade() + settings.board_profile = root["board_profile"] | EMSESP_DEFAULT_BOARD_PROFILE; + + // get current values that are related to the board profile + settings.led_gpio = root["led_gpio"]; + settings.dallas_gpio = root["dallas_gpio"]; + settings.rx_gpio = root["rx_gpio"]; + settings.tx_gpio = root["tx_gpio"]; + settings.pbutton_gpio = root["pbutton_gpio"]; + settings.phy_type = root["phy_type"]; + settings.eth_power = root["eth_power"]; + settings.eth_phy_addr = root["eth_phy_addr"]; + settings.eth_clock_mode = root["eth_clock_mode"]; + settings.led_type = root["led_type"]; // 1 = RGB-LED + +#if defined(EMSESP_DEBUG) + EMSESP::logger().debug("NVS boot value=[%s], current board_profile=[%s], new board_profile=[%s]", + EMSESP::nvs_.getString("boot").c_str(), + original_settings.board_profile.c_str(), + settings.board_profile.c_str()); +#endif + + // TODO: make sure board profile is loaded when EMS-ESP starts. it will because original_settings.board_profile is empty when EMS-ESP starts + // see if the user has changed the board profile + // this will set: led_gpio, dallas_gpio, rx_gpio, tx_gpio, pbutton_gpio, phy_type, eth_power, eth_phy_addr, eth_clock_mode, led_type + if (original_settings.board_profile != settings.board_profile) { + set_board_profile(settings); add_flags(ChangeFlags::RESTART); } - // pins - prev = settings.led_gpio; - settings.led_gpio = data[0]; - check_flag(prev, settings.led_gpio, ChangeFlags::LED); - prev = settings.dallas_gpio; - settings.dallas_gpio = data[1]; - check_flag(prev, settings.dallas_gpio, ChangeFlags::TEMPERATURE_SENSOR); - prev = settings.rx_gpio; - settings.rx_gpio = data[2]; - check_flag(prev, settings.rx_gpio, ChangeFlags::RESTART); - prev = settings.tx_gpio; - settings.tx_gpio = data[3]; - check_flag(prev, settings.tx_gpio, ChangeFlags::RESTART); - prev = settings.pbutton_gpio; - settings.pbutton_gpio = data[4]; - check_flag(prev, settings.pbutton_gpio, ChangeFlags::BUTTON); - prev = settings.phy_type; - settings.phy_type = data[5]; - check_flag(prev, settings.phy_type, ChangeFlags::RESTART); - prev = settings.eth_power; - settings.eth_power = data[6]; - check_flag(prev, settings.eth_power, ChangeFlags::RESTART); - prev = settings.eth_phy_addr; - settings.eth_phy_addr = data[7]; - check_flag(prev, settings.eth_phy_addr, ChangeFlags::RESTART); - prev = settings.eth_clock_mode; - settings.eth_clock_mode = data[8]; - check_flag(prev, settings.eth_clock_mode, ChangeFlags::RESTART); - prev = settings.led_type; - settings.led_type = data[9]; - check_flag(prev, settings.led_type, ChangeFlags::LED); + // if any of the GPIOs have changed and re-validate them + bool valid_gpios = true; - // tx_mode, rx and tx pins - prev = settings.tx_mode; + if (check_flag(original_settings.led_gpio, settings.led_gpio, ChangeFlags::LED)) { + if (settings.led_gpio != 0) { // 0 means disabled + valid_gpios = valid_gpios && EMSESP::system_.add_gpio(settings.led_gpio, "LED"); + } + } + + if (check_flag(original_settings.dallas_gpio, settings.dallas_gpio, ChangeFlags::TEMPERATURE_SENSOR)) { + if (settings.dallas_gpio != 0) { // 0 means disabled + valid_gpios = valid_gpios && EMSESP::system_.add_gpio(settings.dallas_gpio, "Dallas"); + } + } + + if (check_flag(original_settings.rx_gpio, settings.rx_gpio, ChangeFlags::RESTART)) { + valid_gpios = valid_gpios && EMSESP::system_.add_gpio(settings.rx_gpio, "UART Rx"); + } + + if (check_flag(original_settings.tx_gpio, settings.tx_gpio, ChangeFlags::RESTART)) { + valid_gpios = valid_gpios && EMSESP::system_.add_gpio(settings.tx_gpio, "UART Tx"); + } + + if (check_flag(original_settings.pbutton_gpio, settings.pbutton_gpio, ChangeFlags::BUTTON)) { + valid_gpios = valid_gpios && EMSESP::system_.add_gpio(settings.pbutton_gpio, "Button"); + } + + if (check_flag(original_settings.phy_type, settings.phy_type, ChangeFlags::RESTART)) { + // ETH has changed, so we need to check the ethernet pins. Only if ETH is being used. + if (settings.phy_type != PHY_type::PHY_TYPE_NONE) { + if (settings.eth_power != -1) { + // Ethernet Power -1 means disabled + valid_gpios = valid_gpios && EMSESP::system_.add_gpio(settings.eth_power, "Ethernet Power"); + } + // all valid so far, now remove the ethernet pins from list, regardless of whether the GPIOs are valid or not + EMSESP::system_.remove_gpio(23); // MDC + EMSESP::system_.remove_gpio(18); // MDIO + EMSESP::system_.remove_gpio(0); // ETH.clock input + EMSESP::system_.remove_gpio(16); // ETH.clock output + EMSESP::system_.remove_gpio(17); // ETH.clock output + EMSESP::system_.remove_gpio(21); // I2C SDA + EMSESP::system_.remove_gpio(22); // I2C SCL + } + } + + // check if the LED type, eth_phy_addr or eth_clock_mode have changed + check_flag(original_settings.led_type, settings.led_type, ChangeFlags::LED); + check_flag(original_settings.eth_phy_addr, settings.eth_phy_addr, ChangeFlags::RESTART); + check_flag(original_settings.eth_clock_mode, settings.eth_clock_mode, ChangeFlags::RESTART); + + // tx_mode settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE; - check_flag(prev, settings.tx_mode, ChangeFlags::UART); + check_flag(original_settings.tx_mode, settings.tx_mode, ChangeFlags::UART); // syslog - prev = settings.syslog_enabled; settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED; - check_flag(prev, settings.syslog_enabled, ChangeFlags::SYSLOG); - prev = settings.syslog_level; + check_flag(original_settings.syslog_enabled, settings.syslog_enabled, ChangeFlags::SYSLOG); settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL; - check_flag(prev, settings.syslog_level, ChangeFlags::SYSLOG); - prev = settings.syslog_mark_interval; + check_flag(original_settings.syslog_level, settings.syslog_level, ChangeFlags::SYSLOG); settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL; - check_flag(prev, settings.syslog_mark_interval, ChangeFlags::SYSLOG); - prev = settings.syslog_port; + check_flag(original_settings.syslog_mark_interval, settings.syslog_mark_interval, ChangeFlags::SYSLOG); settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT; - check_flag(prev, settings.syslog_port, ChangeFlags::SYSLOG); + check_flag(original_settings.syslog_port, settings.syslog_port, ChangeFlags::SYSLOG); #ifndef EMSESP_STANDALONE - String old_syslog_host = settings.syslog_host; - settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; - if (old_syslog_host != settings.syslog_host) { + settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; + if (original_settings.syslog_host != settings.syslog_host) { add_flags(ChangeFlags::SYSLOG); } #endif // temperature sensor - prev = settings.dallas_parasite; settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE; - check_flag(prev, settings.dallas_parasite, ChangeFlags::TEMPERATURE_SENSOR); + check_flag(original_settings.dallas_parasite, settings.dallas_parasite, ChangeFlags::TEMPERATURE_SENSOR); // shower - prev = settings.shower_timer; settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER; - check_flag(prev, settings.shower_timer, ChangeFlags::SHOWER); - prev = settings.shower_alert; + check_flag(original_settings.shower_timer, settings.shower_timer, ChangeFlags::SHOWER); settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT; - check_flag(prev, settings.shower_alert, ChangeFlags::SHOWER); - prev = settings.shower_alert_trigger; + check_flag(original_settings.shower_alert, settings.shower_alert, ChangeFlags::SHOWER); 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_min_duration; + check_flag(original_settings.shower_alert_trigger, settings.shower_alert_trigger, ChangeFlags::SHOWER); settings.shower_min_duration = root["shower_min_duration"] | EMSESP_DEFAULT_SHOWER_MIN_DURATION; - check_flag(prev, settings.shower_min_duration, ChangeFlags::SHOWER); - prev = settings.shower_alert_coldshot; + check_flag(original_settings.shower_min_duration, settings.shower_min_duration, ChangeFlags::SHOWER); settings.shower_alert_coldshot = root["shower_alert_coldshot"] | EMSESP_DEFAULT_SHOWER_ALERT_COLDSHOT; - check_flag(prev, settings.shower_alert_coldshot, ChangeFlags::SHOWER); + check_flag(original_settings.shower_alert_coldshot, settings.shower_alert_coldshot, ChangeFlags::SHOWER); - // led - prev = settings.hide_led; + // LED settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED; - check_flag(prev, settings.hide_led, ChangeFlags::LED); + check_flag(original_settings.hide_led, settings.hide_led, ChangeFlags::LED); // adc - prev = settings.analog_enabled; settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED; - check_flag(prev, settings.analog_enabled, ChangeFlags::ANALOG_SENSOR); + check_flag(original_settings.analog_enabled, settings.analog_enabled, ChangeFlags::ANALOG_SENSOR); - // - // these need system restarts first before settings are activated... - // - prev = settings.telnet_enabled; + // telnet, ems bus id and low clock settings.telnet_enabled = root["telnet_enabled"] | EMSESP_DEFAULT_TELNET_ENABLED; - check_flag(prev, settings.telnet_enabled, ChangeFlags::RESTART); - - prev = settings.ems_bus_id; + check_flag(original_settings.telnet_enabled, settings.telnet_enabled, ChangeFlags::RESTART); settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID; - check_flag(prev, settings.ems_bus_id, ChangeFlags::RESTART); - - prev = settings.low_clock; + check_flag(original_settings.ems_bus_id, settings.ems_bus_id, ChangeFlags::RESTART); settings.low_clock = root["low_clock"]; - check_flag(prev, settings.low_clock, ChangeFlags::RESTART); + check_flag(original_settings.low_clock, settings.low_clock, ChangeFlags::RESTART); // Modbus settings - prev = settings.modbus_enabled; settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED; - check_flag(prev, settings.modbus_enabled, ChangeFlags::RESTART); - - prev = settings.modbus_port; + check_flag(original_settings.modbus_enabled, settings.modbus_enabled, ChangeFlags::RESTART); settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT; - check_flag(prev, settings.modbus_port, ChangeFlags::RESTART); - - prev = settings.modbus_max_clients; + check_flag(original_settings.modbus_port, settings.modbus_port, ChangeFlags::RESTART); settings.modbus_max_clients = root["modbus_max_clients"] | EMSESP_DEFAULT_MODBUS_MAX_CLIENTS; - check_flag(prev, settings.modbus_max_clients, ChangeFlags::RESTART); - - prev = settings.modbus_timeout; + check_flag(original_settings.modbus_max_clients, settings.modbus_max_clients, ChangeFlags::RESTART); settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT; - check_flag(prev, settings.modbus_timeout, ChangeFlags::RESTART); + check_flag(original_settings.modbus_timeout, settings.modbus_timeout, ChangeFlags::RESTART); // // these may need mqtt restart to rebuild HA discovery topics // - prev = settings.bool_format; settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT; EMSESP::system_.bool_format(settings.bool_format); if (Mqtt::ha_enabled()) { - check_flag(prev, settings.bool_format, ChangeFlags::MQTT); + check_flag(original_settings.bool_format, settings.bool_format, ChangeFlags::MQTT); } - prev = settings.enum_format; settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT; EMSESP::system_.enum_format(settings.enum_format); if (Mqtt::ha_enabled()) { - check_flag(prev, settings.enum_format, ChangeFlags::MQTT); + check_flag(original_settings.enum_format, settings.enum_format, ChangeFlags::MQTT); } - String old_locale = settings.locale; - settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE; + settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE; EMSESP::system_.locale(settings.locale); - if (Mqtt::ha_enabled() && old_locale != settings.locale) { + if (Mqtt::ha_enabled() && original_settings.locale != settings.locale) { add_flags(ChangeFlags::MQTT); } @@ -370,7 +266,7 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) { settings.boiler_heatingoff = root["boiler_heatingoff"] | EMSESP_DEFAULT_BOILER_HEATINGOFF; settings.remote_timeout = root["remote_timeout"] | EMSESP_DEFAULT_REMOTE_TIMEOUT; settings.remote_timeout_enabled = root["remote_timeout_en"] | EMSESP_DEFAULT_REMOTE_TIMEOUT_EN; - emsesp::Roomctrl::set_timeout(settings.remote_timeout_enabled ? settings.remote_timeout : 0); + Roomctrl::set_timeout(settings.remote_timeout_enabled ? settings.remote_timeout : 0); settings.fahrenheit = root["fahrenheit"]; EMSESP::system_.fahrenheit(settings.fahrenheit); @@ -391,12 +287,24 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) { if (EMSESP::system_.PSram() > 0) { settings.weblog_buffer = root["weblog_buffer"] | EMSESP_DEFAULT_WEBLOG_BUFFER; } else { - settings.weblog_buffer = root["weblog_buffer"] | 25; + settings.weblog_buffer = root["weblog_buffer"] | 25; // limit to 25 messages if no psram } - // save the settings - if (get_flags() == WebSettings::ChangeFlags::RESTART) { - return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed + // save the setting internally, for reference later + EMSESP::system_.store_settings(settings); + + // save the settings if changed from the webUI + // if we encountered an invalid GPIO, don't save settings and report the error + if (!valid_gpios) { +#if defined(EMSESP_DEBUG) + EMSESP::logger().debug("Warning: one or more GPIOs are invalid"); +#endif + EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO); + return StateUpdateResult::CHANGED; // save the settings anyway, without restart + } + + if (has_flags(WebSettings::ChangeFlags::RESTART)) { + return StateUpdateResult::CHANGED_RESTART; } return StateUpdateResult::CHANGED; @@ -440,7 +348,7 @@ void WebSettingsService::onUpdate() { } if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) { - emsesp::Mqtt::reset_mqtt(); // reload MQTT, init HA etc + Mqtt::reset_mqtt(); // reload MQTT, init HA etc } WebSettings::reset_flags(); @@ -456,7 +364,7 @@ void WebSettingsService::save() { _fsPersistence.writeToFS(); } -// build the json profile to send back +// send the board profile as JSON void WebSettingsService::board_profile(AsyncWebServerRequest * request) { if (request->hasParam("boardProfile")) { std::string board_profile = request->getParam("boardProfile")->value().c_str(); @@ -464,7 +372,8 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request) { auto * response = new AsyncJsonResponse(false); JsonObject root = response->getRoot(); - std::vector data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode, led_type + // 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type + std::vector data; (void)System::load_board_profile(data, board_profile); root["board_profile"] = board_profile; root["led_gpio"] = data[0]; @@ -487,4 +396,124 @@ void WebSettingsService::board_profile(AsyncWebServerRequest * request) { request->send(response); } +// loads the board profile to set the gpios +// if the board profile is not found, or default, it will try to autodetect the board profile +void WebSettings::set_board_profile(WebSettings & settings) { + // The optional NVS boot value has priority and overrides any board_profile setting. + // This is only done for BBQKees boards + // Note 1: we never set the NVS boot value in the code - this is done on initial pre-loading + // Note 2: The board profile is dynamically changed for the session, but the value in the settings file on the FS remains untouched + if (EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::MFG).startsWith("BBQKees")) { + String bbq_board = EMSESP::system_.getBBQKeesGatewayDetails(FUSE_VALUE::BOARD); + if (!bbq_board.isEmpty()) { +#if defined(EMSESP_DEBUG) + EMSESP::logger().info("Overriding board profile with fuse value %s", bbq_board.c_str()); +#endif + settings.board_profile = bbq_board; + } + } + + // load the board profile into the data vector + // 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type + std::vector data(10, 255); // initialize with 255 for all values + if (settings.board_profile != "default") { + if (!System::load_board_profile(data, settings.board_profile.c_str())) { +#if defined(EMSESP_DEBUG) + EMSESP::logger().debug("Unable to identify board profile %s", settings.board_profile.c_str()); +#endif + settings.board_profile = "default"; // can't find profile, fallback to "default" + } + } + + // we still don't have a valid board profile. Let's see if we can determine one from the build config or hardware + if (settings.board_profile == "default") { +#if defined(EMSESP_DEBUG) + EMSESP::logger().debug("Autodetecting board profile"); +#endif +#if CONFIG_IDF_TARGET_ESP32 + // check for no PSRAM, could be a E32 or S32? + if (!ESP.getPsramSize()) { +#if ESP_ARDUINO_VERSION_MAJOR < 3 + if (ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN)) { +#else + if (ETH.begin(ETH_PHY_LAN8720, 1, 23, 18, 16, ETH_CLOCK_GPIO0_IN)) { +#endif + settings.board_profile = "E32"; // Ethernet without PSRAM + } else { + settings.board_profile = "S32"; // ESP32 standard WiFi without PSRAM + } + } else { +// check for boards with PSRAM, could be a E32V2 otherwise default back to the S32 +#if ESP_ARDUINO_VERSION_MAJOR < 3 + if (ETH.begin(0, 15, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_OUT)) { +#else + if (ETH.begin(ETH_PHY_LAN8720, 0, 23, 18, 15, ETH_CLOCK_GPIO0_OUT)) { +#endif + + if (analogReadMilliVolts(39) > 700) { // core voltage > 2.6V + settings.board_profile = "E32V2_2"; // Ethernet, PSRAM, internal sensors + } else { + settings.board_profile = "E32V2"; // Ethernet and PSRAM + } + } else { + settings.board_profile = "S32"; // ESP32 standard WiFi with PSRAM + } + } +// override if we know the target from the build config like C3, S2, S3 etc.. +#elif CONFIG_IDF_TARGET_ESP32C3 + settings.board_profile = "C3MINI"; +#elif CONFIG_IDF_TARGET_ESP32S2 + settings.board_profile = "S2MINI"; +#elif CONFIG_IDF_TARGET_ESP32S3 + settings.board_profile = "S32S3"; // BBQKees Gateway S3 +#endif + // apply the new board profile setting + System::load_board_profile(data, settings.board_profile.c_str()); + } + + EMSESP::logger().info("Loaded board profile: %s", settings.board_profile.c_str()); + + // apply the new board profile settings + // 0=led, 1=dallas, 2=rx, 3=tx, 4=button, 5=phy_type, 6=eth_power, 7=eth_phy_addr, 8=eth_clock_mode, 9=led_type + settings.led_gpio = data[0]; // LED GPIO + settings.dallas_gpio = data[1]; // Dallas GPIO + settings.rx_gpio = data[2]; // UART Rx GPIO + settings.tx_gpio = data[3]; // UART Tx GPIO + settings.pbutton_gpio = data[4]; // Button GPIO + settings.phy_type = data[5]; // PHY Type + settings.eth_power = data[6]; // Ethernet Power GPIO + settings.eth_phy_addr = data[7]; // Ethernet PHY Address + settings.eth_clock_mode = data[8]; // Ethernet Clock Mode + settings.led_type = data[9]; // LED Type +} + + +// returns true if the value was changed +bool WebSettings::check_flag(int prev_v, int new_v, uint8_t flag) { + if (prev_v != new_v) { + add_flags(flag); +#if defined(EMSESP_DEBUG) + // EMSESP::logger().debug("check_flag: flag %d, prev_v=%d, new_v=%d", flag, prev_v, new_v); +#endif + return true; + } + return false; +} + +void WebSettings::add_flags(uint8_t flags) { + flags_ |= flags; +} + +bool WebSettings::has_flags(uint8_t flags) { + return (flags_ & flags) == flags; +} + +void WebSettings::reset_flags() { + flags_ = ChangeFlags::NONE; +} + +uint8_t WebSettings::get_flags() { + return flags_; +} + } // namespace emsesp diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index addac0149..6ce6fab8a 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -89,7 +89,6 @@ class WebSettings { static StateUpdateResult update(JsonObject root, WebSettings & settings); enum ChangeFlags : uint8_t { - NONE = 0, UART = (1 << 0), // 1 - uart SYSLOG = (1 << 1), // 2 - syslog @@ -102,29 +101,15 @@ class WebSettings { RESTART = 0xFF // 255 - restart request (all changes) }; - static void check_flag(int prev_v, int new_v, uint8_t flag) { - if (prev_v != new_v) { - add_flags(flag); - } - } - - static void add_flags(uint8_t flags) { - flags_ |= flags; - } - - static bool has_flags(uint8_t flags) { - return (flags_ & flags) == flags; - } - - static void reset_flags() { - flags_ = ChangeFlags::NONE; - } - - static uint8_t get_flags() { - return flags_; - } + static bool check_flag(int prev_v, int new_v, uint8_t flag); + static void add_flags(uint8_t flags); + static bool has_flags(uint8_t flags); + static void reset_flags(); + static uint8_t get_flags(); private: + static void set_board_profile(WebSettings & settings); + static uint8_t flags_; };