Files
EMS-ESP32/src/web/WebSettingsService.cpp
T
Alexander DandClaude Opus 5.5 e1db103470 fix: give ESP32-C6 valid default GPIOs on first start
The C6 autodetect picked the CUSTOM profile, but load_board_profile() has
no entry for it, so Rx, Tx and the button stayed at the placeholder 99.
The startup settings load then failed the GPIO check and fell back to an
empty settings object (blank Board Profile, Tx Mode and Bus ID), and every
Application settings save returned 400 until valid pins were sent together
with CUSTOM through the API.

Start a C6 without a profile as CUSTOM with pins for the Seeed XIAO
ESP32-C6 (the c6_xiao_4M build): Rx GPIO20 (D9), Tx GPIO19 (D8), button
GPIO9 (BOOT), no LED, no Dallas sensor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 19:39:06 +00:00

593 lines
28 KiB
C++

/*
* EMS-ESP - https://github.com/emsesp/EMS-ESP
* Copyright 2020-2026 emsesp.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "emsesp.h"
namespace emsesp {
uint16_t WebSettings::flags_ = 0;
WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) {
securityManager->addEndpoint(server, EMSESP_BOARD_PROFILE_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) {
board_profile(request);
});
addUpdateHandler([this] { onUpdate(); }, false);
}
void WebSettings::read(WebSettings & settings, JsonObject root) {
root["version"] = settings.version;
root["board_profile"] = settings.board_profile;
root["platform"] = EMSESP_PLATFORM;
root["system_name"] = settings.system_name;
root["locale"] = settings.locale;
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["boiler_heatingoff"] = settings.boiler_heatingoff;
root["remote_timeout"] = settings.remote_timeout;
root["remote_timeout_en"] = settings.remote_timeout_enabled;
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["shower_min_duration"] = settings.shower_min_duration;
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["led_type"] = settings.led_type;
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["fahrenheit"] = settings.fahrenheit;
root["bool_format"] = settings.bool_format;
root["bool_dashboard"] = settings.bool_dashboard;
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["modbus_enabled"] = settings.modbus_enabled;
root["modbus_port"] = settings.modbus_port;
root["modbus_max_clients"] = settings.modbus_max_clients;
root["modbus_timeout"] = settings.modbus_timeout;
root["developer_mode"] = settings.developer_mode;
root["disable_reset"] = settings.disable_reset;
root["auto_fw_check"] = settings.auto_fw_check;
root["email_enabled"] = settings.email_enabled;
root["email_security"] = settings.email_security;
root["email_server"] = settings.email_server;
root["email_port"] = settings.email_port;
root["email_login"] = settings.email_login;
root["email_pass"] = settings.email_pass;
root["email_sender"] = settings.email_sender;
root["email_recp"] = settings.email_recp;
root["email_subject"] = settings.email_subject;
}
// call on initialization and also when settings are updated/saved via web or console
// note, settings is empty when the service starts
StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
// make a copy of the settings to compare to later
const WebSettings original_settings(settings);
// make a snapshot of the current GPIOs
std::vector<int8_t> used_gpios;
std::vector<int8_t> system_gpios;
EMSESP::system_.make_snapshot_gpios(used_gpios, system_gpios);
settings.version = root["version"] | EMSESP_APP_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
reset_flags();
// before loading new board profile free old gpios from used list to allow remapping
EMSESP::system_.remove_optional_gpio(original_settings.led_gpio);
EMSESP::system_.remove_optional_gpio(original_settings.dallas_gpio);
EMSESP::system_.remove_gpio(original_settings.pbutton_gpio);
EMSESP::system_.remove_optional_gpio(original_settings.rx_gpio);
EMSESP::system_.remove_optional_gpio(original_settings.tx_gpio);
// 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
// this will always run when EMS-ESP starts since original_settings{} is empty
if (original_settings.board_profile != settings.board_profile || original_settings.board_profile == "default"
|| original_settings.board_profile.length() == 0) {
set_board_profile(settings);
add_flags(ChangeFlags::RESTART);
}
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) {
check_flag(original_settings.eth_power, settings.eth_power, ChangeFlags::RESTART);
check_flag(original_settings.eth_clock_mode, settings.eth_clock_mode, ChangeFlags::RESTART);
if (settings.eth_power != -1) { // Ethernet Power -1 means disabled
EMSESP::system_.remove_gpio(settings.eth_power, true);
}
// remove the ethernet pins from valid list, regardless of whether the GPIOs are valid or not
EMSESP::system_.remove_gpio(23, true); // MDC
EMSESP::system_.remove_gpio(18, true); // MDIO
EMSESP::system_.remove_gpio(19, true); // TXD0
EMSESP::system_.remove_gpio(22, true); // TXD1
EMSESP::system_.remove_gpio(21, true); // TXEN
EMSESP::system_.remove_gpio(25, true); // RXD0
EMSESP::system_.remove_gpio(26, true); // RXD1
EMSESP::system_.remove_gpio(27, true); // CRS
if (settings.eth_clock_mode < 2) {
EMSESP::system_.remove_gpio(0, true); // ETH.clock input
} else if (settings.eth_clock_mode == 2) {
EMSESP::system_.remove_gpio(16, true); // ETH.clock output
} else if (settings.eth_clock_mode == 3) {
EMSESP::system_.remove_gpio(17, true); // ETH.clock output
}
}
// if any of the GPIOs have changed and re-validate them
bool have_valid_gpios = true;
// Helper lambda for optional GPIOs (can be 0 to disable)
auto add_optional_gpio = [&have_valid_gpios](uint8_t & gpio, const char * name) {
if (gpio != 0 && !EMSESP::system_.add_gpio(gpio, name)) {
gpio = 0; // 0 means disabled
have_valid_gpios = false;
}
};
// add new gpio assignment
check_flag(original_settings.rx_gpio, settings.rx_gpio, ChangeFlags::UART);
have_valid_gpios &= EMSESP::system_.add_gpio(settings.rx_gpio, "UART Rx");
check_flag(original_settings.tx_gpio, settings.tx_gpio, ChangeFlags::UART);
have_valid_gpios &= EMSESP::system_.add_gpio(settings.tx_gpio, "UART Tx");
check_flag(original_settings.led_gpio, settings.led_gpio, ChangeFlags::LED);
add_optional_gpio(settings.led_gpio, "LED");
check_flag(original_settings.dallas_gpio, settings.dallas_gpio, ChangeFlags::TEMPERATURE_SENSOR);
add_optional_gpio(settings.dallas_gpio, "Dallas");
check_flag(original_settings.pbutton_gpio, settings.pbutton_gpio, ChangeFlags::BUTTON);
have_valid_gpios &= EMSESP::system_.add_gpio(settings.pbutton_gpio, "Button");
// 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(original_settings.tx_mode, settings.tx_mode, ChangeFlags::UART);
// syslog
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
check_flag(original_settings.syslog_enabled, settings.syslog_enabled, ChangeFlags::SYSLOG);
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
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(original_settings.syslog_mark_interval, settings.syslog_mark_interval, ChangeFlags::SYSLOG);
settings.syslog_port = root["syslog_port"] | EMSESP_DEFAULT_SYSLOG_PORT;
check_flag(original_settings.syslog_port, settings.syslog_port, ChangeFlags::SYSLOG);
#ifndef EMSESP_STANDALONE
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
settings.dallas_parasite = root["dallas_parasite"] | EMSESP_DEFAULT_DALLAS_PARASITE;
check_flag(original_settings.dallas_parasite, settings.dallas_parasite, ChangeFlags::TEMPERATURE_SENSOR);
// shower
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
check_flag(original_settings.shower_timer, settings.shower_timer, ChangeFlags::SHOWER);
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
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(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(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(original_settings.shower_alert_coldshot, settings.shower_alert_coldshot, ChangeFlags::SHOWER);
// LED
settings.hide_led = root["hide_led"] | EMSESP_DEFAULT_HIDE_LED;
check_flag(original_settings.hide_led, settings.hide_led, ChangeFlags::LED);
// adc
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
check_flag(original_settings.analog_enabled, settings.analog_enabled, ChangeFlags::ANALOG_SENSOR);
// telnet, ems bus id and low clock
settings.telnet_enabled = root["telnet_enabled"] | EMSESP_DEFAULT_TELNET_ENABLED;
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(original_settings.ems_bus_id, settings.ems_bus_id, ChangeFlags::RESTART);
settings.low_clock = root["low_clock"];
check_flag(original_settings.low_clock, settings.low_clock, ChangeFlags::RESTART);
// Modbus settings
settings.modbus_enabled = root["modbus_enabled"] | EMSESP_DEFAULT_MODBUS_ENABLED;
check_flag(original_settings.modbus_enabled, settings.modbus_enabled, ChangeFlags::MODBUS);
settings.modbus_port = root["modbus_port"] | EMSESP_DEFAULT_MODBUS_PORT;
check_flag(original_settings.modbus_port, settings.modbus_port, ChangeFlags::MODBUS);
settings.modbus_max_clients = root["modbus_max_clients"] | EMSESP_DEFAULT_MODBUS_MAX_CLIENTS;
check_flag(original_settings.modbus_max_clients, settings.modbus_max_clients, ChangeFlags::MODBUS);
settings.modbus_timeout = root["modbus_timeout"] | EMSESP_DEFAULT_MODBUS_TIMEOUT;
check_flag(original_settings.modbus_timeout, settings.modbus_timeout, ChangeFlags::MODBUS);
//
// these may need mqtt restart to rebuild HA discovery topics
//
settings.bool_format = root["bool_format"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::system_.bool_format(settings.bool_format);
if (Mqtt::ha_enabled()) {
check_flag(original_settings.bool_format, settings.bool_format, ChangeFlags::MQTT);
}
settings.enum_format = root["enum_format"] | EMSESP_DEFAULT_ENUM_FORMAT;
EMSESP::system_.enum_format(settings.enum_format);
if (Mqtt::ha_enabled()) {
check_flag(original_settings.enum_format, settings.enum_format, ChangeFlags::MQTT);
}
settings.locale = root["locale"] | EMSESP_DEFAULT_LOCALE;
EMSESP::system_.locale(settings.locale);
if (Mqtt::ha_enabled() && original_settings.locale != settings.locale) {
add_flags(ChangeFlags::MQTT);
}
//
// without checks or necessary restarts...
//
settings.system_name = root["system_name"] | EMSESP_DEFAULT_SYSTEM_NAME;
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
EMSESP::trace_raw(settings.trace_raw);
settings.notoken_api = root["notoken_api"] | EMSESP_DEFAULT_NOTOKEN_API;
settings.solar_maxflow = root["solar_maxflow"] | EMSESP_DEFAULT_SOLAR_MAXFLOW;
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;
Roomctrl::set_timeout(settings.remote_timeout_enabled ? settings.remote_timeout : 0);
settings.fahrenheit = root["fahrenheit"];
EMSESP::system_.fahrenheit(settings.fahrenheit);
settings.readonly_mode = root["readonly_mode"];
EMSESP::system_.readonly_mode(settings.readonly_mode);
settings.developer_mode = root["developer_mode"];
EMSESP::system_.developer_mode(settings.developer_mode);
settings.disable_reset = root["disable_reset"];
EMSESP::system_.disable_reset(settings.disable_reset);
settings.auto_fw_check = root["auto_fw_check"] | EMSESP_DEFAULT_AUTO_FW_CHECK;
EMSESP::system_.auto_fw_check(settings.auto_fw_check);
if (settings.auto_fw_check && !original_settings.auto_fw_check) {
EMSESP::webStatusService.schedule_versions_refresh(); // just switched on, so look straight away
}
settings.bool_dashboard = root["bool_dashboard"] | EMSESP_DEFAULT_BOOL_FORMAT;
EMSESP::system_.bool_dashboard(settings.bool_dashboard);
settings.weblog_level = root["weblog_level"] | EMSESP_DEFAULT_WEBLOG_LEVEL;
settings.weblog_compact = root["weblog_compact"] | EMSESP_DEFAULT_WEBLOG_COMPACT;
settings.email_enabled = root["email_enabled"] | FACTORY_EMAIL_ENABLE;
// Off is 0, so `| FACTORY_EMAIL_SECURITY` would replace it with STARTTLS (2)
settings.email_security = root["email_security"].isNull() ? FACTORY_EMAIL_SECURITY : root["email_security"].as<uint8_t>();
settings.email_server = root["email_server"] | FACTORY_EMAIL_SERVER;
settings.email_port = root["email_port"] | FACTORY_EMAIL_PORT;
settings.email_login = root["email_login"] | FACTORY_EMAIL_LOGIN;
settings.email_pass = root["email_pass"] | FACTORY_EMAIL_PASSWORD;
settings.email_sender = root["email_sender"] | FACTORY_EMAIL_FROM;
settings.email_recp = root["email_recp"] | FACTORY_EMAIL_TO;
settings.email_subject = root["email_subject"] | FACTORY_EMAIL_SUBJECT;
// if no psram limit weblog buffer to 25 messages
const uint16_t weblog_buffer_default = (EMSESP::system_.PSram() > 0) ? EMSESP_DEFAULT_WEBLOG_BUFFER : 25;
settings.weblog_buffer = root["weblog_buffer"] | weblog_buffer_default;
// a stored 0 is a valid uint16 and passes the | default above, but would make
// limit_log_messages_ 0 in WebLogService
if (settings.weblog_buffer == 0) {
settings.weblog_buffer = weblog_buffer_default;
}
// save the settings if changed from the webUI
// if we encountered an invalid GPIO, rollback changes and don't save settings,
// and report the error to WebUI without a restart
if (!have_valid_gpios) {
// replace settings with original settings
settings = original_settings;
EMSESP::system_.restore_snapshot_gpios(used_gpios, system_gpios);
// report the error to WebUI
EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO);
return StateUpdateResult::ERROR; // don't save the settings if the GPIOs are invalid
}
// save the setting internally, for reference later
EMSESP::system_.store_settings(settings);
// and finally always write to the settings file
if (has_flags(ChangeFlags::RESTART)) {
return StateUpdateResult::CHANGED_RESTART;
}
return StateUpdateResult::CHANGED;
}
// this is called after any of the settings have been persisted to the filesystem
// either via the Web UI or via the Console
void WebSettingsService::onUpdate() {
// skip if we're restarting anyway
if (WebSettings::has_flags(WebSettings::ChangeFlags::RESTART)) {
return;
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::SHOWER)) {
EMSESP::shower_.start();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::TEMPERATURE_SENSOR)) {
EMSESP::temperaturesensor_.start();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::UART)) {
EMSESP::system_.uart_init();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
EMSESP::system_.syslog_init(); // re-start (or stop)
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::ANALOG_SENSOR)) {
EMSESP::analogsensor_.start();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::BUTTON)) {
EMSESP::system_.button_init();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
EMSESP::led_.init();
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::MQTT)) {
Mqtt::reset_mqtt(); // reload MQTT, init HA etc
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::MODBUS)) {
EMSESP::system_.modbus_init();
}
WebSettings::reset_flags();
EMSESP::system_.reset_unused_gpios();
}
void WebSettingsService::begin() {
_fsPersistence.readFromFS();
}
void WebSettingsService::save() {
_fsPersistence.writeToFS();
}
// 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();
auto * response = new PsramAsyncJsonResponse(false);
JsonObject root = response->getRoot();
// 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<int8_t> data;
(void)System::load_board_profile(data, board_profile);
root["board_profile"] = board_profile;
root["led_gpio"] = data[0];
root["dallas_gpio"] = data[1];
root["rx_gpio"] = data[2];
root["tx_gpio"] = data[3];
root["pbutton_gpio"] = data[4];
root["phy_type"] = data[5];
root["eth_power"] = data[6];
root["eth_phy_addr"] = data[7];
root["eth_clock_mode"] = data[8];
root["led_type"] = data[9];
response->setLength();
request->send(response);
return;
}
AsyncWebServerResponse * response = request->beginResponse(200);
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() && settings.board_profile != "CUSTOM") {
#if defined(EMSESP_DEBUG)
EMSESP::logger().debug("Overriding board profile with eFuse value %s", bbq_board.c_str());
#endif
settings.board_profile = bbq_board;
}
}
// if it's CUSTOM no need to load the board profile from the settings
// as it's already set
if (settings.board_profile == "CUSTOM") {
EMSESP::logger().info("Using CUSTOM board profile");
EMSESP::system_.set_valid_system_gpios();
return;
}
// 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<int8_t> data(10, 99); // initialize with 99 for all values, just as a safe guard to catch bad gpios
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") {
EMSESP::logger().info("Autodetecting board profile");
#if CONFIG_IDF_TARGET_ESP32
// check for no PSRAM, could be a E32 or S32?
if (!ESP.getPsramSize()) {
if (ETH.begin(ETH_PHY_LAN8720, 1, 23, 18, 16, ETH_CLOCK_GPIO0_IN)) {
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 (ETH.begin(ETH_PHY_LAN8720, 0, 23, 18, 15, ETH_CLOCK_GPIO0_OUT)) {
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
#elif CONFIG_IDF_TARGET_ESP32C6
// there is no C6 board profile, so start as CUSTOM with pins that suit the Seeed XIAO ESP32-C6 (the c6_xiao_4M build).
// load_board_profile() has no entry for CUSTOM, so without these the GPIOs stay at 99, every settings update
// fails the GPIO check and the settings fall back to empty values.
// Rx D9 (GPIO20), Tx D8 (GPIO19), button BOOT (GPIO9), no LED (the onboard LED on GPIO15 is active low), no Dallas
settings.board_profile = "CUSTOM";
data = {0, 0, 20, 19, 9, PHY_type::PHY_TYPE_NONE, 0, 0, 0, 0};
#endif
// apply the new board profile setting
System::load_board_profile(data, settings.board_profile.c_str());
}
// log board profile and PSRAM info
#ifndef EMSESP_STANDALONE
uint32_t psram_size = ESP.getPsramSize() / 1024; // in KB
if (psram_size > 0) {
EMSESP::logger().info("Applied board profile %s (PSRAM: %lu KB)", settings.board_profile.c_str(), psram_size);
} else {
EMSESP::logger().info("Applied board profile %s (PSRAM: not available)", settings.board_profile.c_str());
}
#endif
// 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, uint16_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(uint16_t flags) {
flags_ |= flags;
}
bool WebSettings::has_flags(uint16_t flags) {
return (flags_ & flags) == flags;
}
void WebSettings::reset_flags() {
flags_ = ChangeFlags::NONE;
}
uint16_t WebSettings::get_flags() {
return flags_;
}
} // namespace emsesp