This commit is contained in:
MichaelDvP
2026-01-07 08:38:01 +01:00
44 changed files with 800 additions and 451 deletions

View File

@@ -58,12 +58,12 @@ enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING };
class APSettings {
public:
uint8_t provisionMode; // 0 = on, 2 = off
String ssid;
String password;
uint8_t channel;
bool ssidHidden;
uint8_t maxClients;
uint8_t provisionMode = FACTORY_AP_PROVISION_MODE; // 0 = on, 2 = off
String ssid = FACTORY_AP_SSID;
String password = FACTORY_AP_PASSWORD;
uint8_t channel = FACTORY_AP_CHANNEL;
bool ssidHidden = FACTORY_AP_SSID_HIDDEN;
uint8_t maxClients = FACTORY_AP_MAX_CLIENTS;
IPAddress localIP;
IPAddress gatewayIP;

View File

@@ -70,6 +70,10 @@ class FSPersistence {
// failed to open file, return false
if (!settingsFile || !jsonObject.size()) {
#if defined(EMSESP_DEBUG)
Serial.printf("Failed to write file %s", _filePath);
Serial.println();
#endif
return false;
}

View File

@@ -8,6 +8,7 @@
#include <espMqttClient.h>
#include <uuid/common.h>
#include <default_settings.h>
#define MQTT_RECONNECTION_DELAY 2000 // 2 seconds
@@ -65,37 +66,37 @@
class MqttSettings {
public:
bool enabled;
String host;
uint16_t port;
String rootCA;
bool enableTLS;
String username;
String password;
bool enabled = FACTORY_MQTT_ENABLED;
String host = FACTORY_MQTT_HOST;
uint16_t port = FACTORY_MQTT_PORT;
String rootCA = "";
bool enableTLS = false;
String username = FACTORY_MQTT_USERNAME;
String password = FACTORY_MQTT_PASSWORD;
String clientId;
uint16_t keepAlive;
bool cleanSession;
uint16_t keepAlive = FACTORY_MQTT_KEEP_ALIVE;
bool cleanSession = FACTORY_MQTT_CLEAN_SESSION;
// EMS-ESP specific
String base;
uint16_t publish_time_boiler;
uint16_t publish_time_thermostat;
uint16_t publish_time_solar;
uint16_t publish_time_mixer;
uint16_t publish_time_water;
uint16_t publish_time_other;
uint16_t publish_time_sensor;
uint16_t publish_time_heartbeat;
uint8_t mqtt_qos;
bool mqtt_retain;
bool ha_enabled;
uint8_t nested_format;
String discovery_prefix;
uint8_t discovery_type;
bool publish_single;
bool publish_single2cmd;
bool send_response;
uint8_t entity_format;
String base = FACTORY_MQTT_BASE;
uint16_t publish_time_boiler = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_thermostat = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_solar = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_mixer = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_water = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_other = EMSESP_DEFAULT_PUBLISH_TIME_OTHER;
uint16_t publish_time_sensor = EMSESP_DEFAULT_PUBLISH_TIME;
uint16_t publish_time_heartbeat = EMSESP_DEFAULT_PUBLISH_HEARTBEAT;
uint8_t mqtt_qos = EMSESP_DEFAULT_MQTT_QOS;
bool mqtt_retain = EMSESP_DEFAULT_MQTT_RETAIN;
bool ha_enabled = EMSESP_DEFAULT_HA_ENABLED;
uint8_t nested_format = EMSESP_DEFAULT_NESTED_FORMAT;
String discovery_prefix = EMSESP_DEFAULT_DISCOVERY_PREFIX;
uint8_t discovery_type = EMSESP_DEFAULT_DISCOVERY_TYPE;
bool publish_single = EMSESP_DEFAULT_PUBLISH_SINGLE;
bool publish_single2cmd = EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
bool send_response = EMSESP_DEFAULT_SEND_RESPONSE;
uint8_t entity_format = EMSESP_DEFAULT_ENTITY_FORMAT;
static void read(MqttSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, MqttSettings & settings);

View File

@@ -30,10 +30,10 @@
class NTPSettings {
public:
bool enabled;
String tzLabel;
String tzFormat;
String server;
bool enabled = FACTORY_NTP_ENABLED;
String tzLabel = FACTORY_NTP_TIME_ZONE_LABEL;
String tzFormat = FACTORY_NTP_TIME_ZONE_FORMAT;
String server = FACTORY_NTP_SERVER;
static void read(NTPSettings & settings, JsonObject root);
static StateUpdateResult update(JsonObject root, NTPSettings & settings);

View File

@@ -64,17 +64,17 @@
class NetworkSettings {
public:
// core wifi configuration
String ssid;
String bssid;
String password;
String hostname;
bool staticIPConfig;
bool bandwidth20;
uint8_t tx_power;
bool nosleep;
bool enableMDNS;
bool enableCORS;
String CORSOrigin;
String ssid = FACTORY_WIFI_SSID;
String bssid = "";
String password = FACTORY_WIFI_PASSWORD;
String hostname = FACTORY_WIFI_HOSTNAME;
bool staticIPConfig = false;
bool bandwidth20 = false;
uint8_t tx_power = 0;
bool nosleep = true;
bool enableMDNS = true;
bool enableCORS = false;
String CORSOrigin = "*";
// optional configuration for static IP address
IPAddress localIP;

View File

@@ -9,7 +9,6 @@
#include <list>
#define ACCESS_TOKEN_PARAMATER "access_token"
#define AUTHORIZATION_HEADER "Authorization"
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
#define AUTHORIZATION_HEADER_PREFIX_LEN 7

View File

@@ -172,7 +172,7 @@ void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
// check for invalid extension and immediately kill the connection, which will throw an error
// that is caught by the web code. Unfortunately the http error code is not sent to the client on fast network connections
if (code == 406) {
request->client()->close(true);
request->client()->close();
_is_firmware = false;
Update.abort();
}

View File

@@ -576,7 +576,7 @@ bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, do
newSensor.is_system = is_system;
settings.analogCustomizations.push_back(newSensor);
// check the gpio again and add to used list
if (EMSESP::system_.add_gpio(gpio, "Analog Sensor")) {
if (EMSESP::system_.add_gpio(gpio, name)) {
LOG_DEBUG("Adding customization for analog sensor GPIO %02d", gpio);
return StateUpdateResult::CHANGED; // persist the change
} else {

View File

@@ -25,10 +25,6 @@
#define EMSESP_DEFAULT_LOCALE EMSESP_LOCALE_EN // English
#endif
#ifndef EMSESP_DEFAULT_VERSION
#define EMSESP_DEFAULT_VERSION ""
#endif
#ifndef EMSESP_DEFAULT_TX_MODE
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
#endif

View File

@@ -20,6 +20,7 @@
#ifndef EMSESP_STANDALONE
#include "esp_ota_ops.h"
#include "rom/rtc.h"
#endif
static_assert(uuid::thread_safe, "uuid-common must be thread-safe");
@@ -1721,15 +1722,31 @@ void EMSESP::start() {
LOG_INFO("EMS-ESP version %s", EMSESP_APP_VERSION);
#endif
// check if the firmware is fresh
LOG_DEBUG("System is running in Debug mode");
// check if the firmware is fresh, i.e. a new install or a new version has been uploaded
// this is set in UploadFileService::uploadComplete()
// and reset in System::set_partition_install_date()
if (!EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE)) {
LOG_DEBUG("Firmware is fresh");
if (EMSESP::nvs_.getBool(EMSESP_NVS_BOOT_NEW_FIRMWARE)) {
LOG_DEBUG("Firmware is a new install");
} else {
// check if the firmware has been uploaded via Serial/USB
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
if (rtc_get_reset_reason(0) == 11) { // ESP_RST_USB (Reset by USB peripheral, on CPU 0 only
#else
if ((rtc_get_reset_reason(0) == 14 || rtc_get_reset_reason(1) == 14)) { // APP CPU reset by PRO CPU, can be either CPUs
#endif
LOG_DEBUG("Firmware is a new install, uploaded via Serial/USB");
EMSESP::nvs_.putBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, true); // set flag so it's picked up later to set the install date
}
}
LOG_DEBUG("System is running in Debug mode");
// S2 are C3 are both single core
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
LOG_INFO("Last system reset reason Core0: %s", system_.reset_reason(0).c_str());
#else
LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str());
#endif
// see if we're restoring a settings file
#ifndef EMSESP_STANDALONE
@@ -1837,7 +1854,7 @@ void EMSESP::loop() {
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
static bool only_once = false;
if (!only_once) {
LOG_ERROR("Invalid GPIOs used. Please check your settings and log");
LOG_ERROR("Invalid GPIOs used. Please check your settings and the system log");
only_once = true;
}
}

View File

@@ -562,8 +562,8 @@ void Mqtt::ha_status() {
// Note we don't use camelCase as it would change the HA entity_id and impact historic data
#ifndef EMSESP_STANDALONE
if (!EMSESP::system_.ethernet_connected() || WiFi.isConnected()) {
publish_system_ha_sensor_config(DeviceValueType::INT8, "WiFi RSSI", "rssi", DeviceValueUOM::DBM);
publish_system_ha_sensor_config(DeviceValueType::INT8, "WiFi strength", "wifistrength", DeviceValueUOM::PERCENT);
publish_system_ha_sensor_config(DeviceValueType::INT8, "RSSI", "rssi", DeviceValueUOM::DBM);
publish_system_ha_sensor_config(DeviceValueType::INT8, "Signal", "wifistrength", DeviceValueUOM::PERCENT);
}
#endif
@@ -1628,4 +1628,4 @@ void Mqtt::add_value_bool(JsonObject doc, const char * name, bool value) {
}
}
} // namespace emsesp
} // namespace emsesp

View File

@@ -105,8 +105,8 @@ uint32_t System::led_flash_duration_ = 0;
bool System::led_flash_timer_ = false;
// GPIOs
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::used_gpios_;
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
std::vector<System::GpioUsage, AllocatorPSRAM<System::GpioUsage>> System::used_gpios_;
// find the index of the language
// 0 = EN, 1 = DE, etc...
@@ -316,11 +316,9 @@ void System::get_partition_info() {
auto current_partition = (const char *)esp_ota_get_running_partition()->label;
// update the current version and partition name in NVS if not already set (saves on flash wearing)
if (EMSESP::nvs_.getString(current_partition) != EMSESP_APP_VERSION || emsesp::EMSESP::nvs_.getBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, false)) {
if (EMSESP::nvs_.getBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, false)) {
EMSESP::nvs_.putBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, false);
}
// update the current version and partition name in NVS if not already set
if (EMSESP::nvs_.getString(current_partition) != EMSESP_APP_VERSION || emsesp::EMSESP::nvs_.getBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, true)) {
EMSESP::nvs_.putBool(emsesp::EMSESP_NVS_BOOT_NEW_FIRMWARE, false);
EMSESP::nvs_.putString(current_partition, EMSESP_APP_VERSION);
char c[20];
snprintf(c, sizeof(c), "d_%s", current_partition);
@@ -1216,16 +1214,16 @@ void System::show_system(uuid::console::Shell & shell) {
// GPIOs
shell.println(" GPIOs:");
shell.printf(" in use:");
for (const auto & gpio : used_gpios_) {
shell.printf(" %d", gpio);
for (const auto & usage : used_gpios_) {
shell.printf(" %d(%s)", usage.pin, usage.source.c_str());
}
shell.printfln(" (total %d)", used_gpios_.size());
shell.printfln(" [total %d]", used_gpios_.size());
auto available = available_gpios();
shell.printf(" available:");
for (const auto & gpio : available) {
shell.printf(" %d", gpio);
}
shell.printfln(" (total %d)", available.size());
shell.printfln(" [total %d]", available.size());
// List all partitions and their version info
shell.println(" Partitions:");
for (const auto & partition : partition_info_) {
@@ -1410,7 +1408,7 @@ bool System::check_upgrade() {
// see if we're missing a version, will be < 3.5.0b13 from Dec 23 2022
missing_version = (settingsVersion.empty() || (settingsVersion.length() < 5));
if (missing_version) {
LOG_WARNING("No version information found");
LOG_WARNING("No version information found. Assuming version 3.5.0");
settingsVersion = "3.5.0"; // this was the last stable version without version info
}
@@ -1422,7 +1420,7 @@ bool System::check_upgrade() {
bool save_version = true;
bool reboot_required = false;
LOG_DEBUG("Checking for version upgrades (settings file is v%d.%d.%d%s)",
LOG_DEBUG("Checking for version upgrades from v%d.%d.%d%s",
settings_version.major(),
settings_version.minor(),
settings_version.patch(),
@@ -1432,7 +1430,7 @@ bool System::check_upgrade() {
if (this_version > settings_version) {
// we need to do an upgrade
if (missing_version) {
LOG_NOTICE("Upgrading to version %d.%d.%d%s", this_version.major(), this_version.minor(), this_version.patch(), this_version_type);
LOG_NOTICE("Upgrading to version %d.%d.%d%s", this_version.major(), this_version.minor(), this_version.patch(), this_version_type.c_str());
} else {
LOG_NOTICE("Upgrading from version %d.%d.%d%s to %d.%d.%d%s",
settings_version.major(),
@@ -1499,7 +1497,15 @@ bool System::check_upgrade() {
});
} else if (this_version < settings_version) {
// downgrading
LOG_NOTICE("Downgrading to version %d.%d.%d%s", this_version.major(), this_version.minor(), this_version.patch(), this_version_type.c_str());
LOG_NOTICE("Downgrading from version %d.%d.%d%s to version %d.%d.%d%s",
settings_version.major(),
settings_version.minor(),
settings_version.patch(),
settings_version_type.c_str(),
this_version.major(),
this_version.minor(),
this_version.patch(),
this_version_type.c_str());
} else {
save_version = false; // same version, do nothing
}
@@ -2031,8 +2037,9 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#else
node["version"] = EMSESP_APP_VERSION;
#endif
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptimeSec"] = uuid::get_uptime_sec();
node["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
node["uptimeSec"] = uuid::get_uptime_sec();
node["resetReason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
#ifndef EMSESP_STANDALONE
node["platform"] = EMSESP_PLATFORM;
node["cpuType"] = ESP.getChipModel();
@@ -2045,10 +2052,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
node["freeApp"] = EMSESP::system_.appFree(); // kilobytes
node["partition"] = (const char *)esp_ota_get_running_partition()->label; // active partition
node["flash_chip_size"] = ESP.getFlashChipSize() / 1024; // kilobytes
#endif
node["resetReason"] = EMSESP::system_.reset_reason(0) + " / " + EMSESP::system_.reset_reason(1);
#ifndef EMSESP_STANDALONE
node["psram"] = (EMSESP::system_.PSram() > 0); // make boolean
node["psram"] = (EMSESP::system_.PSram() > 0); // make boolean
if (EMSESP::system_.PSram()) {
node["psramSize"] = EMSESP::system_.PSram();
node["freePsram"] = ESP.getFreePsram() / 1024;
@@ -2057,9 +2061,27 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
node["temperature"] = EMSESP::system_.temperature();
#endif
node["txpause"] = EMSbus::tx_mode() == EMS_TXMODE_OFF;
#endif
node["txpause"] = EMSbus::tx_mode() == EMS_TXMODE_OFF;
// GPIO information
std::string gpios_in_use_str;
for (const auto & usage : EMSESP::system_.used_gpios_) {
if (!gpios_in_use_str.empty()) {
gpios_in_use_str += ", ";
}
gpios_in_use_str += Helpers::itoa(usage.pin);
}
node["gpios_in_use"] = gpios_in_use_str;
std::string gpios_available_str;
for (const auto & gpio : EMSESP::system_.available_gpios()) {
if (!gpios_available_str.empty()) {
gpios_available_str += ", ";
}
gpios_available_str += Helpers::itoa(gpio);
}
node["gpios_available"] = gpios_available_str;
// Network Status
node = output["network"].to<JsonObject>();
@@ -2448,15 +2470,15 @@ bool System::command_txpause(const char * value, const int8_t id) {
// format command - factory reset, removing all config files
bool System::command_format(const char * value, const int8_t id) {
#if !defined(EMSESP_STANDALONE) && !defined(EMSESP_DEBUG)
// don't really format the filesystem in debug or standalone mode
#if !defined(EMSESP_STANDALONE) && !defined(EMSESP_TEST)
// don't really format the filesystem in test or standalone mode
if (LittleFS.format()) {
LOG_INFO("Filesystem formatted successfully. All config files removed.");
} else {
LOG_ERROR("Format failed");
}
#else
LOG_INFO("Format command not available in standalone or debug mode");
LOG_ERROR("Format command not available in standalone or test mode");
#endif
// restart will be handled by the main loop
@@ -2553,21 +2575,6 @@ bool System::ntp_connected() {
// see if its a BBQKees Gateway by checking the nvs values
String System::getBBQKeesGatewayDetails(uint8_t detail) {
#ifndef EMSESP_STANDALONE
/*
if (!EMSESP::nvs_.isKey("mfg")) {
return "";
}
// mfg can be either "BBQKees" or "BBQKees Electronics"
auto mfg = EMSESP::nvs_.getString("mfg");
if (mfg) {
if (!mfg.startsWith("BBQKees")) {
return "";
}
}
return "BBQKees Gateway Model " + EMSESP::nvs_.getString("model") + " v" + EMSESP::nvs_.getString("hwrevision") + "/" + EMSESP::nvs_.getString("batch");
*/
union {
struct {
uint32_t no : 4;
@@ -2580,14 +2587,17 @@ String System::getBBQKeesGatewayDetails(uint8_t detail) {
};
uint32_t reg;
} gw;
for (uint8_t reg = 0; reg < 8; reg++) {
gw.reg = esp_efuse_read_reg(EFUSE_BLK3, reg);
if (reg == 7 || esp_efuse_read_reg(EFUSE_BLK3, reg + 1) == 0)
break;
}
const char * mfg[] = {"unknown", "BBQKees Electronics", "", "", "", "", "", ""};
const char * model[] = {"unknown", "S3", "E32V2", "E32V2.2", "S32", "E32", "", "", ""};
const char * board[] = {"CUSTOM", "S32S3", "E32V2", "E32V2_2", "S32", "E32", "", "", ""};
switch (detail) {
case FUSE_VALUE::MFG:
return gw.mfg < 2 ? String(mfg[gw.mfg]) : "unknown";
@@ -2605,9 +2615,11 @@ String System::getBBQKeesGatewayDetails(uint8_t detail) {
default:
break;
}
if (!gw.reg || gw.mfg > 1 || gw.model > 5) {
return "";
}
return String(mfg[gw.mfg]) + " " + String(model[gw.model]) + " rev." + String(gw.rev_major) + "." + String(gw.rev_minor) + "/" + String(2000 + gw.year)
+ (gw.month < 10 ? "0" : "") + String(gw.month) + String(gw.no);
#else
@@ -2684,6 +2696,9 @@ 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);
// 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)); });
// get tcp stream and send it to Updater
WiFiClient * stream = http.getStreamPtr();
if (Update.writeStream(*stream) != firmware_size) {
@@ -2772,10 +2787,15 @@ bool System::command_read(const char * value, const int8_t id) {
}
// set the system status code - SYSTEM_STATUS in system.h
// this is also used in the SystemMonitor.tsx WebUI to show the progress of the firmware upload, start at 100
void System::systemStatus(uint8_t status_code) {
if (systemStatus_ != status_code) {
systemStatus_ = status_code;
LOG_DEBUG("Setting System status code %d", status_code);
#ifdef EMSESP_DEBUG
if (status_code < SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING) {
LOG_DEBUG("Setting System status code %d", status_code);
}
#endif
}
}
@@ -2783,8 +2803,11 @@ uint8_t System::systemStatus() {
return systemStatus_;
}
// takes a string range like "6-11, 1, 23, 24-48" which has optional ranges and single values and converts to a vector of ints
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(const std::string & range) {
// takes two arguments:
// the first is the full range of pins to consider
// the second is a string range of GPIOs to exclude, like "6-11, 1, 23, 24-48"
// returns a vector array of GPIOs that are valid for use
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(const std::string & range, const std::string & exclude) {
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> gpios;
std::string::size_type pos = 0;
std::string::size_type prev = 0;
@@ -2816,41 +2839,122 @@ std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(con
// handle the last part
process_part(range.substr(prev));
// if exclude list is provided, parse it and remove excluded GPIOs
if (!exclude.empty()) {
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> exclude_gpios;
pos = 0;
prev = 0;
auto process_exclude = [&exclude_gpios](std::string part) {
// trim whitespace
part.erase(0, part.find_first_not_of(" \t"));
part.erase(part.find_last_not_of(" \t") + 1);
// check if it's a range (contains '-')
std::string::size_type dash_pos = part.find('-');
if (dash_pos != std::string::npos) {
// it's a range like "6-11"
int start = std::stoi(part.substr(0, dash_pos));
int end = std::stoi(part.substr(dash_pos + 1));
for (int i = start; i <= end; i++) {
exclude_gpios.push_back(static_cast<uint8_t>(i));
}
} else {
exclude_gpios.push_back(static_cast<uint8_t>(std::stoi(part)));
}
};
while ((pos = exclude.find(',', prev)) != std::string::npos) {
process_exclude(exclude.substr(prev, pos - prev));
prev = pos + 1;
}
// handle the last part
process_exclude(exclude.substr(prev));
// remove excluded GPIOs from the main list
gpios.erase(std::remove_if(gpios.begin(),
gpios.end(),
[&exclude_gpios](uint8_t gpio) { return std::find(exclude_gpios.begin(), exclude_gpios.end(), gpio) != exclude_gpios.end(); }),
gpios.end());
}
return gpios;
}
// initialize a list of valid GPIOs based on the ESP32 board
// note: we always allow 0, which is used to indicate Dallas or LED is disabled
// string_to_vector() take two strings, the first is the range of GPIOs to use, the second is a list of GPIOs to exclude
// notes:
// we always allow 0 (which is usually a strapping pin), because it's used to indicate whether EMS-ESP Dallas or the LED is disabled
// we allow UART0, 1 and 2 as they are configurable
// strapping pins are disabled as they can affect boot behaviour
// we accept GPIOs that are fixed on BBQKees boards
//
void System::set_valid_system_gpios() {
valid_system_gpios_.clear(); // reset system list
used_gpios_.clear(); // reset used list
// get free gpios based on board/platform type
#if CONFIG_IDF_TARGET_ESP32C3
// https://www.wemos.cc/en/latest/c3/c3_mini.html
valid_system_gpios_ = string_range_to_vector("0-10"); // UART0=20,21
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/gpio.html
// excluded:
// GPIO2, GPIO8 - GPIO9 = strapping pins
// GPIO12 - GPIO17 = used for SPI flash and PSRAM
// GPIO18 - GPIO19 = USB-JTAG
//
// notes on what is allowed:
// GPIO10 = button on BOARD_C3_MINI_V1
valid_system_gpios_ = string_range_to_vector("0-21", "2, 8-9, 12-17, 18-19");
#elif CONFIG_IDF_TARGET_ESP32S2
// 43 and 44 are UART0 pins
// 38 and 39 are strapping pins, input only
valid_system_gpios_ = string_range_to_vector("0-14, 19, 20, 21, 33-37, 45, 46");
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s2/api-reference/peripherals/gpio.html
// excluded:
// GPIO26 - GPIO32 = SPI flash and PSRAM
// GPIO45 - GPIO46 = strapping pins
// GPIO39 - GPIO42 = USB-JTAG
// GPIO22 - GPIO25 = don't exist
//
// notes on what is allowed:
valid_system_gpios_ = string_range_to_vector("0-46", "26-32, 45-46, 39-42, 22-25");
#elif CONFIG_IDF_TARGET_ESP32S3
// 43 and 44 are UART0 pins
// 33-37 for Octal SPI (SPIIO4 through SPIIO7 and SPIDQS)
// 38 and 39 are input only
// 45 and 36 are strapping pins, input only
// 47 and 48 are valid on a Wemos S3 (https://github.com/emsesp/EMS-ESP32/issues/2874)
valid_system_gpios_ = string_range_to_vector("0-14, 17, 18, 21, 33-39, 45-48");
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/gpio.html
// excluded:
// GPIO3, GPIO45 - GPIO46 = strapping pins
// GPIO26 - GPIO32 = SPI flash and PSRAM and not recommended
// GPIO33 - GPIO37 = Octal flash/PSRAM
// GPIO19 - GPIO20 = USB-JTAG
// GPIO22 - GPIO25 = don't exist
//
// notes on what is allowed:
// GPIO11 - GPIO19 = ADC analog input only pins
// GPIO47 - GPIO48 = valid on a Wemos S3
// GPIO8 = used by Liligo S3 board profile for Rx
valid_system_gpios_ = string_range_to_vector("0-48", "3, 45-46, 26-32, 33-37, 19-20, 22-25");
#elif CONFIG_IDF_TARGET_ESP32
// 1 and 3 are UART0 pins, but used for some eth-boards (BBQKees-E32, OlimexPOE)
// 32-39 is ADC1, input only
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/gpio.html
// excluded:
// GPIO6 - GPIO11, GPIO16 - GPIO17 = used for SPI flash and PSRAM
// GPIO12 - GPIO15 = USB-JTAG (but we allow GPIO14 for BBQKees) and GPIO12 & GPIO13 also reserved for BBQKees E32V2.2
// GPIO20, GPIO24, GPIO28 - GPIO31 = don't exist
//
// notes on what is allowed:
// GPIO34, GPIO35, GPIO37 = input only
// GPIO2, GPIO4, GPIO5, GPIO14 = used on BBQKees boards for either LED, Dallas or Rx
// GPIO23 and GPIO18 are used by Ethernet
// GPIO25 - GPIO37 = ADC2
// GPIO32 - GPIO39 = ADC1
// GPIO36 = used on BBQKees boards for supply_voltage (E32V2.2) (note may conflict with WiFI on other boards)
// GPIO39 = used on BBQKees boards for core_voltage (E32V2.2) (note may conflict with WiFI on other boards)
if (ESP.getPsramSize() > 0) {
// if psram is enabled remove pins 16 and 17 from the list
valid_system_gpios_ = string_range_to_vector("0-5, 12-15, 18-19, 23, 25-27, 32-39");
// remove SPI0/1 PSRAM pins GPIO16 (CS) and GPIO17 (CLK) from the list
valid_system_gpios_ = string_range_to_vector("0-39", "6-11, 12, 13, 15, 16, 17, 20, 24, 28-31");
} else {
valid_system_gpios_ = string_range_to_vector("0-5, 12-19, 23, 25-27, 32-39");
valid_system_gpios_ = string_range_to_vector("0-39", "6-11, 12, 13, 15, 20, 24, 28-31");
}
#elif defined(EMSESP_STANDALONE)
valid_system_gpios_ = string_range_to_vector("0-5, 12-19, 23, 25-27, 32-39");
valid_system_gpios_ = string_range_to_vector("0-39");
#endif
}
@@ -2860,8 +2964,9 @@ 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 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_WARNING("GPIO %d for %s is already in use", pin, source_name);
auto it = std::find_if(used_gpios_.begin(), used_gpios_.end(), [pin](const GpioUsage & usage) { return usage.pin == pin; });
if (it != used_gpios_.end()) {
LOG_WARNING("GPIO %d for %s is already in use by %s", pin, source_name, it->source.c_str());
return false; // Pin is already used
}
} else {
@@ -2874,24 +2979,24 @@ bool System::add_gpio(uint8_t pin, const char * source_name) {
remove_gpio(pin);
LOG_DEBUG("Adding GPIO %d for %s to used gpio list", pin, source_name);
used_gpios_.push_back(pin); // add to used list
used_gpios_.push_back({pin, source_name}); // add to used list
return true;
}
// remove a gpio from both valid and used lists
void System::remove_gpio(uint8_t pin, bool also_system) {
auto it = std::find(used_gpios_.begin(), used_gpios_.end(), pin);
auto it = std::find_if(used_gpios_.begin(), used_gpios_.end(), [pin](const GpioUsage & usage) { return usage.pin == pin; });
if (it != used_gpios_.end()) {
LOG_DEBUG("GPIO %d removed from used gpio list", pin);
used_gpios_.erase(it);
}
if (also_system) {
it = std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin);
if (it != valid_system_gpios_.end()) {
auto it_sys = std::find(valid_system_gpios_.begin(), valid_system_gpios_.end(), pin);
if (it_sys != valid_system_gpios_.end()) {
LOG_DEBUG("GPIO %d removed from valid gpio list", pin);
valid_system_gpios_.erase(it);
valid_system_gpios_.erase(it_sys);
}
}
}
@@ -2900,7 +3005,7 @@ void System::remove_gpio(uint8_t pin, bool also_system) {
std::vector<uint8_t> System::available_gpios() {
std::vector<uint8_t> gpios;
for (const auto & gpio : valid_system_gpios_) {
if (std::find(used_gpios_.begin(), used_gpios_.end(), gpio) == used_gpios_.end()) {
if (std::find_if(used_gpios_.begin(), used_gpios_.end(), [gpio](const GpioUsage & usage) { return usage.pin == gpio; }) == used_gpios_.end()) {
gpios.push_back(gpio); // didn't find it in used_gpios_, so it's available
}
}
@@ -2909,8 +3014,8 @@ std::vector<uint8_t> System::available_gpios() {
// make a snapshot of the current GPIOs
void System::make_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8_t> & s_gpios) {
for (const auto & gpio : used_gpios_) {
u_gpios.push_back(gpio);
for (const auto & usage : used_gpios_) {
u_gpios.push_back(usage.pin);
}
for (const auto & gpio : valid_system_gpios_) {
s_gpios.push_back(gpio);
@@ -2921,7 +3026,7 @@ void System::make_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8
void System::restore_snapshot_gpios(std::vector<int8_t> & u_gpios, std::vector<int8_t> & s_gpios) {
used_gpios_.clear();
for (const auto & gpio : u_gpios) {
used_gpios_.push_back(gpio);
used_gpios_.push_back({static_cast<uint8_t>(gpio), "restored"});
}
valid_system_gpios_.clear();

View File

@@ -159,7 +159,6 @@ class System {
static void extractSettings(const char * filename, const char * section, JsonObject output);
static bool saveSettings(const char * filename, const char * section, JsonObject input);
// GPIOs
static bool add_gpio(uint8_t pin, const char * source_name);
static std::vector<uint8_t> available_gpios();
static bool load_board_profile(std::vector<int8_t> & data, const std::string & board_profile);
@@ -434,11 +433,15 @@ class System {
void led_monitor();
void system_check();
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range);
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range, const std::string & exclude = "");
// GPIOs
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> used_gpios_; // list of GPIOs used by the application
struct GpioUsage {
uint8_t pin;
std::string source;
};
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
static std::vector<GpioUsage, AllocatorPSRAM<GpioUsage>> used_gpios_; // list of GPIOs used by the application
int8_t wifi_quality(int8_t dBm);

View File

@@ -221,14 +221,14 @@ void RxService::add(uint8_t * data, uint8_t length) {
LOG_TRACE("Rx: %s", Helpers::data_to_hex(data, length).c_str());
}
LOG_DEBUG("New Rx telegram, message length %d", message_length);
// if we don't have a type_id exit,
// do not exit on empty message, it is checked for toggle fetch
// if we don't have a type_id exit
// do not exit on empty message, it is checked later for toggle fetch
if (type_id == 0) {
return;
}
LOG_DEBUG("New Rx telegram, message length %d", message_length);
// create the telegram
auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length);

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.8.1-dev.3"
#define EMSESP_APP_VERSION "3.8.1-dev.4"

View File

@@ -120,10 +120,11 @@ 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_.add_gpio(analogJson["gpio"].as<uint8_t>(), "Analog Sensor")) {
auto analog_sensor_name = analogJson["name"].as<const char *>();
if (!EMSESP::system_.add_gpio(analogJson["gpio"].as<uint8_t>(), analog_sensor_name)) {
EMSESP::logger().warning("Analog sensor: Invalid GPIO %d for %s. Skipping.",
analogJson["gpio"].as<uint8_t>(),
analogJson["name"].as<const char *>());
analog_sensor_name);
continue;
}
auto analog = AnalogCustomization();

View File

@@ -97,7 +97,7 @@ void WebLogService::show(Shell & shell) {
}
shell.println();
shell.printfln("Recent Log (level %s, max %d messages):", uuid::log::format_level_uppercase(level_), maximum_log_messages_);
shell.printfln("Recent Log:");
shell.println();
for (const auto & message : log_messages_) {

View File

@@ -96,9 +96,7 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
std::vector<int8_t> system_gpios;
EMSESP::system_.make_snapshot_gpios(used_gpios, system_gpios);
reset_flags();
settings.version = root["version"] | EMSESP_DEFAULT_VERSION; // save the version, we use it later in System::check_upgrade()
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
@@ -113,10 +111,13 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
settings.eth_clock_mode = root["eth_clock_mode"];
settings.led_type = root["led_type"]; // 1 = RGB-LED
reset_flags();
// 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) {
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);
}
@@ -306,10 +307,10 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
// 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; // the original settings are still valid
// restore the GPIOs from the snapshot
settings = original_settings;
EMSESP::system_.restore_snapshot_gpios(used_gpios, system_gpios);
// report the error to WebUI
@@ -320,7 +321,8 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
// save the setting internally, for reference later
EMSESP::system_.store_settings(settings);
if (has_flags(WebSettings::ChangeFlags::RESTART)) {
// and finally always write to the settings file
if (has_flags(ChangeFlags::RESTART)) {
return StateUpdateResult::CHANGED_RESTART;
}

View File

@@ -30,7 +30,7 @@ namespace emsesp {
class WebSettings {
public:
String version;
String version = EMSESP_APP_VERSION;
String locale;
uint8_t tx_mode;
uint8_t ems_bus_id;