From d2af79edfd0a6aa91dd42d60a27855a3529f049e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 17 Aug 2025 22:02:32 +0200 Subject: [PATCH 01/44] [Storage] Add settings for External EEPROM --- src/src/CustomBuild/define_plugin_sets.h | 13 +++ src/src/DataStructs/DeviceStruct.h | 21 ++++- src/src/DataStructs/SettingsStruct.h | 14 +++- src/src/DataStructs_templ/SettingsStruct.cpp | 36 ++++++++- src/src/Helpers/ESPEasy_FactoryDefault.cpp | 2 +- src/src/WebServer/DevicesPage.cpp | 58 ++++++++------ src/src/WebServer/DevicesPage.h | 9 +++ src/src/WebServer/HardwarePage.cpp | 83 ++++++++++++++++++++ src/src/WebServer/I2C_Scanner.cpp | 12 ++- src/src/WebServer/Markup_Forms.cpp | 3 + 10 files changed, 219 insertions(+), 32 deletions(-) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 9a6a2bc2f2..7bb3660f74 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3769,6 +3769,19 @@ To create/register a plugin, you have to : #endif #endif // if FEATURE_TASKVALUE_ATTRIBUTES +#ifndef FEATURE_EEPROM_EXTERNAL + #ifdef ESP32 + #define FEATURE_EEPROM_EXTERNAL 1 + #endif + #ifdef ESP8266 + #ifdef LIMIT_BUILD_SIZE + #define FEATURE_EEPROM_EXTERNAL 0 // Disabled for limited builds on ESP8266 + #else + #define FEATURE_EEPROM_EXTERNAL 1 // Enabled by default on ESP8266 + #endif + #endif +#endif // ifndef FEATURE_EEPROM_EXTERNAL + //-------------------HTTPResponseParser Section---------------- #ifndef FEATURE_THINGSPEAK_EVENT #if defined(PLUGIN_BUILD_MAX_ESP32) diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 09385a420a..6fd3a2c0d6 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -44,9 +44,28 @@ #define I2C_PERIPHERAL_BUS_CLOCK 0 // bit-offset for I2C bus used for the RTC clock device #define I2C_PERIPHERAL_BUS_WDT 3 // bit-offset for I2C bus used for the watchdog timer #define I2C_PERIPHERAL_BUS_PCFMCP 6 // bit-offset for I2C bus used for PCF & MCP direct access -// #define I2C_PERIPHERAL_BUS_??? 9 // bit-offset for I2C bus used for the ??? +#if FEATURE_EEPROM_EXTERNAL +#define I2C_PERIPHERAL_BUS_EEPROM 9 // bit-offset for I2C bus used for an external EEPROM +#endif // if FEATURE_EEPROM_EXTERNAL +// #define I2C_PERIPHERAL_BUS_??? 12 // bit-offset for I2C bus used for the ??? #endif // if FEATURE_I2C_MULTIPLE +#if FEATURE_EEPROM_EXTERNAL +#define EEPROM_EXTERNAL_FLAGS_ADDRESS 0 // bit-offset for the I2C Address (8 bits) +#define EEPROM_EXTERNAL_FLAGS_SIZE 8 // bit-offset for the size-id of the EEPROM (3 bits) +#define EEPROM_EXTERNAL_FLAGS_MUX 16 // bit-offset for the multiplexer flags of the EEPROM (16 bits) + +#define EEPROM_MUX_FLAGS_PORT 0 // bit-offset within multiplexerflags for the portnr/bits (8 bits) +#define EEPROM_MUX_FLAGS_MULTI 8 // bit-offset within multiplexerflags for bits or port (1 bit) + +enum EEPROMExternal_Type_e : uint8_t { + AT24C64 = 0, // + AT24C128 = 1, // + AT24C256 = 2, // + AT24C512 = 3, // + AT24C1024 = 4, // +}; +#endif // if FEATURE_EEPROM_EXTERNAL /*********************************************************************************************\ * DeviceStruct diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 8c2a31699e..20292a8504 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -343,8 +343,20 @@ class SettingsStruct_tmpl uint8_t getI2CInterfaceRTC() const; uint8_t getI2CInterfaceWDT() const; uint8_t getI2CInterfacePCFMCP() const; + #if FEATURE_EEPROM_EXTERNAL + uint8_t getI2CInterfaceEEPROM() const; + #endif // if FEATURE_EEPROM_EXTERNAL #endif // if FEATURE_I2C_MULTIPLE + #if FEATURE_EEPROM_EXTERNAL + uint8_t EEPROMExternalI2CAddress() const; + void EEPROMExternalI2CAddress(uint8_t address); + uint16_t EEPROMExternalI2CMultiplexerFlags() const; + void EEPROMExternalI2CMultiplexerFlags(uint16_t muxFlags); + uint8_t EEPROMExternalSize() const; + void EEPROMExternalSize(uint8_t size); + #endif + #if FEATURE_I2CMULTIPLEXER int8_t getI2CMultiplexerType(uint8_t i2cBus) const; int8_t getI2CMultiplexerAddr(uint8_t i2cBus) const; @@ -414,7 +426,7 @@ class SettingsStruct_tmpl uint8_t WebLogLevel = 0; uint8_t SDLogLevel = 0; unsigned long BaudRate = 115200; - unsigned long MessageDelay_unused = 0; // MQTT settings now moved to the controller settings. + unsigned long EEPROMExternalFlags = 0; uint8_t deepSleep_wakeTime = 0; // 0 = Sleep Disabled, else time awake from sleep in seconds boolean CustomCSS = false; boolean DST = false; diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index 060b702784..a7b1e6939f 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -658,7 +658,7 @@ void SettingsStruct_tmpl::clearMisc() { # endif // ifdef ESP32 } BaudRate = DEFAULT_SERIAL_BAUD; - MessageDelay_unused = 0; + EEPROMExternalFlags = 0; deepSleep_wakeTime = 0; CustomCSS = false; WDI2CAddress = 0; @@ -1132,8 +1132,42 @@ template uint8_t SettingsStruct_tmpl::getI2CInterfacePCFMCP() const { return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_PCFMCP); } + +#if FEATURE_EEPROM_EXTERNAL +template +uint8_t SettingsStruct_tmpl::getI2CInterfaceEEPROM() const { + return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_EEPROM); +} +#endif // if FEATURE_EEPROM_EXTERNAL #endif // if FEATURE_I2C_MULTIPLE +#if FEATURE_EEPROM_EXTERNAL +template +uint8_t SettingsStruct_tmpl::EEPROMExternalI2CAddress() const { + return get8BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_ADDRESS); +} +template +void SettingsStruct_tmpl::EEPROMExternalI2CAddress(uint8_t address) { + set8BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_ADDRESS, address); +} +template +uint16_t SettingsStruct_tmpl::EEPROMExternalI2CMultiplexerFlags() const { + return get16BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_MUX); +} +template +void SettingsStruct_tmpl::EEPROMExternalI2CMultiplexerFlags(uint16_t muxFlags) { + set16BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_MUX, muxFlags); +} +template +uint8_t SettingsStruct_tmpl::EEPROMExternalSize() const { + return static_cast(get3BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE)); +} +template +void SettingsStruct_tmpl::EEPROMExternalSize(uint8_t size) { + set3BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE, static_cast(size)); +} +#endif // if FEATURE_EEPROM_EXTERNAL + #if FEATURE_I2CMULTIPLEXER template int8_t SettingsStruct_tmpl::getI2CMultiplexerType(uint8_t i2cBus) const { diff --git a/src/src/Helpers/ESPEasy_FactoryDefault.cpp b/src/src/Helpers/ESPEasy_FactoryDefault.cpp index 77a98564da..cc9da7c8eb 100644 --- a/src/src/Helpers/ESPEasy_FactoryDefault.cpp +++ b/src/src/Helpers/ESPEasy_FactoryDefault.cpp @@ -294,7 +294,7 @@ void ResetFactory(bool formatFS) // Settings.UseRules = DEFAULT_USE_RULES; Settings.ControllerEnabled[0] = DEFAULT_CONTROLLER_ENABLED; Settings.MQTTRetainFlag_unused = DEFAULT_MQTT_RETAIN; - Settings.MessageDelay_unused = DEFAULT_MQTT_DELAY; + // Settings.MessageDelay_unused = DEFAULT_MQTT_DELAY; Settings.MQTTUseUnitNameAsClientId_unused = DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID; // allow to set default latitude and longitude diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 35c3b0435d..70a2fc6c21 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -298,22 +298,11 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task #endif // if FEATURE_I2C_MULTIPLE # if FEATURE_I2CMULTIPLEXER - - if (isI2CMultiplexerEnabled(i2cBus)) { - int multipleMuxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0); - bitWrite(flags, I2C_FLAGS_MUX_MULTICHANNEL, multipleMuxPortsOption == 1); - - if (multipleMuxPortsOption == 1) { - uint8_t selectedPorts = 0; - - for (int x = 0; x < I2CMultiplexerMaxChannels(i2cBus); ++x) { - bitWrite(selectedPorts, x, isFormItemChecked(concat(F("taskdeviceflag1ch"), x))); - } - Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; - } else { - Settings.I2C_Multiplexer_Channel[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); - } - } + bool muxPortsOption{}; + int selectedPorts{}; + GetI2CMultiplexerFromPage(i2cBus, muxPortsOption, selectedPorts); + bitWrite(flags, I2C_FLAGS_MUX_MULTICHANNEL, muxPortsOption); + Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; # endif // if FEATURE_I2CMULTIPLEXER @@ -1393,10 +1382,16 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex } #endif // if FEATURE_I2C_MULTIPLE # if FEATURE_I2CMULTIPLEXER + ShowI2CMultiplexerUI(i2cBus, + bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL), + Settings.I2C_Multiplexer_Channel[taskIndex]); + # endif // if FEATURE_I2CMULTIPLEXER +} +# if FEATURE_I2CMULTIPLEXER +void ShowI2CMultiplexerUI(uint8_t i2cBus, bool muxPortsOption, int taskDeviceI2CMuxPort) { // Show selector for an I2C multiplexer port if a multiplexer is configured if (isI2CMultiplexerEnabled(i2cBus)) { - bool multipleMuxPorts = bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL); { const __FlashStringHelper *i2c_mux_channels[] = { F("Single channel"), @@ -1404,8 +1399,8 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex constexpr int i2c_mux_channelOptions[] = { 0, 1 }; int i2c_mux_channelCount = 1; - if (Settings.I2C_Multiplexer_Type == I2C_MULTIPLEXER_PCA9540) { - multipleMuxPorts = false; // force off + if (Settings.getI2CMultiplexerType(i2cBus) == I2C_MULTIPLEXER_PCA9540) { + muxPortsOption = false; // force off } else { i2c_mux_channelCount++; } @@ -1417,10 +1412,10 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex selector.addFormSelector( F("Multiplexer channels"), F("taskdeviceflags1"), - multipleMuxPorts ? 1 : 0); + muxPortsOption ? 1 : 0); } - if (multipleMuxPorts) { + if (muxPortsOption) { addRowLabel(F("Select connections"), EMPTY_STRING); html_table(EMPTY_STRING, false); // Sub-table html_table_header(F("Channel"), 100); @@ -1433,11 +1428,10 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex html_TD(); addHtml(concat(F("Channel "), x)); html_TD(); - addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(Settings.I2C_Multiplexer_Channel[taskIndex], x), false); + addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(taskDeviceI2CMuxPort, x), false); } html_end_table(); } else { - int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Channel[taskIndex]; const uint32_t mux_max = I2CMultiplexerMaxChannels(i2cBus); String i2c_mux_portoptions[mux_max + 1]; int i2c_mux_portchoices[mux_max + 1]; @@ -1461,9 +1455,25 @@ void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex taskDeviceI2CMuxPort); } } - # endif // if FEATURE_I2CMULTIPLEXER } +void GetI2CMultiplexerFromPage(uint8_t i2cBus, bool &muxPortsOption, int &selectedPorts) { + if (isI2CMultiplexerEnabled(i2cBus)) { + muxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0) == 1; + + if (muxPortsOption) { + selectedPorts = 0; + + for (int x = 0; x < I2CMultiplexerMaxChannels(i2cBus); ++x) { + bitWrite(selectedPorts, x, isFormItemChecked(concat(F("taskdeviceflag1ch"), x))); + } + } else { + selectedPorts = getFormItemInt(F("taskdevicei2cmuxport"), -1); + } + } +} +# endif // if FEATURE_I2CMULTIPLEXER + void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { struct EventStruct TempEvent(taskIndex); diff --git a/src/src/WebServer/DevicesPage.h b/src/src/WebServer/DevicesPage.h index 8b7bece77b..7ad5690c9a 100644 --- a/src/src/WebServer/DevicesPage.h +++ b/src/src/WebServer/DevicesPage.h @@ -58,6 +58,15 @@ void devicePage_show_serial_config(taskIndex_t taskIndex); void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); +# if FEATURE_I2CMULTIPLEXER +void ShowI2CMultiplexerUI(uint8_t i2cBus, + bool muxPortsOption, + int taskDeviceI2CMuxPort); +void GetI2CMultiplexerFromPage(uint8_t i2cBus, + bool &muxPortsOption, + int &selectedPorts); +#endif // if FEATURE_I2CMULTIPLEXER + void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); #if FEATURE_PLUGIN_STATS diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index 32e4d1a58e..c10a2330f1 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -25,6 +25,10 @@ #include "../Helpers/Hardware_device_info.h" #endif // if FEATURE_I2C_MULTIPLE +#if FEATURE_EEPROM_EXTERNAL +#include "../WebServer/DevicesPage.h" // For using ShowI2CMultiplexerUI() and GetI2CMultiplexerFromPage() +#endif // if FEATURE_EEPROM_EXTERNAL + // ******************************************************************************** // Web Interface hardware page // ******************************************************************************** @@ -38,6 +42,8 @@ void handle_hardware() { TXBuffer.startStream(); sendHeadandTail_stdtemplate(_HEAD); + html_add_form(); + if (isFormItem(F("pled"))) { String error; Settings.Pin_status_led = getFormItemInt(F("pled")); @@ -106,7 +112,36 @@ void handle_hardware() { } #endif // if FEATURE_I2C_INTERFACE_3 set3BitToUL(Settings.I2C_peripheral_bus, I2C_PERIPHERAL_BUS_PCFMCP, getFormItemInt(F("pi2cbuspcf"))); + + // EEPROM settings + # if FEATURE_EEPROM_EXTERNAL + const uint8_t i2cBus = getFormItemInt(F("pi2cbuseeprom"), 0); + set3BitToUL(Settings.I2C_peripheral_bus, I2C_PERIPHERAL_BUS_EEPROM, i2cBus); + # endif // if FEATURE_EEPROM_EXTERNAL + #endif // if FEATURE_I2C_MULTIPLE + + #if FEATURE_I2CMULTIPLEXER && !FEATURE_I2C_MULTIPLE && FEATURE_EEPROM_EXTERNAL + constexpr uint8_t i2cBus = 0; + #endif // if FEATURE_I2CMULTIPLEXER && !FEATURE_I2C_MULTIPLE && FEATURE_EEPROM_EXTERNAL + + #if FEATURE_EEPROM_EXTERNAL + Settings.EEPROMExternalSize(getFormItemInt(F("eepromtype"), static_cast(EEPROMExternal_Type_e::AT24C256))); + Settings.EEPROMExternalI2CAddress(getFormItemInt(F("i2c_eeprom"), 0)); + + # if FEATURE_I2CMULTIPLEXER + + bool muxPortsOption{}; + int selectedPorts{}; + GetI2CMultiplexerFromPage(i2cBus, muxPortsOption, selectedPorts); + uint16_t muxFlags{}; + bitWrite(muxFlags, EEPROM_MUX_FLAGS_MULTI, muxPortsOption); + set8BitToUL(muxFlags, EEPROM_MUX_FLAGS_PORT, selectedPorts); + Settings.EEPROMExternalI2CMultiplexerFlags(muxFlags); + # endif // if FEATURE_I2CMULTIPLEXER + + #endif // if FEATURE_EEPROM_EXTERNAL + #ifdef ESP32 Settings.InitSPI = getFormItemInt(F("initspi"), static_cast(SPI_Options_e::None)); if (Settings.InitSPI == static_cast(SPI_Options_e::UserDefined)) { // User-define SPI GPIO pins @@ -280,6 +315,54 @@ void handle_hardware() { } #endif // if FEATURE_I2C_MULTIPLE + #if FEATURE_EEPROM_EXTERNAL + { + addFormSubHeader(F("External I2C EEPROM")); + const __FlashStringHelper*eepromOptions[] = { + F("AT24C128"), + F("AT24C256"), + F("AT24C512"), + F("AT24C1024"), + }; + const int eepromTypes[] = { + static_cast(EEPROMExternal_Type_e::AT24C128), + static_cast(EEPROMExternal_Type_e::AT24C256), + static_cast(EEPROMExternal_Type_e::AT24C512), + static_cast(EEPROMExternal_Type_e::AT24C1024), + }; + constexpr uint8_t eepromSizeCount = NR_ELEMENTS(eepromTypes); + FormSelectorOptions eepromSizeSelector(eepromSizeCount, eepromOptions, eepromTypes); + eepromSizeSelector.addFormSelector(F("EEPROM Model/size"), F("eepromtype"), Settings.EEPROMExternalSize()); + + const uint8_t i2cAddressValues[] = { 0, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57 }; + constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); + + addFormSelectorI2C(F("i2c_eeprom"), nrAddressOptions, i2cAddressValues, Settings.EEPROMExternalI2CAddress()); + + #if FEATURE_I2C_MULTIPLE + const uint8_t i2cBus = Settings.getI2CInterfaceEEPROM(); + if (i2cMaxBusCount > 1) { + I2CInterfaceSelector(F("I2C Bus"), + F("pi2cbuseeprom"), + i2cBus, + false); + + } + #endif // if FEATURE_I2C_MULTIPLE + + #if FEATURE_I2CMULTIPLEXER && !FEATURE_I2C_MULTIPLE && FEATURE_EEPROM_EXTERNAL + constexpr uint8_t i2cBus = 0; + #endif // if FEATURE_I2CMULTIPLEXER && !FEATURE_I2C_MULTIPLE && FEATURE_EEPROM_EXTERNAL + + #if FEATURE_I2CMULTIPLEXER + const uint16_t eepromMux = Settings.EEPROMExternalI2CMultiplexerFlags(); + ShowI2CMultiplexerUI(i2cBus, + bitRead(eepromMux, EEPROM_MUX_FLAGS_MULTI), + get8BitFromUL(eepromMux, EEPROM_MUX_FLAGS_PORT)); // Re-used from DevicesPage + #endif // if FEATURE_I2CMULTIPLEXER + } + #endif // if FEATURE_EEPROM_EXTERNAL + // SPI Init addFormSubHeader(F("SPI Interface")); #ifdef ESP32 diff --git a/src/src/WebServer/I2C_Scanner.cpp b/src/src/WebServer/I2C_Scanner.cpp index 7ca8c567eb..9e1759d9bd 100644 --- a/src/src/WebServer/I2C_Scanner.cpp +++ b/src/src/WebServer/I2C_Scanner.cpp @@ -315,19 +315,23 @@ String getKnownI2Cdevice(uint8_t address) { case 0x4D: result += F("PCF8591,MCP3221,LM75A,INA219"); break; + case 0x50: + case 0x52: + result += F("AT24Cxx"); + break; case 0x51: - result += F("PCF8563"); + result += F("PCF8563,AT24Cxx"); break; case 0x53: - result += F("ADXL345,LTR390"); + result += F("ADXL345,LTR390,AT24Cxx"); break; case 0x55: - result += F("DFRobot Rotary enc,BeFlE Moisture"); + result += F("DFRobot Rotary enc,BeFlE Moisture,AT24Cxx"); break; case 0x54: case 0x56: case 0x57: - result += F("DFRobot Rotary enc"); + result += F("DFRobot Rotary enc,AT24Cxx"); break; case 0x58: result += F("SGP30,GP8403"); diff --git a/src/src/WebServer/Markup_Forms.cpp b/src/src/WebServer/Markup_Forms.cpp index 4ab8d9b338..00e9c69dd6 100644 --- a/src/src/WebServer/Markup_Forms.cpp +++ b/src/src/WebServer/Markup_Forms.cpp @@ -504,6 +504,9 @@ void addFormSelectorI2C(const String& id, { String option = formatToHex_decimal(addresses[x]); + if (0 == addresses[x]) { + option = F("Disabled"); + } else if (((x == 0) && (defaultAddress == 0)) || (defaultAddress == addresses[x])) { option += F(" (default)"); } From b3102012221c74fcb4fd3ee276e3f1cc8e452da5 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 17 Aug 2025 22:36:40 +0200 Subject: [PATCH 02/44] [Storage] Add settings for External EEPROM --- src/src/Helpers/Misc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/src/Helpers/Misc.h b/src/src/Helpers/Misc.h index 0f94ffad4c..c3123f0b79 100644 --- a/src/src/Helpers/Misc.h +++ b/src/src/Helpers/Misc.h @@ -19,11 +19,13 @@ #define setNBitToUL(N, B, V, M) N=(((N) & ~((M) << (B))) | (static_cast((V) & (M)) << (B))) #define getNBitFromUL(number, bitnr, mask) (((number) >> (bitnr)) & (mask)) +#define set16BitToUL(N, B, V) setNBitToUL(N, B, V, 0xFFFFUL) #define set8BitToUL(N, B, V) setNBitToUL(N, B, V, 0xFFUL) #define set4BitToUL(N, B, V) setNBitToUL(N, B, V, 0x0FUL) #define set3BitToUL(N, B, V) setNBitToUL(N, B, V, 0x07UL) #define set2BitToUL(N, B, V) setNBitToUL(N, B, V, 0x03UL) +#define get16BitFromUL(number, bitnr) getNBitFromUL(number, bitnr, 0xFFFFUL) #define get8BitFromUL(number, bitnr) getNBitFromUL(number, bitnr, 0xFFUL) #define get4BitFromUL(number, bitnr) getNBitFromUL(number, bitnr, 0x0FUL) #define get3BitFromUL(number, bitnr) getNBitFromUL(number, bitnr, 0x07UL) From e3864f18916f96c2076259926fa18c7cd9122b34 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 18 Aug 2025 22:33:33 +0200 Subject: [PATCH 03/44] [Storage] Implement initializing EEPROM and writing UserVars, several improvements --- lib/AT24Cx/AT24CX.cpp | 345 ++++++++++++++++++ lib/AT24Cx/AT24CX.h | 122 +++++++ lib/AT24Cx/AT24CX_demo/AT24CX_demo.ino | 128 +++++++ .../AT24CX_search_IC/AT24CX_search_IC.ino | 40 ++ lib/AT24Cx/LICENSE | 21 ++ lib/AT24Cx/LIZENZ | 25 ++ lib/AT24Cx/README.md | 61 ++++ src/src/DataStructs/DeviceStruct.h | 7 - src/src/DataStructs/SettingsStruct.h | 4 +- src/src/DataStructs_templ/SettingsStruct.cpp | 6 +- src/src/Helpers/EEPROMExternal.cpp | 114 ++++++ src/src/Helpers/EEPROMExternal.h | 50 +++ src/src/Helpers/ESPEasyRTC.cpp | 84 ++++- src/src/Helpers/ESPEasyRTC.h | 1 + src/src/Helpers/Hardware_I2C.cpp | 72 ++++ src/src/Helpers/Hardware_I2C.h | 5 + src/src/WebServer/HardwarePage.cpp | 29 +- 17 files changed, 1092 insertions(+), 22 deletions(-) create mode 100644 lib/AT24Cx/AT24CX.cpp create mode 100644 lib/AT24Cx/AT24CX.h create mode 100644 lib/AT24Cx/AT24CX_demo/AT24CX_demo.ino create mode 100644 lib/AT24Cx/AT24CX_search_IC/AT24CX_search_IC.ino create mode 100644 lib/AT24Cx/LICENSE create mode 100644 lib/AT24Cx/LIZENZ create mode 100644 lib/AT24Cx/README.md create mode 100644 src/src/Helpers/EEPROMExternal.cpp create mode 100644 src/src/Helpers/EEPROMExternal.h diff --git a/lib/AT24Cx/AT24CX.cpp b/lib/AT24Cx/AT24CX.cpp new file mode 100644 index 0000000000..bca40187cd --- /dev/null +++ b/lib/AT24Cx/AT24CX.cpp @@ -0,0 +1,345 @@ +/** + +AT24CX.cpp +Library for using the EEPROM AT24C32/64 + +Copyright (c) 2014 Christian Paul + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + */ +#include "AT24CX.h" +#include + +/** + * Constructor with AT24Cx EEPROM at index 0 + */ +AT24CX::AT24CX() { + init(0, 32, 4096); +} + +/** + * Constructor with AT24Cx EEPROM at given index, size of page and max size in bytes + */ +AT24CX::AT24CX(uint8_t index, uint8_t pageSize, uint32_t maxSize) { + init(index, pageSize, maxSize); +} + +/** + * Constructor with AT24C32 EEPROM at index 0 + */ +AT24C32::AT24C32() { + init(0, 32, 4096); +} +/** + * Constructor with AT24Cx EEPROM at given index + */ +AT24C32::AT24C32(uint8_t index) { + init(index, 32, 4096); +} + +/** + * Constructor with AT24C64 EEPROM at index 0 + */ +AT24C64::AT24C64() { + init(0, 32, 8192); +} +/** + * Constructor with AT24C64 EEPROM at given index + */ +AT24C64::AT24C64(uint8_t index) { + init(index, 32, 8192); +} + +/** + * Constructor with AT24C128 EEPROM at index 0 + */ +AT24C128::AT24C128() { + init(0, 64, 16384); +} +/** + * Constructor with AT24C128 EEPROM at given index + */ +AT24C128::AT24C128(uint8_t index) { + init(index, 64, 16384); +} + +/** + * Constructor with AT24C256 EEPROM at index 0 + */ +AT24C256::AT24C256() { + init(0, 64, 32768); +} +/** + * Constructor with AT24C128 EEPROM at given index + */ +AT24C256::AT24C256(uint8_t index) { + init(index, 64, 32768); +} + +/** + * Constructor with AT24C512 EEPROM at index 0 + */ +AT24C512::AT24C512() { + init(0, 128, 65536); +} +/** + * Constructor with AT24C512 EEPROM at given index + */ +AT24C512::AT24C512(uint8_t index) { + init(index, 128, 65536); +} + +/** + * Init + */ +void AT24CX::init(uint8_t index, uint8_t pageSize, uint32_t maxSize) { + _id = AT24CX_ID | (index & 0x7); + _pageSize = pageSize; + _maxSize = maxSize; + // Wire.begin(); +} + +/** + * Check address not reached + */ +bool AT24CX::checkSize(uint32_t address, uint32_t size) { + return (address + size - 1) <= _maxSize; +} + +/** + * Write byte + */ +void AT24CX::write(uint32_t address, uint8_t data) { + if (!checkSize(address, 1)) { + return; + } + Wire.beginTransmission(_id); + if(Wire.endTransmission()==0) { + Wire.beginTransmission(_id); + Wire.write(address >> 8); + Wire.write(address & 0xFF); + Wire.write(data); + Wire.endTransmission(); + delay(20); + } +} + +/** + * Write integer + */ +void AT24CX::writeInt(uint32_t address, uint16_t data) { + write(address, (uint8_t*)&data, 2); +} + +/** + * Write long + */ +void AT24CX::writeLong(uint32_t address, uint32_t data) { + write(address, (uint8_t*)&data, 4); +} + +/** + * Write float + */ +void AT24CX::writeFloat(uint32_t address, float data) { + write(address, (uint8_t*)&data, 4); +} + +/** + * Write double + */ +void AT24CX::writeDouble(uint32_t address, double data) { + write(address, (uint8_t*)&data, 8); +} + +/** + * Write chars + */ +void AT24CX::writeChars(uint32_t address, char *data, int length) { + write(address, (uint8_t*)data, length); +} + +/** + * Write bytes + */ +void AT24CX::writeBytes(uint32_t address, uint8_t *data, int length) { + write(address, data, length); +} + +/** + * Read integer + */ +uint16_t AT24CX::readInt(uint32_t address) { + memset(_b, 0, sizeof(_b)); + read(address, _b, 2); + return *(uint32_t*)&_b[0]; +} + +/** + * Read long + */ +uint32_t AT24CX::readLong(uint32_t address) { + read(address, _b, 4); + return *(unsigned long*)&_b[0]; +} + +/** + * Read float + */ +float AT24CX::readFloat(uint32_t address) { + read(address, _b, 4); + return *(float*)&_b[0]; +} + +/** + * Read double + */ +double AT24CX::readDouble(uint32_t address) { + read(address, _b, 8); + return *(double*)&_b[0]; +} + +/** + * Read chars + */ +void AT24CX::readChars(uint32_t address, char *data, int n) { + read(address, (uint8_t*)data, n); +} + +/** + * Read bytes + */ +void AT24CX::readBytes(uint32_t address, uint8_t *data, int n) { + read(address, data, n); +} + +/** + * Write sequence of n bytes + */ +void AT24CX::write(uint32_t address, uint8_t *data, int n) { + if (!checkSize(address, n)) { + return; + } + // status quo + int c = n; // bytes left to write + int offD = 0; // current offset in data pointer + int offP; // current offset in page + int nc = 0; // next n bytes to write + + // write alle bytes in multiple steps + while (c > 0) { + // calc offset in page + offP = address % _pageSize; + // maximal 30 bytes to write + nc = min(min(c, 30), _pageSize - offP); + write(address, data, offD, nc); + c-=nc; + offD+=nc; + address+=nc; + } +} + +/** + * Write sequence of n bytes from offset + */ +void AT24CX::write(uint32_t address, uint8_t *data, int offset, int n) { + if (!checkSize(address, n)) { + return; + } + Wire.beginTransmission(_id); + if (Wire.endTransmission()==0) { + Wire.beginTransmission(_id); + Wire.write(address >> 8); + Wire.write(address & 0xFF); + uint8_t *adr = data+offset; + Wire.write(adr, n); + Wire.endTransmission(); + delay(20); + } +} + +/** + * Read byte + */ +uint8_t AT24CX::read(uint32_t address) { + if (!checkSize(address, 1)) { + return 0; + } + uint8_t b = 0; + int r = 0; + Wire.beginTransmission(_id); + if (Wire.endTransmission()==0) { + Wire.beginTransmission(_id); + Wire.write(address >> 8); + Wire.write(address & 0xFF); + if (Wire.endTransmission()==0) { + Wire.requestFrom(_id, 1); + while (Wire.available() > 0 && r<1) { + b = (uint8_t)Wire.read(); + r++; + } + } + } + return b; +} + +/** + * Read sequence of n bytes + */ +void AT24CX::read(uint32_t address, uint8_t *data, int n) { + if (!checkSize(address, n)) { + return; + } + int c = n; + int offD = 0; + // read until are n bytes read + while (c > 0) { + // read maximal 32 bytes + int nc = c; + if (nc > 32) + nc = 32; + read(address, data, offD, nc); + address+=nc; + offD+=nc; + c-=nc; + } +} + + +/** + * Read sequence of n bytes to offset + */ +void AT24CX::read(uint32_t address, uint8_t *data, int offset, int n) { + Wire.beginTransmission(_id); + if (Wire.endTransmission()==0) { + Wire.beginTransmission(_id); + Wire.write(address >> 8); + Wire.write(address & 0xFF); + if (Wire.endTransmission()==0) { + int r = 0; + Wire.requestFrom(_id, n); + while (Wire.available() > 0 && r +#include + +// // byte +// typedef uint8_t byte; + +// AT24Cx I2C adress +// 80 +// 0x50 +#define AT24CX_ID 0b01010000 + +// general class definition +class AT24CX { +public: + AT24CX(); + AT24CX(uint8_t index, uint8_t pageSize, uint32_t maxSize); + void write(uint32_t address, uint8_t data); + void write(uint32_t address, uint8_t *data, int n); + void writeInt(uint32_t address, uint16_t data); + void writeLong(uint32_t address, uint32_t data); + void writeFloat(uint32_t address, float data); + void writeDouble(uint32_t address, double data); + void writeChars(uint32_t address, char *data, int length); + void writeBytes(uint32_t address, uint8_t *data, int length); + uint8_t read(uint32_t address); + void read(uint32_t address, uint8_t *data, int n); + uint16_t readInt(uint32_t address); + uint32_t readLong(uint32_t address); + float readFloat(uint32_t address); + double readDouble(uint32_t address); + void readChars(uint32_t address, char *data, int n); + void readBytes(uint32_t address, uint8_t *data, int n); +protected: + void init(uint8_t index, uint8_t pageSize, uint32_t maxSize); +private: + void read(uint32_t address, uint8_t *data, int offset, int n); + void write(uint32_t address, uint8_t *data, int offset, int n); + bool checkSize(uint32_t address, uint32_t size); + int _id; + uint8_t _b[8]; + uint8_t _pageSize; + uint32_t _maxSize{}; +}; + +// AT24C32 class definiton +class AT24C32 : public AT24CX { +public: + AT24C32(); + AT24C32(uint8_t index); +}; + +// AT24C64 class definiton +class AT24C64 : public AT24CX { +public: + AT24C64(); + AT24C64(uint8_t index); +}; + +// AT24C128 class definiton +class AT24C128 : public AT24CX { +public: + AT24C128(); + AT24C128(uint8_t index); +}; + +// AT24C256 class definiton +class AT24C256 : public AT24CX { +public: + AT24C256(); + AT24C256(uint8_t index); +}; + +// AT24C512 class definiton +class AT24C512 : public AT24CX { +public: + AT24C512(); + AT24C512(uint8_t index); +}; + + + +#endif diff --git a/lib/AT24Cx/AT24CX_demo/AT24CX_demo.ino b/lib/AT24Cx/AT24CX_demo/AT24CX_demo.ino new file mode 100644 index 0000000000..b49cadab5c --- /dev/null +++ b/lib/AT24Cx/AT24CX_demo/AT24CX_demo.ino @@ -0,0 +1,128 @@ +/* +* +* Read and write demo of the AT24CX library +* Written by Christian Paul, 2014-11-24 +* +* +*/ + +// include libraries +#include +#include + +// EEPROM object +AT24CX mem; + +// setup +void setup() { + // serial init + Serial.begin(115200); + Serial.println("AT24CX read/write demo"); + Serial.println("----------------------"); +} + +// main loop +void loop() { + // read and write byte + Serial.println("Write 42 to address 12"); + mem.write(12, 42); + Serial.println("Read byte from address 12 ..."); + byte b = mem.read(12); + Serial.print("... read: "); + Serial.println(b, DEC); + Serial.println(); + + // read and write integer + Serial.println("Write 65000 to address 15"); + mem.writeInt(15, 65000); + Serial.println("Read integer from address 15 ..."); + unsigned int i = mem.readInt(15); + Serial.print("... read: "); + Serial.println(i, DEC); + Serial.println(); + + // read and write long + Serial.println("Write 3293732729 to address 20"); + mem.writeLong(20, 3293732729UL); + Serial.println("Read long from address 20 ..."); + unsigned long l = mem.readLong(20); + Serial.print("... read: "); + Serial.println(l, DEC); + Serial.println(); + + // read and write long + Serial.println("Write 1111111111 to address 31"); + mem.writeLong(31, 1111111111); + Serial.println("Read long from address 31 ..."); + unsigned long l2 = mem.readLong(31); + Serial.print("... read: "); + Serial.println(l2, DEC); + Serial.println(); + + // read and write float + Serial.println("Write 3.14 to address 40"); + mem.writeFloat(40, 3.14); + Serial.println("Read float from address 40 ..."); + float f = mem.readFloat(40); + Serial.print("... read: "); + Serial.println(f, DEC); + Serial.println(); + + // read and write double + Serial.println("Write 3.14159265359 to address 50"); + mem.writeDouble(50, 3.14159265359); + Serial.println("Read double from address 50 ..."); + double d = mem.readDouble(50); + Serial.print("... read: "); + Serial.println(d, DEC); + Serial.println(); + + // read and write char + Serial.print("Write chars: '"); + char msg[] = "This is a message"; + Serial.print(msg); + Serial.println("' to address 200"); + mem.writeChars(200, msg, sizeof(msg)); + Serial.println("Read chars from address 200 ..."); + char msg2[30]; + mem.readChars(200, msg2, sizeof(msg2)); + Serial.print("... read: '"); + Serial.print(msg2); + Serial.println("'"); + Serial.println(); + + // write array of bytes + Serial.println("Write array of 80 bytes at address 1000"); + byte xy[] = {0,0,0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9, // 10 x 3 = 30 + 10,11,12,13,14,15,16,17,18,19, // 10 + 120,121,122,123,124,125,126,127,128,129, // 10 + 130,131,132,133,134,135,136,137,138,139, // 10 + 200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219}; // 20 + mem.write(1000, (byte*)xy, sizeof(xy)); + + // read bytes with multiple steps + Serial.println("Read 80 single bytes starting at address 1000"); + for (int i=0; i for definitons and differences. + +Written by Christian Paul, 2014 - 2015. +This software is released under the terms of the MIT license. +See the file LICENSE or LIZENZ for details, please. + +You can use any of the eight possibles EEPROM devices on the I2C bus. + +Constructor + + AT24CX(byte pageSize); + +uses the device with index 0 and given page size. You can select a device with given index between 0 and 8 with constructor + + AT24CX(byte index, byte pageSize); + +Than, you can single write or read single bytes from the EEPROM with + + void write(unsigned int address, byte data); + byte read(unsigned int address); + +or write and read an array of bytes with + + void write(unsigned int address, byte *data, int n); + void read(unsigned int address, byte *data, int n); + +For writing integers, long, float, double or sequences of chars you can use the comfort functions + + void writeInt(unsigned int address, unsigned int data); + void writeLong(unsigned int address, unsigned long data); + void writeFloat(unsigned int address, float data); + void writeDouble(unsigned int address, double data); + void writeChars(unsigned int address, char *data, int length); + +Reading the values is done by using + + unsigned int readInt(unsigned int address); + unsigned long readLong(unsigned int address); + float readFloat(unsigned int address); + double readDouble(unsigned int address); + void readChars(unsigned int address, char *data, int n); + +Alternative you can use the individual classes with predefined page sizes: + + AT24C32(); + AT24C64(); + AT24C128(); + AT24C256(); + AT24C512(); + +or with different index than 0: + + AT24C32(byte index); + AT24C64(byte index); + AT24C128(byte index); + AT24C256(byte index); + AT24C512(byte index); + diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 6fd3a2c0d6..8762fcf8ce 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -58,13 +58,6 @@ #define EEPROM_MUX_FLAGS_PORT 0 // bit-offset within multiplexerflags for the portnr/bits (8 bits) #define EEPROM_MUX_FLAGS_MULTI 8 // bit-offset within multiplexerflags for bits or port (1 bit) -enum EEPROMExternal_Type_e : uint8_t { - AT24C64 = 0, // - AT24C128 = 1, // - AT24C256 = 2, // - AT24C512 = 3, // - AT24C1024 = 4, // -}; #endif // if FEATURE_EEPROM_EXTERNAL /*********************************************************************************************\ diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 20292a8504..6610f15516 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -353,8 +353,8 @@ class SettingsStruct_tmpl void EEPROMExternalI2CAddress(uint8_t address); uint16_t EEPROMExternalI2CMultiplexerFlags() const; void EEPROMExternalI2CMultiplexerFlags(uint16_t muxFlags); - uint8_t EEPROMExternalSize() const; - void EEPROMExternalSize(uint8_t size); + uint8_t EEPROMExternalType() const; + void EEPROMExternalType(uint8_t type); #endif #if FEATURE_I2CMULTIPLEXER diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index a7b1e6939f..7f64bb962f 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -1159,12 +1159,12 @@ void SettingsStruct_tmpl::EEPROMExternalI2CMultiplexerFlags(uint16_t mu set16BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_MUX, muxFlags); } template -uint8_t SettingsStruct_tmpl::EEPROMExternalSize() const { +uint8_t SettingsStruct_tmpl::EEPROMExternalType() const { return static_cast(get3BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE)); } template -void SettingsStruct_tmpl::EEPROMExternalSize(uint8_t size) { - set3BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE, static_cast(size)); +void SettingsStruct_tmpl::EEPROMExternalType(uint8_t type) { + set3BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE, type); } #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/src/Helpers/EEPROMExternal.cpp new file mode 100644 index 0000000000..3a05b2bfe0 --- /dev/null +++ b/src/src/Helpers/EEPROMExternal.cpp @@ -0,0 +1,114 @@ +#include "../Helpers/EEPROMExternal.h" +#include "../Globals/Settings.h" +#include "../Helpers/I2C_access.h" +#include "../../ESPEasy_common.h" + +#if FEATURE_EEPROM_EXTERNAL + +AT24CX *EEPROMExternal = nullptr; + +/** + * Switch to I2C Bus and multiplexer channel of External EEPROM + */ +uint8_t selectEEPROMI2CBusAndMultiplexer() { + const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); + + if (eepromAddress > 0) { // EEPROM Configured? + # if FEATURE_I2C_MULTIPLE + const uint8_t i2cBus = Settings.getI2CInterfaceEEPROM(); + # else // if FEATURE_I2C_MULTIPLE + constexpr uint8_t i2cBus = 0; + # endif // if FEATURE_I2C_MULTIPLE + + I2CSelectHighClockSpeed(i2cBus); + + # if FEATURE_I2CMULTIPLEXER + const uint16_t eepromFlags = Settings.EEPROMExternalI2CMultiplexerFlags(); + const int eepromMuxPort = get8BitFromUL(eepromFlags, EEPROM_MUX_FLAGS_PORT); + const bool eepromMulti = bitRead(eepromFlags, EEPROM_MUX_FLAGS_MULTI); + I2CMultiplexerSelectByBusAndMux(i2cBus, eepromMulti, eepromMuxPort); + # endif // if FEATURE_I2CMULTIPLEXER + + if (0 == I2C_wakeup(eepromAddress)) { + return eepromAddress; + } + } + return 0; +} + +uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { + switch (type) { + case EEPROMExternal_Type_e::AT24C256: + return 32768; + case EEPROMExternal_Type_e::AT24C512: + return 65536; + # if EEPROM_SUPPORT_AT24C1024 + case EEPROMExternal_Type_e::AT24C1024: + return 131072; + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::AT24C2048: + return 262144; + # endif // if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::AT24C32: + return 4096; + case EEPROMExternal_Type_e::AT24C64: + return 8192; + case EEPROMExternal_Type_e::AT24C128: + return 16384; + } + return 0; +} + +uint32_t getEEPROMSize(EEPROMExternal_Type_e type, + uint8_t & pageSize) { + pageSize = 0; + + switch (type) { + case EEPROMExternal_Type_e::AT24C256: + pageSize = 64; + case EEPROMExternal_Type_e::AT24C512: + pageSize = 128; + # if EEPROM_SUPPORT_AT24C1024 + case EEPROMExternal_Type_e::AT24C1024: + pageSize = 128; + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::AT24C2048: + pageSize = 128; + # endif // if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::AT24C32: + pageSize = 32; + case EEPROMExternal_Type_e::AT24C64: + pageSize = 32; + case EEPROMExternal_Type_e::AT24C128: + pageSize = 64; + } + return getEEPROMSize(type); +} + +const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { + switch (type) { + case EEPROMExternal_Type_e::AT24C256: + return F("AT24C256"); + case EEPROMExternal_Type_e::AT24C512: + return F("AT24C512"); + # if EEPROM_SUPPORT_AT24C1024 + case EEPROMExternal_Type_e::AT24C1024: + return F("AT24C1024"); + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::AT24C2048: + return F("AT24C2048"); + # endif // if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::AT24C32: + return F("AT24C32"); + case EEPROMExternal_Type_e::AT24C64: + return F("AT24C64"); + case EEPROMExternal_Type_e::AT24C128: + return F("AT24C128"); + } + return F(""); +} + +#endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/EEPROMExternal.h b/src/src/Helpers/EEPROMExternal.h new file mode 100644 index 0000000000..b0f71ec444 --- /dev/null +++ b/src/src/Helpers/EEPROMExternal.h @@ -0,0 +1,50 @@ +#pragma once +#include "../../ESPEasy_common.h" + +#if FEATURE_EEPROM_EXTERNAL + +# include + +extern AT24CX *EEPROMExternal; + +// Start writing the base RTC struct from this offset (not currently saving this to EEPROM) // TODO +# define EEPROM_BASERTC_START_OFFSET (0) + +// Start writing the UserVar values from this offset, should be > sizeof(RTCStruct) that is 32 currently +# define EEPROM_USERVAR_START_OFFSET (100) + +// Write the UserVar-checksum from this offset, right after the UserVar values +# define EEPROM_USERVAR_CHECKSUM_OFFSET (EEPROM_USERVAR_START_OFFSET + (TASKS_MAX * VARS_PER_TASK * sizeof(uint32_t))) + +// Offset for storing GPIO states // TODO +# define EEPROM_GPIO_MCPPCF_START_OFFSET (1024) + +// Choose an arbitrary but fixed offset +# define EEPROM_CUSTOM_START_OFFSET (2048) + +// Enable/disable some models +# define EEPROM_SUPPORT_AT24C1024 1 +# define EEPROM_SUPPORT_AT24C2048 0 + +// Supported AT24Cxxx devices +enum class EEPROMExternal_Type_e : uint8_t { + AT24C256 = 0, // Default, 32 kB + AT24C512 = 1, // 64 kB + # if EEPROM_SUPPORT_AT24C1024 + AT24C1024 = 2, // 128 kB + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 + AT24C2048 = 3, // 256 kB (not supported yet) + # endif // if EEPROM_SUPPORT_AT24C2048 + AT24C32 = 4, // 4 kB, not endorsed, but widely available types: + AT24C64 = 5, // 8 kB + AT24C128 = 6, // 16 kB +}; + +uint8_t selectEEPROMI2CBusAndMultiplexer(); + +uint32_t getEEPROMSize(EEPROMExternal_Type_e type); +uint32_t getEEPROMSize(EEPROMExternal_Type_e type, + uint8_t & pageSize); +const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type); +#endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 1c501d13cd..65a18d6486 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -12,6 +12,12 @@ #include "../Helpers/CRC_functions.h" #include "../../ESPEasy_common.h" +#if FEATURE_EEPROM_EXTERNAL +#include "../Helpers/EEPROMExternal.h" +#include "../Helpers/Hardware_I2C.h" +#include "../Helpers/StringConverter.h" +#endif // if FEATURE_EEPROM_EXTERNAL + #ifdef ESP8266 #include #endif @@ -81,6 +87,11 @@ // - RTCStruct to keep information on reboot reason, last used WiFi, etc. // - UserVar to keep task values persistent just like on ESP8266 +/** + * With EEPROMExternal (AT24cxxx) enabled and configured: + * - UserVar will be stored in external EEPROM + * - + */ @@ -134,7 +145,7 @@ void initRTC() saveToRTC(); UserVar.clear(); - saveUserVarToRTC(); + saveUserVarToRTC(true); } /********************************************************************************************\ @@ -158,8 +169,59 @@ bool readFromRTC() /********************************************************************************************\ Save values to RTC memory \*********************************************************************************************/ -bool saveUserVarToRTC() +bool saveUserVarToRTC() { + return saveUserVarToRTC(false); +} + +bool saveUserVarToRTC(bool initial) { + #if FEATURE_EEPROM_EXTERNAL + // Check if we have an external EEPROM available on the configured I2C bus & channel, and save all task values there + const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); + if (!initial && (nullptr != EEPROMExternal) && (eepromAddress > 0)) { // EEPROM Configured? + + if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + uint32_t eepromWritten{}; + uint32_t checksum = EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); + + if (UserVar.compute_CRC32() != checksum) { // Only save if data changed + for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { + const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); + if (taskValues != nullptr) { + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + const size_t index = (task * VARS_PER_TASK) + varNr; + EEPROMExternal->writeLong(EEPROM_USERVAR_START_OFFSET + (index * sizeof(uint32_t)), + taskValues->getUint32(varNr)); + eepromWritten += sizeof(uint32_t); + } + } + } + EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, + UserVar.compute_CRC32()); + eepromWritten += sizeof(uint32_t); + } + + #ifndef BUILD_NO_DEBUG + // if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { // FIXME + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); + addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: UserVar: %u bytes written to %s"), eepromWritten, FsP(getEEPROMName(eepromType)))); + } + #endif // ifndef BUILD_NO_DEBUG + + } + #if FEATURE_I2CMULTIPLEXER + I2CMultiplexerOff( + #if FEATURE_I2C_MULTIPLE + Settings.getI2CInterfaceEEPROM() + #else //if FEATURE_I2C_MULTIPLE + 0 + #endif // if FEATURE_I2C_MULTIPLE + ); // Restore the Multiplexer channel + #endif // if FEATURE_I2CMULTIPLEXER + } + #endif // if FEATURE_EEPROM_EXTERNAL + // ESP8266 has the RTC struct stored in memory which we must actively fetch // ESP32 Uses a temp structure which is mapped to the RTC address range. #if defined(ESP32) @@ -168,7 +230,6 @@ bool saveUserVarToRTC() if (taskValues != nullptr) { for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { const size_t index = (task * VARS_PER_TASK) + varNr; - constexpr bool raw = true; UserVar_RTC[index] = taskValues->getUint32(varNr); } } @@ -193,6 +254,23 @@ bool saveUserVarToRTC() \*********************************************************************************************/ bool readUserVarFromRTC() { + // const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); + // if ((nullptr != EEPROMExternal) && (eepromAddress > 0)) { // EEPROM Configured? + + // if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + // // TODO Check checksum and if correct, restore UserVar values + // } + // #if FEATURE_I2CMULTIPLEXER + // I2CMultiplexerOff( + // #if FEATURE_I2C_MULTIPLE + // Settings.getI2CInterfaceEEPROM() + // #else //if FEATURE_I2C_MULTIPLE + // 0 + // #endif // if FEATURE_I2C_MULTIPLE + // ); // Restore the Multiplexer channel + // #endif // if FEATURE_I2CMULTIPLEXER + // } + // ESP8266 has the RTC struct stored in memory which we must actively fetch // ESP32 Uses a temp structure which is mapped to the RTC address range. #if defined(ESP32) diff --git a/src/src/Helpers/ESPEasyRTC.h b/src/src/Helpers/ESPEasyRTC.h index d98182f83d..58bdf5269c 100644 --- a/src/src/Helpers/ESPEasyRTC.h +++ b/src/src/Helpers/ESPEasyRTC.h @@ -17,6 +17,7 @@ bool readFromRTC(); Save values to RTC memory \*********************************************************************************************/ bool saveUserVarToRTC(); +bool saveUserVarToRTC(bool initial); /********************************************************************************************\ Read RTC struct from RTC memory diff --git a/src/src/Helpers/Hardware_I2C.cpp b/src/src/Helpers/Hardware_I2C.cpp index 733ecb4445..34e31ac72a 100644 --- a/src/src/Helpers/Hardware_I2C.cpp +++ b/src/src/Helpers/Hardware_I2C.cpp @@ -7,6 +7,9 @@ #include "../Helpers/I2C_access.h" #include "../Helpers/StringConverter.h" +#if FEATURE_EEPROM_EXTERNAL +#include "../Helpers/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL #include @@ -81,6 +84,55 @@ void initI2C() { } } } + + #if FEATURE_EEPROM_EXTERNAL + const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); + + if ((nullptr != EEPROMExternal) && (eepromAddress == 0)) { // Cleanup when turning off EEPROM + delete EEPROMExternal; + EEPROMExternal = nullptr; + } + + if ((nullptr == EEPROMExternal) && (eepromAddress > 0)) { + const EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); + if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + // We have an I2C device at this address, let's assume it's an EEPROM... + uint8_t pageSize = 0; + const uint32_t eepromSize = getEEPROMSize(eepromType, pageSize); + EEPROMExternal = new (std::nothrow) AT24CX(eepromAddress, pageSize, eepromSize); + + if (nullptr != EEPROMExternal) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: %s initialized at address 0x%02x"), + FsP(getEEPROMName(eepromType)), + eepromAddress)); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat(F("EEPROM: Initialization of %s failed"), + FsP(getEEPROMName(eepromType)))); + } + } + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat(F("EEPROM: No %s found at address 0x%02x"), + FsP(getEEPROMName(eepromType)), + eepromAddress)); + } + } + + #if FEATURE_I2CMULTIPLEXER + I2CMultiplexerOff( + #if FEATURE_I2C_MULTIPLE + Settings.getI2CInterfaceEEPROM() + #else //if FEATURE_I2C_MULTIPLE + 0 + #endif // if FEATURE_I2C_MULTIPLE + ); // Restore the Multiplexer channel + #endif // if FEATURE_I2CMULTIPLEXER + } + #endif // if FEATURE_EEPROM_EXTERNAL + I2CSelectHighClockSpeed(0); // Select first interface by default } @@ -247,6 +299,26 @@ uint8_t I2CMultiplexerShiftBit(uint8_t i2cBus, uint8_t i) { return toWrite; } +void I2CMultiplexerSelectByBusAndMux(uint8_t i2cBus, bool singleMulti, int muxPort) { + uint8_t toWrite{}; + + if ((singleMulti && (muxPort > 0))|| + (!singleMulti && (muxPort > -1))) { + if (!singleMulti) { + uint8_t i = muxPort; + + if (i < 8) { + toWrite = I2CMultiplexerShiftBit(i2cBus, i); + } + } else { + toWrite = muxPort; // Bitpattern is already correctly stored + } + } + + SetI2CMultiplexer(i2cBus, toWrite); + +} + // As initially constructed by krikk in PR#254, quite adapted // utility method for the I2C multiplexer // select the multiplexer port given as parameter, if taskIndex < 0 then take that abs value as the port to select (to allow I2C scanner) diff --git a/src/src/Helpers/Hardware_I2C.h b/src/src/Helpers/Hardware_I2C.h index 22b3d3947b..3f060f9719 100644 --- a/src/src/Helpers/Hardware_I2C.h +++ b/src/src/Helpers/Hardware_I2C.h @@ -22,6 +22,11 @@ void I2CBegin(int8_t sda, #if FEATURE_I2CMULTIPLEXER bool isI2CMultiplexerEnabled(uint8_t i2cBus); +uint8_t I2CMultiplexerShiftBit(uint8_t i2cBus, uint8_t i); + +void I2CMultiplexerSelectByBusAndMux(uint8_t i2cBus, + bool singleMulti, + int muxPort); void I2CMultiplexerSelectByTaskIndex(taskIndex_t taskIndex); void I2CMultiplexerSelect(uint8_t i2cBus, uint8_t i); diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index c10a2330f1..dbae93a3b5 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -26,6 +26,7 @@ #endif // if FEATURE_I2C_MULTIPLE #if FEATURE_EEPROM_EXTERNAL +#include "../Helpers/EEPROMExternal.h" #include "../WebServer/DevicesPage.h" // For using ShowI2CMultiplexerUI() and GetI2CMultiplexerFromPage() #endif // if FEATURE_EEPROM_EXTERNAL @@ -126,7 +127,7 @@ void handle_hardware() { #endif // if FEATURE_I2CMULTIPLEXER && !FEATURE_I2C_MULTIPLE && FEATURE_EEPROM_EXTERNAL #if FEATURE_EEPROM_EXTERNAL - Settings.EEPROMExternalSize(getFormItemInt(F("eepromtype"), static_cast(EEPROMExternal_Type_e::AT24C256))); + Settings.EEPROMExternalType(getFormItemInt(F("eepromtype"), static_cast(EEPROMExternal_Type_e::AT24C256))); Settings.EEPROMExternalI2CAddress(getFormItemInt(F("i2c_eeprom"), 0)); # if FEATURE_I2CMULTIPLEXER @@ -319,20 +320,34 @@ void handle_hardware() { { addFormSubHeader(F("External I2C EEPROM")); const __FlashStringHelper*eepromOptions[] = { - F("AT24C128"), - F("AT24C256"), - F("AT24C512"), - F("AT24C1024"), + getEEPROMName(EEPROMExternal_Type_e::AT24C256), + getEEPROMName(EEPROMExternal_Type_e::AT24C512), + #if EEPROM_SUPPORT_AT24C1024 + getEEPROMName(EEPROMExternal_Type_e::AT24C1024), + #endif // if EEPROM_SUPPORT_AT24C1024 + #if EEPROM_SUPPORT_AT24C2048 + getEEPROMName(EEPROMExternal_Type_e::AT24C2048), + #endif // if EEPROM_SUPPORT_AT24C2048 + getEEPROMName(EEPROMExternal_Type_e::AT24C32), + getEEPROMName(EEPROMExternal_Type_e::AT24C64), + getEEPROMName(EEPROMExternal_Type_e::AT24C128), }; const int eepromTypes[] = { - static_cast(EEPROMExternal_Type_e::AT24C128), static_cast(EEPROMExternal_Type_e::AT24C256), static_cast(EEPROMExternal_Type_e::AT24C512), + #if EEPROM_SUPPORT_AT24C1024 static_cast(EEPROMExternal_Type_e::AT24C1024), + #endif // if EEPROM_SUPPORT_AT24C1024 + #if EEPROM_SUPPORT_AT24C2048 + static_cast(EEPROMExternal_Type_e::AT24C2048), + #endif // if EEPROM_SUPPORT_AT24C2048 + static_cast(EEPROMExternal_Type_e::AT24C32), + static_cast(EEPROMExternal_Type_e::AT24C64), + static_cast(EEPROMExternal_Type_e::AT24C128), }; constexpr uint8_t eepromSizeCount = NR_ELEMENTS(eepromTypes); FormSelectorOptions eepromSizeSelector(eepromSizeCount, eepromOptions, eepromTypes); - eepromSizeSelector.addFormSelector(F("EEPROM Model/size"), F("eepromtype"), Settings.EEPROMExternalSize()); + eepromSizeSelector.addFormSelector(F("EEPROM Model/size"), F("eepromtype"), Settings.EEPROMExternalType()); const uint8_t i2cAddressValues[] = { 0, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57 }; constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); From 97d1308cc8598bae66df7a3b8a2459c2cfaa9bb5 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 19 Aug 2025 13:16:12 +0200 Subject: [PATCH 04/44] [Storage] Reduce writing to only when data changes, add FRAM to I2C scanner, code optimizations --- src/src/Helpers/EEPROMExternal.cpp | 23 ++++++++++++++--------- src/src/Helpers/ESPEasyRTC.cpp | 20 +++++++++++++++----- src/src/WebServer/I2C_Scanner.cpp | 13 ++++++++----- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/src/Helpers/EEPROMExternal.cpp index 3a05b2bfe0..a1f31f2f5e 100644 --- a/src/src/Helpers/EEPROMExternal.cpp +++ b/src/src/Helpers/EEPROMExternal.cpp @@ -88,27 +88,32 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, } const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { + # ifndef BUILD_NO_DEBUG + switch (type) { case EEPROMExternal_Type_e::AT24C256: - return F("AT24C256"); + return F("AT24C256/MB85RC256"); case EEPROMExternal_Type_e::AT24C512: - return F("AT24C512"); - # if EEPROM_SUPPORT_AT24C1024 + return F("AT24C512/MB85RC512"); + # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: - return F("AT24C1024"); - # endif // if EEPROM_SUPPORT_AT24C1024 - # if EEPROM_SUPPORT_AT24C2048 + return F("AT24C1024/MB85RC1M"); + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: return F("AT24C2048"); - # endif // if EEPROM_SUPPORT_AT24C2048 + # endif // if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C32: return F("AT24C32"); case EEPROMExternal_Type_e::AT24C64: - return F("AT24C64"); + return F("AT24C64/MB85RC64"); case EEPROMExternal_Type_e::AT24C128: - return F("AT24C128"); + return F("AT24C128/MB85RC128"); } return F(""); + # else // ifndef BUILD_NO_DEBUG + return F("EEPROM/FRAM"); + # endif // ifndef BUILD_NO_DEBUG } #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 65a18d6486..cddac3bbd4 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -99,6 +99,7 @@ //#define RTC_STRUCT_DEBUG +constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); #ifdef ESP32 constexpr size_t UserVar_nrelements = VARS_PER_TASK * TASKS_MAX; @@ -181,8 +182,10 @@ bool saveUserVarToRTC(bool initial) if (!initial && (nullptr != EEPROMExternal) && (eepromAddress > 0)) { // EEPROM Configured? if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + #ifndef BUILD_NO_DEBUG uint32_t eepromWritten{}; - uint32_t checksum = EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); + #endif // ifndef BUILD_NO_DEBUG + const uint32_t checksum = EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); if (UserVar.compute_CRC32() != checksum) { // Only save if data changed for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { @@ -190,15 +193,22 @@ bool saveUserVarToRTC(bool initial) if (taskValues != nullptr) { for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { const size_t index = (task * VARS_PER_TASK) + varNr; - EEPROMExternal->writeLong(EEPROM_USERVAR_START_OFFSET + (index * sizeof(uint32_t)), - taskValues->getUint32(varNr)); - eepromWritten += sizeof(uint32_t); + const uint32_t newData = taskValues->getUint32(varNr); // Only update EEPROM is data differs + if (newData != EEPROMExternal->readLong(EEPROM_USERVAR_START_OFFSET + (index * sizeof_uint32_t))) { + EEPROMExternal->writeLong(EEPROM_USERVAR_START_OFFSET + (index * sizeof_uint32_t), + newData); + #ifndef BUILD_NO_DEBUG + eepromWritten += sizeof_uint32_t; + #endif // ifndef BUILD_NO_DEBUG + } } } } EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, UserVar.compute_CRC32()); - eepromWritten += sizeof(uint32_t); + #ifndef BUILD_NO_DEBUG + eepromWritten += sizeof_uint32_t; + #endif // ifndef BUILD_NO_DEBUG } #ifndef BUILD_NO_DEBUG diff --git a/src/src/WebServer/I2C_Scanner.cpp b/src/src/WebServer/I2C_Scanner.cpp index 9e1759d9bd..5e3145ffdc 100644 --- a/src/src/WebServer/I2C_Scanner.cpp +++ b/src/src/WebServer/I2C_Scanner.cpp @@ -317,21 +317,21 @@ String getKnownI2Cdevice(uint8_t address) { break; case 0x50: case 0x52: - result += F("AT24Cxx"); + result += F("AT24Cxx,MB85RCxx"); break; case 0x51: - result += F("PCF8563,AT24Cxx"); + result += F("PCF8563,AT24Cxx,MB85RCxx"); break; case 0x53: - result += F("ADXL345,LTR390,AT24Cxx"); + result += F("ADXL345,LTR390,AT24Cxx,MB85RCxx"); break; case 0x55: - result += F("DFRobot Rotary enc,BeFlE Moisture,AT24Cxx"); + result += F("DFRobot Rotary enc,BeFlE Moisture,AT24Cxx,MB85RCxx"); break; case 0x54: case 0x56: case 0x57: - result += F("DFRobot Rotary enc,AT24Cxx"); + result += F("DFRobot Rotary enc,AT24Cxx,MB85RCxx"); break; case 0x58: result += F("SGP30,GP8403"); @@ -404,6 +404,9 @@ String getKnownI2Cdevice(uint8_t address) { case 0x78: result += F("LiquidLevel"); break; + case 0x7C: + result += F("MB85RCxx"); + break; case 0x7f: result += F("Arduino PME,XDB401"); break; From 759dff70b84a1b7fefadb03743d8cdf29497bd60 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 19 Aug 2025 20:55:14 +0200 Subject: [PATCH 05/44] [Storage] Add WriteEE,slot,value command and [ReadEE#slot] for retrieving values --- src/src/Commands/EEPROMExternal.cpp | 35 ++++++++ src/src/Commands/EEPROMExternal.h | 8 ++ src/src/Commands/InternalCommands.cpp | 6 ++ src/src/Commands/InternalCommands_decoder.cpp | 3 + src/src/Commands/InternalCommands_decoder.h | 3 + src/src/Helpers/EEPROMExternal.cpp | 86 +++++++++++++++++-- src/src/Helpers/EEPROMExternal.h | 14 +++ src/src/Helpers/StringParser.cpp | 29 ++++++- 8 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 src/src/Commands/EEPROMExternal.cpp create mode 100644 src/src/Commands/EEPROMExternal.h diff --git a/src/src/Commands/EEPROMExternal.cpp b/src/src/Commands/EEPROMExternal.cpp new file mode 100644 index 0000000000..156eed87c4 --- /dev/null +++ b/src/src/Commands/EEPROMExternal.cpp @@ -0,0 +1,35 @@ +#include "../Commands/EEPROMExternal.h" +#include "../Helpers/EEPROMExternal.h" + +#include "../../ESPEasy_common.h" + +#include "../Commands/Common.h" + +#include "../DataStructs/ESPEasy_EventStruct.h" + +#include "../Helpers/Misc.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" + +#if FEATURE_EEPROM_EXTERNAL + +// Command: WriteEE,, : set a slot value. 0 is 'erased' +// Command: WriteEE,erase,erase : reset all slots to 0 +const __FlashStringHelper* Command_writeEE(struct EventStruct *event, const char *Line) +{ + uint32_t slot{}; + float value{}; + + if (validUIntFromString(parseString(Line, 2), slot) && validFloatFromString(parseString(Line, 3), value)) { + return return_command_boolean_result_flashstr(writeEEPROMSlot(slot, value)); + } else if (equals(parseString(Line, 2), F("erase")) && equals(parseString(Line, 3), F("erase"))) { + for (uint32_t slot = 0; slot < getEEPROMMaxSlots(); ++slot) { + writeEEPROMSlot(slot, 0.0f); + } + addLog(LOG_LEVEL_INFO, F("EEPROM: All slot-values erased.")); + return return_command_success_flashstr(); + } + return return_command_failed_flashstr(); +} + +#endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Commands/EEPROMExternal.h b/src/src/Commands/EEPROMExternal.h new file mode 100644 index 0000000000..c42ac560a4 --- /dev/null +++ b/src/src/Commands/EEPROMExternal.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#if FEATURE_EEPROM_EXTERNAL +const __FlashStringHelper* Command_writeEE(struct EventStruct *event, + const char *Line); +#endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Commands/InternalCommands.cpp b/src/src/Commands/InternalCommands.cpp index dce54f5a3e..da56dd66a9 100644 --- a/src/src/Commands/InternalCommands.cpp +++ b/src/src/Commands/InternalCommands.cpp @@ -13,6 +13,9 @@ #include "../Commands/Common.h" #include "../Commands/Controller.h" #include "../Commands/Diagnostic.h" +#if FEATURE_EEPROM_EXTERNAL +#include "../Commands/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL #include "../Commands/GPIO.h" #include "../Commands/HTTP.h" #include "../Commands/InternalCommands_decoder.h" @@ -483,6 +486,9 @@ bool InternalCommands::executeInternalCommand() case ESPEasy_cmd_e::wifissid: COMMAND_CASE_R(Command_Wifi_SSID, 1); // WiFi.h case ESPEasy_cmd_e::wifissid2: COMMAND_CASE_R(Command_Wifi_SSID2, 1); // WiFi.h case ESPEasy_cmd_e::wifistamode: COMMAND_CASE_R(Command_Wifi_STAMode, 0); // WiFi.h +#if FEATURE_EEPROM_EXTERNAL + case ESPEasy_cmd_e::writeee: COMMAND_CASE_R(Command_writeEE, 2); // EEPROMExternal.h +#endif // if FEATURE_EEPROM_EXTERNAL case ESPEasy_cmd_e::NotMatched: diff --git a/src/src/Commands/InternalCommands_decoder.cpp b/src/src/Commands/InternalCommands_decoder.cpp index 0c6115f6eb..38669cc097 100644 --- a/src/src/Commands/InternalCommands_decoder.cpp +++ b/src/src/Commands/InternalCommands_decoder.cpp @@ -286,6 +286,9 @@ const char Internal_commands_w[] PROGMEM = "wdconfig|" "wdread|" #endif // ifndef LIMIT_BUILD_SIZE +#if FEATURE_EEPROM_EXTERNAL + "writeee|" +#endif // if FEATURE_EEPROM_EXTERNAL ; const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) diff --git a/src/src/Commands/InternalCommands_decoder.h b/src/src/Commands/InternalCommands_decoder.h index b5c3f5fa32..5445d5851b 100644 --- a/src/src/Commands/InternalCommands_decoder.h +++ b/src/src/Commands/InternalCommands_decoder.h @@ -241,6 +241,9 @@ enum class ESPEasy_cmd_e : uint8_t { wdconfig, wdread, #endif // ifndef LIMIT_BUILD_SIZE +#if FEATURE_EEPROM_EXTERNAL + writeee, +#endif // if FEATURE_EEPROM_EXTERNAL NotMatched // Keep as last one diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/src/Helpers/EEPROMExternal.cpp index a1f31f2f5e..7247a81b51 100644 --- a/src/src/Helpers/EEPROMExternal.cpp +++ b/src/src/Helpers/EEPROMExternal.cpp @@ -7,6 +7,8 @@ AT24CX *EEPROMExternal = nullptr; +constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); + /** * Switch to I2C Bus and multiplexer channel of External EEPROM */ @@ -36,6 +38,9 @@ uint8_t selectEEPROMI2CBusAndMultiplexer() { return 0; } +/** + * EEPROM size in bytes + */ uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { switch (type) { case EEPROMExternal_Type_e::AT24C256: @@ -60,6 +65,9 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { return 0; } +/** + * EEPROM pagesize in bytes + */ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, uint8_t & pageSize) { pageSize = 0; @@ -87,17 +95,20 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, return getEEPROMSize(type); } +/** + * EEPROM/FRAM name + */ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { # ifndef BUILD_NO_DEBUG switch (type) { case EEPROMExternal_Type_e::AT24C256: - return F("AT24C256/MB85RC256"); + return F("AT24C256 / MB85RC256"); case EEPROMExternal_Type_e::AT24C512: - return F("AT24C512/MB85RC512"); + return F("AT24C512 / MB85RC512"); # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: - return F("AT24C1024/MB85RC1M"); + return F("AT24C1024 / MB85RC1M"); # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: @@ -106,9 +117,9 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { case EEPROMExternal_Type_e::AT24C32: return F("AT24C32"); case EEPROMExternal_Type_e::AT24C64: - return F("AT24C64/MB85RC64"); + return F("AT24C64 / MB85RC64"); case EEPROMExternal_Type_e::AT24C128: - return F("AT24C128/MB85RC128"); + return F("AT24C128 / MB85RC128"); } return F(""); # else // ifndef BUILD_NO_DEBUG @@ -116,4 +127,69 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { # endif // ifndef BUILD_NO_DEBUG } +/** + * EEPROM address for slot or 0 when error + */ +uint32_t getEEPROMAddressForSlot(uint32_t slot) { + if ((nullptr != EEPROMExternal) && (Settings.EEPROMExternalI2CAddress() > 0)) { + const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); + + if ((eepromSize > 0) && (slot < getEEPROMMaxSlots())) { + const uint32_t slotAddr = EEPROM_CUSTOM_START_OFFSET + (slot * sizeof_uint32_t); + + if (slotAddr < eepromSize) { + return slotAddr; + } + } + } + return 0; +} + +/** + * EEPROM available number of slots + * NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! + */ +uint32_t getEEPROMMaxSlots() { + if ((nullptr != EEPROMExternal) && (Settings.EEPROMExternalI2CAddress() > 0)) { + const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); + + if (eepromSize > 0) { + const uint32_t slotMax = ((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_uint32_t; + + return slotMax; + } + } + return 0; +} + +/** + * EEPROM write value to slot if the slot is valid + */ +bool writeEEPROMSlot(uint32_t slot, + float data) { + const uint32_t addr = getEEPROMAddressForSlot(slot); + + if (addr > 0) { + const float oldData = EEPROMExternal->readLong(addr); + + if (!essentiallyEqual(oldData, data)) { + EEPROMExternal->writeFloat(addr, data); + } + return true; + } + return false; +} + +/** + * EEPROM read value from slot or 0 when invalid + */ +float readEEPROMSlot(uint32_t slot) { + const uint32_t addr = getEEPROMAddressForSlot(slot); + + if (addr > 0) { + return EEPROMExternal->readFloat(addr); + } + return 0; +} + #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/EEPROMExternal.h b/src/src/Helpers/EEPROMExternal.h index b0f71ec444..c03c174e0c 100644 --- a/src/src/Helpers/EEPROMExternal.h +++ b/src/src/Helpers/EEPROMExternal.h @@ -22,6 +22,14 @@ extern AT24CX *EEPROMExternal; // Choose an arbitrary but fixed offset # define EEPROM_CUSTOM_START_OFFSET (2048) +# if FEATURE_STRING_VARIABLES + +// NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! +# define EEPROM_CUSTOM_DIVISOR (2) // Split in slots- and strings- halves +# else // if FEATURE_STRING_VARIABLES +# define EEPROM_CUSTOM_DIVISOR (1) // Use all for slots +# endif // if FEATURE_STRING_VARIABLES + // Enable/disable some models # define EEPROM_SUPPORT_AT24C1024 1 # define EEPROM_SUPPORT_AT24C2048 0 @@ -47,4 +55,10 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type); uint32_t getEEPROMSize(EEPROMExternal_Type_e type, uint8_t & pageSize); const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type); + +uint32_t getEEPROMAddressForSlot(uint32_t slot); +uint32_t getEEPROMMaxSlots(); +bool writeEEPROMSlot(uint32_t slot, + float data); +float readEEPROMSlot(uint32_t slot); #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index bd7ebe2ade..18448dbc3a 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -21,7 +21,9 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringGenerator_GPIO.h" - +#if FEATURE_EEPROM_EXTERNAL +#include "../Helpers/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL /********************************************************************************************\ Parse string template @@ -164,10 +166,16 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us const bool devNameEqStr = equals(deviceName, F("str")); const bool devNameEqLength = equals(deviceName, F("length")); #endif // if FEATURE_STRING_VARIABLES + #if FEATURE_EEPROM_EXTERNAL + const bool devNameEqReadEE = equals(deviceName, F("readee")); + #endif // if FEATURE_EEPROM_EXTERNAL if (devNameEqInt || equals(deviceName, F("var")) #if FEATURE_STRING_VARIABLES || devNameEqStr || devNameEqLength #endif // if FEATURE_STRING_VARIABLES + #if FEATURE_EEPROM_EXTERNAL + || devNameEqReadEE + #endif // if FEATURE_EEPROM_EXTERNAL ) { // Address an internal variable either as float or as int @@ -195,6 +203,25 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us tmpString); } else #endif + #if FEATURE_EEPROM_EXTERNAL + if (devNameEqReadEE) { + uint32_t slot{}; + String value; + if (validUIntFromString(valueName, slot)) { + value = toString(readEEPROMSlot(slot)); + } else if (valueName.equalsIgnoreCase(F("max"))) { + value = getEEPROMMaxSlots(); + } + if (!value.isEmpty()) { + transformValue( + newString, + minimal_lineSize, + std::move(value), + format, + tmpString); + } + } else + #endif // if FEATURE_EEPROM_EXTERNAL { const ESPEASY_RULES_FLOAT_TYPE floatvalue = getCustomFloatVar(valueName); unsigned char nr_decimals = maxNrDecimals_fpType(floatvalue); From 872b6289d5e6e091b48e8e8320d1806b72525809 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 19 Aug 2025 20:59:50 +0200 Subject: [PATCH 06/44] [Storage] Add overview of all values in external EEPROM (new button on Tools page) --- src/src/WebServer/ESPEasy_WebServer.cpp | 4 ++ src/src/WebServer/EepromVarPage.h | 8 +++ src/src/WebServer/EpromVarPage.cpp | 90 +++++++++++++++++++++++++ src/src/WebServer/ToolsPage.cpp | 4 ++ 4 files changed, 106 insertions(+) create mode 100644 src/src/WebServer/EepromVarPage.h create mode 100644 src/src/WebServer/EpromVarPage.cpp diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index 5ba648ab3e..e85f534bf6 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -12,6 +12,7 @@ #include "../WebServer/CustomPage.h" #include "../WebServer/DevicesPage.h" #include "../WebServer/DownloadPage.h" +#include "../WebServer/EepromVarPage.h" #include "../WebServer/FactoryResetPage.h" #include "../WebServer/FileList.h" #include "../WebServer/HTML_wrappers.h" @@ -299,6 +300,9 @@ void WebServerInit() #ifdef WEBSERVER_SYSVARS web_server.on(F("/sysvars"), handle_sysvars); #endif // WEBSERVER_SYSVARS +#if FEATURE_EEPROM_EXTERNAL + web_server.on(F("/eepromvars"), handle_eepromvars); +#endif // if FEATURE_EEPROM_EXTERNAL #ifdef WEBSERVER_TIMINGSTATS web_server.on(F("/timingstats"), handle_timingstats); #endif // WEBSERVER_TIMINGSTATS diff --git a/src/src/WebServer/EepromVarPage.h b/src/src/WebServer/EepromVarPage.h new file mode 100644 index 0000000000..ed4edb5620 --- /dev/null +++ b/src/src/WebServer/EepromVarPage.h @@ -0,0 +1,8 @@ +#pragma once + +#if FEATURE_EEPROM_EXTERNAL +# include "../WebServer/common.h" + +void handle_eepromvars(); + +#endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp new file mode 100644 index 0000000000..d237dc9009 --- /dev/null +++ b/src/src/WebServer/EpromVarPage.cpp @@ -0,0 +1,90 @@ +#include "../WebServer/EepromVarPage.h" + + +#include "../WebServer/ESPEasy_WebServer.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Forms.h" +#include "../WebServer/HTML_wrappers.h" + +#include "../Globals/Settings.h" + +#include "../Helpers/EEPROMExternal.h" + +#include "../Helpers/StringConverter.h" + +// #if FEATURE_STRING_VARIABLES +// # include "../Helpers/StringParser.h" +// #endif // if + +#if FEATURE_EEPROM_EXTERNAL + +void handle_eepromvars() { + if (!isLoggedIn()) { return; } + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + if ((nullptr != EEPROMExternal) && (Settings.EEPROMExternalI2CAddress() > 0)) { + // the table header + html_table_class_normal(); + html_TR(); + html_table_header(F("External EEPROM"), 300); + html_table_header(getEEPROMName(static_cast(Settings.EEPROMExternalType())), 400); + html_table_header(F("")); + html_TR(); + html_table_header(F("Slot"), 300); + html_table_header(F("Value (only non-zero values)"), 400); + html_table_header(F("")); + + const uint32_t maxSlots = getEEPROMMaxSlots(); + uint32_t count{}; + + for (uint32_t slot = 0; slot < maxSlots; ++slot) { + const float value = readEEPROMSlot(slot); + + if (slot % 50 == 0) { delay(0); } + + if (!essentiallyZero(value)) { + ++count; + html_TR_TD(); + addHtmlInt(slot); + html_TD(); + addHtml(toString(value)); + html_TD(); + } + } + + // TODO: List String values in EEPROM + + addTableSeparator(F("Summary"), 3, 2); + + html_TR_TD(); + addHtml(F("Slots occupied: ")); + + if (0 == count) { + addHtml(F("none")); + } else { + addHtmlInt(count); + } + html_TD(); + addHtml(F("Slots available: ")); + addHtmlInt(maxSlots - count); + + if (count > 0) { + addHtml(F(" of ")); + addHtmlInt(maxSlots); + } + html_TD(); + + // TODO: List String summary in EEPROM + + html_end_table(); + } else { + addHtml(F("External EEPROM not enabled.")); + } + html_end_form(); + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +#endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/WebServer/ToolsPage.cpp b/src/src/WebServer/ToolsPage.cpp index 8889e4af99..7579b17b24 100644 --- a/src/src/WebServer/ToolsPage.cpp +++ b/src/src/WebServer/ToolsPage.cpp @@ -94,6 +94,10 @@ void handle_tools() { addWideButtonPlusDescription(F("sysvars"), F("System Variables"), F("Show all system variables and conversions")); # endif // ifdef WEBSERVER_SYSVARS + #if FEATURE_EEPROM_EXTERNAL + addWideButtonPlusDescription(F("eepromvars"), F("External EEPROM values"), F("Show all values stored in the external EEPROM")); + #endif // if FEATURE_EEPROM_EXTERNAL + addFormSubHeader(F("Wifi")); addWideButtonPlusDescription(F("/?cmd=wificonnect"), F("Connect"), F("Connects to known Wifi network")); From beced1361464b07eae17f4dddc72f9ac1d1d6a0d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 21 Aug 2025 13:22:40 +0200 Subject: [PATCH 07/44] [Storage] Restore Task Values optionally on cold or warm boot, code improvements --- src/src/DataStructs/SettingsStruct.h | 11 +++- src/src/ESPEasyCore/ESPEasy_setup.cpp | 12 ++++ src/src/Helpers/CRC_functions.cpp | 26 ++++++++ src/src/Helpers/CRC_functions.h | 7 +++ src/src/Helpers/EEPROMExternal.cpp | 45 ++++++++++--- src/src/Helpers/EEPROMExternal.h | 6 ++ src/src/Helpers/ESPEasyRTC.cpp | 91 ++++++++++++++++++++------- src/src/WebServer/HardwarePage.cpp | 8 +++ 8 files changed, 173 insertions(+), 33 deletions(-) diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 6610f15516..e6aa54ae0f 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -218,6 +218,13 @@ class SettingsStruct_tmpl void ShowUnitOfMeasureOnDevicesPage(bool value) { VariousBits_2.ShowUnitOfMeasureOnDevicesPage = !value; } #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + #if FEATURE_EEPROM_EXTERNAL + bool RestoreUserVarsFromEEPROMOnColdBoot() const { return VariousBits_2.RestoreUserVarsFromEEPROMOnColdBoot; } + void RestoreUserVarsFromEEPROMOnColdBoot(bool value) { VariousBits_2.RestoreUserVarsFromEEPROMOnColdBoot = value; } + bool RestoreUserVarsFromEEPROMOnWarmBoot() const { return VariousBits_2.RestoreUserVarsFromEEPROMOnWarmBoot; } + void RestoreUserVarsFromEEPROMOnWarmBoot(bool value) { VariousBits_2.RestoreUserVarsFromEEPROMOnWarmBoot = value; } + #endif // if FEATURE_EEPROM_EXTERNAL + // Flag indicating whether all task values should be sent in a single event or one event per task value (default behavior) bool CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const; void CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value); @@ -597,8 +604,8 @@ class SettingsStruct_tmpl uint32_t DisableSaveConfigAsTar : 1; // Bit 05 uint32_t PassiveWiFiScan : 1; // Bit 06 // inverted uint32_t ShowUnitOfMeasureOnDevicesPage : 1; // Bit 07 // inverted - uint32_t unused_08 : 1; // Bit 08 - uint32_t unused_09 : 1; // Bit 09 + uint32_t RestoreUserVarsFromEEPROMOnColdBoot : 1; // Bit 08 + uint32_t RestoreUserVarsFromEEPROMOnWarmBoot : 1; // Bit 09 uint32_t unused_10 : 1; // Bit 10 uint32_t unused_11 : 1; // Bit 11 uint32_t unused_12 : 1; // Bit 12 diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index 23546c739d..ed1345ad78 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -34,6 +34,9 @@ #include "../Helpers/StringGenerator_System.h" #include "../WebServer/ESPEasy_WebServer.h" +#if FEATURE_EEPROM_EXTERNAL +#include "../Helpers/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL #ifdef USE_RTOS_MULTITASKING # include "../Helpers/Networking.h" @@ -398,6 +401,15 @@ void ESPEasy_setup() logMemUsageAfter(F("hardwareInit()")); #endif // ifndef BUILD_NO_RAM_TRACKER + #if FEATURE_EEPROM_EXTERNAL + if ((checkEEPROMEnabled() > 0) && + ((lastBootCause <= BOOT_CAUSE_COLD_BOOT) && Settings.RestoreUserVarsFromEEPROMOnColdBoot() || + (lastBootCause <= BOOT_CAUSE_SOFT_RESTART) && Settings.RestoreUserVarsFromEEPROMOnWarmBoot()) + ) { + readUserVarFromRTC(); // Once more to fetch UserVar data from now available EEPROM/FRAM + } + #endif // if FEATURE_EEPROM_EXTERNAL + node_time.restoreFromRTC(); Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. diff --git a/src/src/Helpers/CRC_functions.cpp b/src/src/Helpers/CRC_functions.cpp index ba76121624..d18e93fa21 100644 --- a/src/src/Helpers/CRC_functions.cpp +++ b/src/src/Helpers/CRC_functions.cpp @@ -57,6 +57,32 @@ uint32_t calc_CRC32(const uint8_t *data, size_t length) { return crc; } +uint32_t calc_CRC32(tDataReader dataReader, size_t length) { + uint32_t crc = 0xffffffff; + size_t index{}; + + if (dataReader) { + while (length--) { + uint8_t c = dataReader(index); + ++index; + + for (uint32_t i = 0x80; i > 0; i >>= 1) { + bool bit = crc & 0x80000000; + + if (c & i) { + bit = !bit; + } + crc <<= 1; + + if (bit) { + crc ^= 0x04c11db7; + } + } + } + } + return crc; +} + uint8_t calc_CRC8(const uint8_t *data, size_t length) { /* diff --git a/src/src/Helpers/CRC_functions.h b/src/src/Helpers/CRC_functions.h index dfbf589d8a..d418112b02 100644 --- a/src/src/Helpers/CRC_functions.h +++ b/src/src/Helpers/CRC_functions.h @@ -3,6 +3,10 @@ #include "../../ESPEasy_common.h" +#include + +typedef std::function tDataReader; + int calc_CRC16(const String& text); int IRAM_ATTR calc_CRC16(const char *ptr, @@ -11,6 +15,9 @@ int IRAM_ATTR calc_CRC16(const char *ptr, uint32_t calc_CRC32(const uint8_t *data, size_t length); +uint32_t calc_CRC32(tDataReader dataReader, + size_t length); + uint8_t calc_CRC8(const uint8_t *data, size_t length); diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/src/Helpers/EEPROMExternal.cpp index 7247a81b51..e453bab8ed 100644 --- a/src/src/Helpers/EEPROMExternal.cpp +++ b/src/src/Helpers/EEPROMExternal.cpp @@ -9,6 +9,19 @@ AT24CX *EEPROMExternal = nullptr; constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); +/** + * Check if the EEPROM is properly initialized and enabled. + * Returns the I2C address if all is OK + */ +uint8_t checkEEPROMEnabled() { + const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); + + if ((nullptr != EEPROMExternal) && (eepromAddress > 0)) { // EEPROM Configured? + return eepromAddress; + } + return 0; +} + /** * Switch to I2C Bus and multiplexer channel of External EEPROM */ @@ -128,10 +141,10 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { } /** - * EEPROM address for slot or 0 when error + * EEPROM address for slot or 0xFFFF when error */ uint32_t getEEPROMAddressForSlot(uint32_t slot) { - if ((nullptr != EEPROMExternal) && (Settings.EEPROMExternalI2CAddress() > 0)) { + if (checkEEPROMEnabled() > 0) { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); if ((eepromSize > 0) && (slot < getEEPROMMaxSlots())) { @@ -142,7 +155,25 @@ uint32_t getEEPROMAddressForSlot(uint32_t slot) { } } } - return 0; + return std::numeric_limits::max(); +} + +/** + * EEPROM address for task and varnr or 0xFFFF when error + */ +uint32_t getEEPROMAddressForTaskValue(taskIndex_t task, taskVarIndex_t varNr) { + if (checkEEPROMEnabled() > 0) { + const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); + + if ((eepromSize > 0) && validTaskIndex(task) && validTaskVarIndex(varNr)) { + const uint32_t slotAddr = EEPROM_USERVAR_START_OFFSET + (((task * VARS_PER_TASK) + varNr) * sizeof_uint32_t); + + if (slotAddr < eepromSize) { + return slotAddr; + } + } + } + return std::numeric_limits::max(); } /** @@ -150,7 +181,7 @@ uint32_t getEEPROMAddressForSlot(uint32_t slot) { * NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! */ uint32_t getEEPROMMaxSlots() { - if ((nullptr != EEPROMExternal) && (Settings.EEPROMExternalI2CAddress() > 0)) { + if (checkEEPROMEnabled() > 0) { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); if (eepromSize > 0) { @@ -169,7 +200,7 @@ bool writeEEPROMSlot(uint32_t slot, float data) { const uint32_t addr = getEEPROMAddressForSlot(slot); - if (addr > 0) { + if (addr != std::numeric_limits::max()) { const float oldData = EEPROMExternal->readLong(addr); if (!essentiallyEqual(oldData, data)) { @@ -186,10 +217,10 @@ bool writeEEPROMSlot(uint32_t slot, float readEEPROMSlot(uint32_t slot) { const uint32_t addr = getEEPROMAddressForSlot(slot); - if (addr > 0) { + if (addr != std::numeric_limits::max()) { return EEPROMExternal->readFloat(addr); } - return 0; + return 0.0f; } #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/EEPROMExternal.h b/src/src/Helpers/EEPROMExternal.h index c03c174e0c..6618a9456a 100644 --- a/src/src/Helpers/EEPROMExternal.h +++ b/src/src/Helpers/EEPROMExternal.h @@ -1,6 +1,8 @@ #pragma once #include "../../ESPEasy_common.h" +#include "../DataTypes/TaskIndex.h" + #if FEATURE_EEPROM_EXTERNAL # include @@ -49,6 +51,8 @@ enum class EEPROMExternal_Type_e : uint8_t { AT24C128 = 6, // 16 kB }; +uint8_t checkEEPROMEnabled(); + uint8_t selectEEPROMI2CBusAndMultiplexer(); uint32_t getEEPROMSize(EEPROMExternal_Type_e type); @@ -57,6 +61,8 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type); uint32_t getEEPROMAddressForSlot(uint32_t slot); +uint32_t getEEPROMAddressForTaskValue(taskIndex_t task, + taskVarIndex_t varNr); uint32_t getEEPROMMaxSlots(); bool writeEEPROMSlot(uint32_t slot, float data); diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index cddac3bbd4..2d881b528a 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -178,25 +178,29 @@ bool saveUserVarToRTC(bool initial) { #if FEATURE_EEPROM_EXTERNAL // Check if we have an external EEPROM available on the configured I2C bus & channel, and save all task values there - const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); - if (!initial && (nullptr != EEPROMExternal) && (eepromAddress > 0)) { // EEPROM Configured? + const uint8_t eepromAddress = checkEEPROMEnabled(); + if (!initial && (eepromAddress > 0)) { // EEPROM Configured? if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM #ifndef BUILD_NO_DEBUG uint32_t eepromWritten{}; + uint32_t startmicros{}; #endif // ifndef BUILD_NO_DEBUG const uint32_t checksum = EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); if (UserVar.compute_CRC32() != checksum) { // Only save if data changed + #ifndef BUILD_NO_DEBUG + startmicros = micros(); + #endif // ifndef BUILD_NO_DEBUG for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); if (taskValues != nullptr) { for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { const size_t index = (task * VARS_PER_TASK) + varNr; const uint32_t newData = taskValues->getUint32(varNr); // Only update EEPROM is data differs - if (newData != EEPROMExternal->readLong(EEPROM_USERVAR_START_OFFSET + (index * sizeof_uint32_t))) { - EEPROMExternal->writeLong(EEPROM_USERVAR_START_OFFSET + (index * sizeof_uint32_t), - newData); + const uint32_t addr = getEEPROMAddressForTaskValue(task, varNr); + if (newData != EEPROMExternal->readLong(addr)) { + EEPROMExternal->writeLong(addr, newData); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; #endif // ifndef BUILD_NO_DEBUG @@ -208,14 +212,14 @@ bool saveUserVarToRTC(bool initial) UserVar.compute_CRC32()); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; + startmicros = micros() - startmicros; #endif // ifndef BUILD_NO_DEBUG } #ifndef BUILD_NO_DEBUG - // if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { // FIXME - if (loglevelActiveFor(LOG_LEVEL_INFO)) { + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { const EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); - addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: UserVar: %u bytes written to %s"), eepromWritten, FsP(getEEPROMName(eepromType)))); + addLog(LOG_LEVEL_DEBUG, strformat(F("EEPROM: UserVar: %u bytes (%.2f ms) written to %s"), eepromWritten, startmicros / 1000.0f, FsP(getEEPROMName(eepromType)))); } #endif // ifndef BUILD_NO_DEBUG @@ -259,27 +263,66 @@ bool saveUserVarToRTC(bool initial) #endif } +#if FEATURE_EEPROM_EXTERNAL +uint8_t readDataForUserVars(size_t index) { + if (nullptr != EEPROMExternal) { + return EEPROMExternal->read(EEPROM_USERVAR_START_OFFSET + index); + } + return 0; +} +#endif // if FEATURE_EEPROM_EXTERNAL + /********************************************************************************************\ Read RTC struct from RTC memory \*********************************************************************************************/ bool readUserVarFromRTC() { - // const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); - // if ((nullptr != EEPROMExternal) && (eepromAddress > 0)) { // EEPROM Configured? - - // if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM - // // TODO Check checksum and if correct, restore UserVar values - // } - // #if FEATURE_I2CMULTIPLEXER - // I2CMultiplexerOff( - // #if FEATURE_I2C_MULTIPLE - // Settings.getI2CInterfaceEEPROM() - // #else //if FEATURE_I2C_MULTIPLE - // 0 - // #endif // if FEATURE_I2C_MULTIPLE - // ); // Restore the Multiplexer channel - // #endif // if FEATURE_I2CMULTIPLEXER - // } + #if FEATURE_EEPROM_EXTERNAL + const uint8_t eepromAddress = checkEEPROMEnabled(); + if (eepromAddress > 0) { // EEPROM Configured and restoring of Task Values enabled? + bool result = false; + + if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + // Check checksum and if correct, restore UserVar values + const uint32_t checksum = EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); + const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: readUserVarFromRTC calculated: %u, expected: %u equal: %c"), + calcsum, checksum, calcsum == checksum ? 'Y' : 'n')); + } + #endif // ifndef BUILD_NO_DEBUG + if (calcsum == checksum) { + addLog(LOG_LEVEL_INFO, F("INIT : Restoring Task Values from EEPROM.")); + result = true; + for (size_t i = 0; i < (TASKS_MAX * VARS_PER_TASK) && result; ++i) { + const taskIndex_t taskIndex = i / VARS_PER_TASK; + const uint8_t varNr = i % VARS_PER_TASK; + // Store in raw form, so we don't apply formula as we don't really know what type is required. + const uint32_t addr = getEEPROMAddressForTaskValue(taskIndex, varNr); + if (addr != std::numeric_limits::max()) { + TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(taskIndex); + taskValues->setUint32(varNr, EEPROMExternal->readLong(addr)); + } else { + result = false; + } + } + } + } + #if FEATURE_I2CMULTIPLEXER + I2CMultiplexerOff( + #if FEATURE_I2C_MULTIPLE + Settings.getI2CInterfaceEEPROM() + #else //if FEATURE_I2C_MULTIPLE + 0 + #endif // if FEATURE_I2C_MULTIPLE + ); // Restore the Multiplexer channel + #endif // if FEATURE_I2CMULTIPLEXER + if (result) { + return true; // We did all that was needed + } + } + #endif // if FEATURE_EEPROM_EXTERNAL // ESP8266 has the RTC struct stored in memory which we must actively fetch // ESP32 Uses a temp structure which is mapped to the RTC address range. diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index dbae93a3b5..04ff9f3390 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -129,6 +129,8 @@ void handle_hardware() { #if FEATURE_EEPROM_EXTERNAL Settings.EEPROMExternalType(getFormItemInt(F("eepromtype"), static_cast(EEPROMExternal_Type_e::AT24C256))); Settings.EEPROMExternalI2CAddress(getFormItemInt(F("i2c_eeprom"), 0)); + Settings.RestoreUserVarsFromEEPROMOnColdBoot(isFormItemChecked(F("lduvcbee"))); + Settings.RestoreUserVarsFromEEPROMOnWarmBoot(isFormItemChecked(F("lduvwbee"))); # if FEATURE_I2CMULTIPLEXER @@ -375,6 +377,12 @@ void handle_hardware() { bitRead(eepromMux, EEPROM_MUX_FLAGS_MULTI), get8BitFromUL(eepromMux, EEPROM_MUX_FLAGS_PORT)); // Re-used from DevicesPage #endif // if FEATURE_I2CMULTIPLEXER + + const bool eepromChecked = checkEEPROMEnabled() > 0; + addRowLabel(F("EEPROM Enabled")); + addEnabled(eepromChecked); + addFormCheckBox(F("Cold boot: Restore Task Values from EEPROM"), F("lduvcbee"), Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); + addFormCheckBox(F("Warm boot: Restore Task Values from EEPROM"), F("lduvwbee"), Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); } #endif // if FEATURE_EEPROM_EXTERNAL From 623664d99b15afa62d004510e8158a64008dd7b0 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 21 Aug 2025 13:23:36 +0200 Subject: [PATCH 08/44] [Storage] Show Task Values in EEPROM by using tasks=1 parameter for Eeprom Values page --- src/src/WebServer/EpromVarPage.cpp | 47 ++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp index d237dc9009..7e236de2c5 100644 --- a/src/src/WebServer/EpromVarPage.cpp +++ b/src/src/WebServer/EpromVarPage.cpp @@ -8,8 +8,10 @@ #include "../WebServer/HTML_wrappers.h" #include "../Globals/Settings.h" +#include "../Globals/ExtraTaskSettings.h" #include "../Helpers/EEPROMExternal.h" +#include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/StringConverter.h" @@ -21,16 +23,57 @@ void handle_eepromvars() { if (!isLoggedIn()) { return; } + + const bool showTasks = getFormItemInt(F("tasks"), 0) != 0; + TXBuffer.startStream(); sendHeadandTail_stdtemplate(_HEAD); - if ((nullptr != EEPROMExternal) && (Settings.EEPROMExternalI2CAddress() > 0)) { + if (checkEEPROMEnabled() > 0) { // the table header html_table_class_normal(); html_TR(); html_table_header(F("External EEPROM"), 300); html_table_header(getEEPROMName(static_cast(Settings.EEPROMExternalType())), 400); + html_table_header(F(""), 400); html_table_header(F("")); + + if (showTasks) { + html_TR(); + html_table_header(F("Task"), 300); + html_table_header(F("Value"), 400); + html_table_header(F("Content"), 400); + html_table_header(F("")); + + for (taskIndex_t tsk = 0; tsk < TASKS_MAX; ++tsk) { + LoadTaskSettings(tsk); + html_TR_TD(); + addHtmlInt(tsk + 1); + addHtml(' '); + addHtml(getTaskDeviceName(tsk)); + + for (taskVarIndex_t var = 0; var < VARS_PER_TASK; ++var) { + if (var != 0) { + html_TR_TD(); + } + html_TD(); + addHtmlInt(var + 1); + addHtml(' '); + addHtml(getTaskValueName(tsk, var)); + html_TD(); + const uint32_t addr = getEEPROMAddressForTaskValue(tsk, var); + const float value = EEPROMExternal->readFloat(addr); + const uint32_t data = EEPROMExternal->readLong(addr); + + if (isnan(value) || (addr == std::numeric_limits::max())) { + addHtml('-'); + } else { + addHtml(strformat(F("%s (0x%04x)"), floatToString(value, ExtraTaskSettings.TaskDeviceValueDecimals[var]), data)); + } + } + delay(0); + } + } html_TR(); html_table_header(F("Slot"), 300); html_table_header(F("Value (only non-zero values)"), 400); @@ -44,7 +87,7 @@ void handle_eepromvars() { if (slot % 50 == 0) { delay(0); } - if (!essentiallyZero(value)) { + if (!isnan(value) && !essentiallyZero(value)) { ++count; html_TR_TD(); addHtmlInt(slot); From 65f10f4d6b57028529320c71cd4b58b75b2addca Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 22 Aug 2025 14:31:24 +0200 Subject: [PATCH 09/44] [Storage] Add check for WriteProtected EEPROM and show on Hardware page --- src/src/Helpers/EEPROMExternal.cpp | 53 ++++++++++++++++++++++++++++-- src/src/Helpers/EEPROMExternal.h | 44 +++++++++++++++---------- src/src/Helpers/ESPEasyRTC.cpp | 4 +-- src/src/Helpers/Hardware_I2C.cpp | 6 ++++ src/src/WebServer/EpromVarPage.cpp | 18 ++++++---- src/src/WebServer/HardwarePage.cpp | 3 ++ 6 files changed, 100 insertions(+), 28 deletions(-) diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/src/Helpers/EEPROMExternal.cpp index e453bab8ed..679c34c5e5 100644 --- a/src/src/Helpers/EEPROMExternal.cpp +++ b/src/src/Helpers/EEPROMExternal.cpp @@ -2,10 +2,12 @@ #include "../Globals/Settings.h" #include "../Helpers/I2C_access.h" #include "../../ESPEasy_common.h" +#include "../Helpers/StringConverter.h" #if FEATURE_EEPROM_EXTERNAL -AT24CX *EEPROMExternal = nullptr; +AT24CX *EEPROMExternal = nullptr; +EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::Undefined; constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); @@ -22,6 +24,53 @@ uint8_t checkEEPROMEnabled() { return 0; } +/** + * Check if the EEPROM is write-protected + * when forced = false only detect if current state is Undefined + * - read a random byte in the first half of the address space (some chips ony WP the first half of the space!) + * - write 0xAA and read back -> if unequal: read-only + * - write 0x55 and read back -> if unequal: read-only + * - Still OK: + * - restore original byte + * - Set status read-write + */ +EEPROMExternal_WriteProtect_e checkEEPROMExternalWriteProtected(bool forced) { + if ((nullptr != EEPROMExternal) && ((EEPROMExternal_WriteProtect_e::Undefined == EEPROMExternalWriteProtect) || forced)) { + const uint32_t addr = random(0, getEEPROMSize(static_cast(Settings.EEPROMExternalType())) / 2); + const uint8_t original = EEPROMExternal->read(addr); + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat(F("EEPROM: Writeable check, addr: 0x%04x data: 0x%02X"), addr, original)); + } + # endif // ifndef BUILD_NO_DEBUG + EEPROMExternal->write(addr, 0xAA); + uint8_t newdata = EEPROMExternal->read(addr); + + if (0xAA != newdata) { // write failed + EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::ReadOnly; + } else { + EEPROMExternal->write(addr, 0x55); + newdata = EEPROMExternal->read(addr); + + if (0x55 != newdata) { // write failed + EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::ReadOnly; + } else { + EEPROMExternal->write(addr, original); + EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::ReadWrite; + } + } + } + return EEPROMExternalWriteProtect; +} + +/** + * Is the EEPROM WriteProtected? + */ +bool isEEPROMExternalWriteProtected() { + return EEPROMExternal_WriteProtect_e::ReadWrite != checkEEPROMExternalWriteProtected(); +} + /** * Switch to I2C Bus and multiplexer channel of External EEPROM */ @@ -200,7 +249,7 @@ bool writeEEPROMSlot(uint32_t slot, float data) { const uint32_t addr = getEEPROMAddressForSlot(slot); - if (addr != std::numeric_limits::max()) { + if ((addr != std::numeric_limits::max()) && !isEEPROMExternalWriteProtected()) { const float oldData = EEPROMExternal->readLong(addr); if (!essentiallyEqual(oldData, data)) { diff --git a/src/src/Helpers/EEPROMExternal.h b/src/src/Helpers/EEPROMExternal.h index 6618a9456a..e7132c368f 100644 --- a/src/src/Helpers/EEPROMExternal.h +++ b/src/src/Helpers/EEPROMExternal.h @@ -7,26 +7,32 @@ # include +enum class EEPROMExternal_WriteProtect_e : uint8_t { + Undefined = 0, + ReadWrite = 1, + ReadOnly = 2, +}; + extern AT24CX *EEPROMExternal; +extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; // Start writing the base RTC struct from this offset (not currently saving this to EEPROM) // TODO # define EEPROM_BASERTC_START_OFFSET (0) // Start writing the UserVar values from this offset, should be > sizeof(RTCStruct) that is 32 currently -# define EEPROM_USERVAR_START_OFFSET (100) +# define EEPROM_USERVAR_START_OFFSET (EEPROM_BASERTC_START_OFFSET + 128) // Write the UserVar-checksum from this offset, right after the UserVar values # define EEPROM_USERVAR_CHECKSUM_OFFSET (EEPROM_USERVAR_START_OFFSET + (TASKS_MAX * VARS_PER_TASK * sizeof(uint32_t))) -// Offset for storing GPIO states // TODO -# define EEPROM_GPIO_MCPPCF_START_OFFSET (1024) +// Offset for storing GPIO states, directly following the UserVar storage and checksum +# define EEPROM_GPIO_MCPPCF_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) // Choose an arbitrary but fixed offset # define EEPROM_CUSTOM_START_OFFSET (2048) -# if FEATURE_STRING_VARIABLES - // NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! +# if FEATURE_STRING_VARIABLES # define EEPROM_CUSTOM_DIVISOR (2) // Split in slots- and strings- halves # else // if FEATURE_STRING_VARIABLES # define EEPROM_CUSTOM_DIVISOR (1) // Use all for slots @@ -51,20 +57,22 @@ enum class EEPROMExternal_Type_e : uint8_t { AT24C128 = 6, // 16 kB }; -uint8_t checkEEPROMEnabled(); +uint8_t checkEEPROMEnabled(); +EEPROMExternal_WriteProtect_e checkEEPROMExternalWriteProtected(bool forced = false); +bool isEEPROMExternalWriteProtected(); -uint8_t selectEEPROMI2CBusAndMultiplexer(); +uint8_t selectEEPROMI2CBusAndMultiplexer(); -uint32_t getEEPROMSize(EEPROMExternal_Type_e type); -uint32_t getEEPROMSize(EEPROMExternal_Type_e type, - uint8_t & pageSize); -const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type); +uint32_t getEEPROMSize(EEPROMExternal_Type_e type); +uint32_t getEEPROMSize(EEPROMExternal_Type_e type, + uint8_t & pageSize); +const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type); -uint32_t getEEPROMAddressForSlot(uint32_t slot); -uint32_t getEEPROMAddressForTaskValue(taskIndex_t task, - taskVarIndex_t varNr); -uint32_t getEEPROMMaxSlots(); -bool writeEEPROMSlot(uint32_t slot, - float data); -float readEEPROMSlot(uint32_t slot); +uint32_t getEEPROMAddressForSlot(uint32_t slot); +uint32_t getEEPROMAddressForTaskValue(taskIndex_t task, + taskVarIndex_t varNr); +uint32_t getEEPROMMaxSlots(); +bool writeEEPROMSlot(uint32_t slot, + float data); +float readEEPROMSlot(uint32_t slot); #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 2d881b528a..92b5528787 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -179,7 +179,7 @@ bool saveUserVarToRTC(bool initial) #if FEATURE_EEPROM_EXTERNAL // Check if we have an external EEPROM available on the configured I2C bus & channel, and save all task values there const uint8_t eepromAddress = checkEEPROMEnabled(); - if (!initial && (eepromAddress > 0)) { // EEPROM Configured? + if (!initial && (eepromAddress > 0) && !isEEPROMExternalWriteProtected()) { // EEPROM Configured and writable? if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM #ifndef BUILD_NO_DEBUG @@ -197,7 +197,7 @@ bool saveUserVarToRTC(bool initial) if (taskValues != nullptr) { for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { const size_t index = (task * VARS_PER_TASK) + varNr; - const uint32_t newData = taskValues->getUint32(varNr); // Only update EEPROM is data differs + const uint32_t newData = taskValues->getUint32(varNr); // Only update EEPROM if data differs const uint32_t addr = getEEPROMAddressForTaskValue(task, varNr); if (newData != EEPROMExternal->readLong(addr)) { EEPROMExternal->writeLong(addr, newData); diff --git a/src/src/Helpers/Hardware_I2C.cpp b/src/src/Helpers/Hardware_I2C.cpp index 34e31ac72a..f28ed2f062 100644 --- a/src/src/Helpers/Hardware_I2C.cpp +++ b/src/src/Helpers/Hardware_I2C.cpp @@ -107,6 +107,12 @@ void initI2C() { FsP(getEEPROMName(eepromType)), eepromAddress)); } + + checkEEPROMExternalWriteProtected(); + + if (isEEPROMExternalWriteProtected()) { + addLog(LOG_LEVEL_INFO, concat(F("EEPROM: Write-protected! Status: "), static_cast(checkEEPROMExternalWriteProtected()))); + } } else { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLog(LOG_LEVEL_ERROR, strformat(F("EEPROM: Initialization of %s failed"), diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp index 7e236de2c5..54cc4f859b 100644 --- a/src/src/WebServer/EpromVarPage.cpp +++ b/src/src/WebServer/EpromVarPage.cpp @@ -34,14 +34,16 @@ void handle_eepromvars() { html_table_class_normal(); html_TR(); html_table_header(F("External EEPROM"), 300); - html_table_header(getEEPROMName(static_cast(Settings.EEPROMExternalType())), 400); - html_table_header(F(""), 400); + html_table_header(getEEPROMName(static_cast(Settings.EEPROMExternalType())), 500); + html_table_header(isEEPROMExternalWriteProtected() ? F("Write-protected!") : F(""), 400); html_table_header(F("")); if (showTasks) { html_TR(); + + // sub-table header html_table_header(F("Task"), 300); - html_table_header(F("Value"), 400); + html_table_header(F("Value"), 500); html_table_header(F("Content"), 400); html_table_header(F("")); @@ -75,8 +77,11 @@ void handle_eepromvars() { } } html_TR(); + + // sub-table header html_table_header(F("Slot"), 300); - html_table_header(F("Value (only non-zero values)"), 400); + html_table_header(F("Value (only non-zero values)"), 500); + html_table_header(F("")); html_table_header(F("")); const uint32_t maxSlots = getEEPROMMaxSlots(); @@ -93,11 +98,12 @@ void handle_eepromvars() { addHtmlInt(slot); html_TD(); addHtml(toString(value)); - html_TD(); + html_TD(2); } } // TODO: List String values in EEPROM + // TODO: List C016 Cache entries in EEPROM addTableSeparator(F("Summary"), 3, 2); @@ -109,7 +115,7 @@ void handle_eepromvars() { } else { addHtmlInt(count); } - html_TD(); + addHtml(F("")); addHtml(F("Slots available: ")); addHtmlInt(maxSlots - count); diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index 04ff9f3390..7787814d3b 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -381,6 +381,9 @@ void handle_hardware() { const bool eepromChecked = checkEEPROMEnabled() > 0; addRowLabel(F("EEPROM Enabled")); addEnabled(eepromChecked); + if (isEEPROMExternalWriteProtected()) { + addHtml(F(" Write-protected!")); + } addFormCheckBox(F("Cold boot: Restore Task Values from EEPROM"), F("lduvcbee"), Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); addFormCheckBox(F("Warm boot: Restore Task Values from EEPROM"), F("lduvwbee"), Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); } From ce8453e925369ad5460a894dc13c1cd461516850 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 22 Aug 2025 14:31:50 +0200 Subject: [PATCH 10/44] [Storage] Add command to re-check for WriteProtected EEPROM --- src/src/Commands/EEPROMExternal.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/src/Commands/EEPROMExternal.cpp b/src/src/Commands/EEPROMExternal.cpp index 156eed87c4..92c94ed6bd 100644 --- a/src/src/Commands/EEPROMExternal.cpp +++ b/src/src/Commands/EEPROMExternal.cpp @@ -28,6 +28,14 @@ const __FlashStringHelper* Command_writeEE(struct EventStruct *event, const char } addLog(LOG_LEVEL_INFO, F("EEPROM: All slot-values erased.")); return return_command_success_flashstr(); + } else if (equals(parseString(Line, 2), F("check")) && equals(parseString(Line, 3), F("wp"))) { + addLog(LOG_LEVEL_INFO, F("EEPROM: Check write-protect.")); + checkEEPROMExternalWriteProtected(true); + + if (isEEPROMExternalWriteProtected()) { + addLog(LOG_LEVEL_INFO, concat(F("EEPROM: Write-protected! Status: "), static_cast(checkEEPROMExternalWriteProtected()))); + } + return return_command_success_flashstr(); } return return_command_failed_flashstr(); } From c56c3f6795cd7c54e2c079d808a6fbdf0740f34d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 22 Aug 2025 14:42:54 +0200 Subject: [PATCH 11/44] [Storage] Improved initialization and format source --- src/src/Helpers/Hardware_I2C.cpp | 42 +++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/src/Helpers/Hardware_I2C.cpp b/src/src/Helpers/Hardware_I2C.cpp index f28ed2f062..ab4cebec6c 100644 --- a/src/src/Helpers/Hardware_I2C.cpp +++ b/src/src/Helpers/Hardware_I2C.cpp @@ -8,7 +8,7 @@ #include "../Helpers/StringConverter.h" #if FEATURE_EEPROM_EXTERNAL -#include "../Helpers/EEPROMExternal.h" +# include "../Helpers/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL #include @@ -36,12 +36,12 @@ void initI2C() { { if (Settings.isI2CEnabled(i2cBus)) { #ifndef BUILD_MINIMAL_OTA - #if !FEATURE_I2C_MULTIPLE + # if !FEATURE_I2C_MULTIPLE addLog(LOG_LEVEL_INFO, F("INIT : I2C Bus")); - #else // if !FEATURE_I2C_MULTIPLE + # else // if !FEATURE_I2C_MULTIPLE addLog(LOG_LEVEL_INFO, concat(F("INIT : I2C Bus "), i2cBus)); - #endif // if !FEATURE_I2C_MULTIPLE - #endif + # endif // if !FEATURE_I2C_MULTIPLE + #endif // ifndef BUILD_MINIMAL_OTA I2CSelectHighClockSpeed(i2cBus); // Set normal clock speed, on I2C Bus 1 (index 0) } } @@ -88,24 +88,26 @@ void initI2C() { #if FEATURE_EEPROM_EXTERNAL const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); - if ((nullptr != EEPROMExternal) && (eepromAddress == 0)) { // Cleanup when turning off EEPROM + if ((nullptr != EEPROMExternal) || (eepromAddress == 0)) { // Cleanup when turning off EEPROM delete EEPROMExternal; - EEPROMExternal = nullptr; + EEPROMExternal = nullptr; + EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::Undefined; } if ((nullptr == EEPROMExternal) && (eepromAddress > 0)) { const EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); + if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM // We have an I2C device at this address, let's assume it's an EEPROM... - uint8_t pageSize = 0; + uint8_t pageSize = 0; const uint32_t eepromSize = getEEPROMSize(eepromType, pageSize); EEPROMExternal = new (std::nothrow) AT24CX(eepromAddress, pageSize, eepromSize); if (nullptr != EEPROMExternal) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: %s initialized at address 0x%02x"), - FsP(getEEPROMName(eepromType)), - eepromAddress)); + FsP(getEEPROMName(eepromType)), + eepromAddress)); } checkEEPROMExternalWriteProtected(); @@ -127,15 +129,15 @@ void initI2C() { } } - #if FEATURE_I2CMULTIPLEXER + # if FEATURE_I2CMULTIPLEXER I2CMultiplexerOff( - #if FEATURE_I2C_MULTIPLE + # if FEATURE_I2C_MULTIPLE Settings.getI2CInterfaceEEPROM() - #else //if FEATURE_I2C_MULTIPLE + # else // if FEATURE_I2C_MULTIPLE 0 - #endif // if FEATURE_I2C_MULTIPLE - ); // Restore the Multiplexer channel - #endif // if FEATURE_I2CMULTIPLEXER + # endif // if FEATURE_I2C_MULTIPLE + ); // Restore the Multiplexer channel + # endif // if FEATURE_I2CMULTIPLEXER } #endif // if FEATURE_EEPROM_EXTERNAL @@ -208,10 +210,11 @@ void I2CBegin(int8_t sda, int8_t scl, uint32_t clockFreq, uint32_t clockStretch) // No need to change the clock speed. return; } - if (sda == -1 || scl == -1) { + + if ((sda == -1) || (scl == -1)) { #ifdef ESP32 Wire.end(); -#endif +#endif // ifdef ESP32 last_sda = sda; last_scl = scl; return; @@ -308,7 +311,7 @@ uint8_t I2CMultiplexerShiftBit(uint8_t i2cBus, uint8_t i) { void I2CMultiplexerSelectByBusAndMux(uint8_t i2cBus, bool singleMulti, int muxPort) { uint8_t toWrite{}; - if ((singleMulti && (muxPort > 0))|| + if ((singleMulti && (muxPort > 0)) || (!singleMulti && (muxPort > -1))) { if (!singleMulti) { uint8_t i = muxPort; @@ -322,7 +325,6 @@ void I2CMultiplexerSelectByBusAndMux(uint8_t i2cBus, bool singleMulti, int muxPo } SetI2CMultiplexer(i2cBus, toWrite); - } // As initially constructed by krikk in PR#254, quite adapted From 44e9d3776895fac46cf8f605bee34a9150039e57 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 22 Aug 2025 22:38:02 +0200 Subject: [PATCH 12/44] [Storage] [C016] Store Cache data in EEPROM --- .../DataStructs/RTC_cache_handler_struct.cpp | 86 +++++++++++++++++++ src/src/Helpers/EEPROMExternal.h | 16 +++- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/src/DataStructs/RTC_cache_handler_struct.cpp b/src/src/DataStructs/RTC_cache_handler_struct.cpp index 28027fc833..7e76e497a1 100644 --- a/src/src/DataStructs/RTC_cache_handler_struct.cpp +++ b/src/src/DataStructs/RTC_cache_handler_struct.cpp @@ -11,6 +11,10 @@ #include "../ESPEasyCore/ESPEasy_backgroundtasks.h" #include "../ESPEasyCore/ESPEasy_Log.h" +#if FEATURE_EEPROM_EXTERNAL +# include "../Helpers/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL + #ifdef ESP8266 # include #endif // ifdef ESP8266 @@ -26,6 +30,60 @@ RTC_NOINIT_ATTR RTC_cache_struct RTC_cache; RTC_NOINIT_ATTR uint8_t RTC_cache_data[RTC_CACHE_DATA_SIZE]; #endif // ifdef ESP32 +/********************************************************************************************** + * EEPROM Helper function for RTC Cache + **********************************************************************************************/ +#if FEATURE_EEPROM_EXTERNAL +void EEPROMSaveWritePos() { + const uint8_t eepromAddress = checkEEPROMEnabled(); + if (eepromAddress > 0) { + // Save new position + if (EEPROMExternal->readInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET) != RTC_cache.writePos) { + EEPROMExternal->writeInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET, RTC_cache.writePos); + } + } +} + +void EEPROMSaveMetaDataChecksum() { + const uint8_t eepromAddress = checkEEPROMEnabled(); + if (eepromAddress > 0) { + // Save metadata checksum + if (EEPROMExternal->readLong(EEPROM_RTC_CACHE_META_CRC_OFFSET) != RTC_cache.checksumMetadata) { + EEPROMExternal->writeLong(EEPROM_RTC_CACHE_META_CRC_OFFSET, RTC_cache.checksumMetadata); + } + } +} + +void EEPROMSaveCacheChecksum() { + const uint8_t eepromAddress = checkEEPROMEnabled(); + if (eepromAddress > 0) { + // Save cache checksum + if (EEPROMExternal->readLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET) != RTC_cache.checksumData) { + EEPROMExternal->writeLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET, RTC_cache.checksumData); + } + } +} + +void EEPROMSaveRTCcache() { + const uint8_t eepromAddress = checkEEPROMEnabled(); + if (eepromAddress > 0) { + size_t count{}; + for (uint16_t idx = 0; idx < sizeof(RTC_cache); ++idx) { + const uint32_t metaAddr = EEPROM_RTC_CACHE_META_OFFSET + idx; + const uint8_t oldData = EEPROMExternal->read(metaAddr); + const uint8_t newData = reinterpret_cast(&RTC_cache)[idx]; + if (oldData != newData) { + EEPROMExternal->write(metaAddr, newData); + ++count; + } + } + // Save metadata checksum + EEPROMSaveMetaDataChecksum(); + // Save cache checksum + EEPROMSaveCacheChecksum(); + } +} +#endif // if FEATURE_EEPROM_EXTERNAL /********************************************************************************************\ RTC located cache @@ -234,6 +292,24 @@ bool RTC_cache_handler_struct::write(const uint8_t *data, unsigned int size) { ++RTC_cache.writePos; } + #if FEATURE_EEPROM_EXTERNAL + const uint8_t eepromAddress = checkEEPROMEnabled(); + if (eepromAddress > 0) { + size_t count{}; + for (unsigned int idx = 0; idx < size; ++idx) { + const uint32_t cacheAddr = EEPROM_RTC_CACHE_START_OFFSET + idx; + const uint8_t oldData = EEPROMExternal->read(cacheAddr); + const uint8_t newData = data[idx]; + if (oldData != newData) { + EEPROMExternal->write(cacheAddr, newData); + ++count; + } + } + // Save new position + EEPROMSaveWritePos(); + } + #endif // if FEATURE_EEPROM_EXTERNAL + // Now store the updated part of the buffer to the RTC memory. // Pad some extra bytes around it to allow sample sizes not multiple of 4 bytes. int startOffset = RTC_cache.writePos - size; @@ -490,6 +566,11 @@ bool RTC_cache_handler_struct::saveRTCcache(unsigned int startOffset, size_t nrB { RTC_cache.checksumData = getDataChecksum(); RTC_cache.checksumMetadata = calc_CRC32(reinterpret_cast(&RTC_cache), sizeof(RTC_cache) - sizeof(uint32_t)); + + #if FEATURE_EEPROM_EXTERNAL + EEPROMSaveRTCcache(); + #endif // if FEATURE_EEPROM_EXTERNAL + #ifdef ESP32 return true; #endif // ifdef ESP32 @@ -558,6 +639,11 @@ void RTC_cache_handler_struct::clearRTCcacheData() { RTC_cache_data[i] = 0; } RTC_cache.writePos = 0; + + #if FEATURE_EEPROM_EXTERNAL + // Save new position + EEPROMSaveWritePos(); + #endif // if FEATURE_EEPROM_EXTERNAL } // Return true if any cache file found diff --git a/src/src/Helpers/EEPROMExternal.h b/src/src/Helpers/EEPROMExternal.h index e7132c368f..989c0a1198 100644 --- a/src/src/Helpers/EEPROMExternal.h +++ b/src/src/Helpers/EEPROMExternal.h @@ -1,10 +1,11 @@ #pragma once #include "../../ESPEasy_common.h" -#include "../DataTypes/TaskIndex.h" - #if FEATURE_EEPROM_EXTERNAL +#include "../DataTypes/TaskIndex.h" +#include "../DataStructs/RTCCacheStruct.h" + # include enum class EEPROMExternal_WriteProtect_e : uint8_t { @@ -25,8 +26,15 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; // Write the UserVar-checksum from this offset, right after the UserVar values # define EEPROM_USERVAR_CHECKSUM_OFFSET (EEPROM_USERVAR_START_OFFSET + (TASKS_MAX * VARS_PER_TASK * sizeof(uint32_t))) -// Offset for storing GPIO states, directly following the UserVar storage and checksum -# define EEPROM_GPIO_MCPPCF_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) +// Even when not enabled, we still reserve the space +# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) +# define EEPROM_RTC_CACHE_WRITEPOS_OFFSET (EEPROM_RTC_CACHE_START_OFFSET + RTC_CACHE_DATA_SIZE) +# define EEPROM_RTC_CACHE_META_OFFSET (EEPROM_RTC_CACHE_WRITEPOS_OFFSET + sizeof(uint16_t)) +# define EEPROM_RTC_CACHE_META_CRC_OFFSET (EEPROM_RTC_CACHE_META_OFFSET + sizeof(RTC_cache_struct)) +# define EEPROM_RTC_CACHE_CHECKSUM_OFFSET (EEPROM_RTC_CACHE_META_CRC_OFFSET + sizeof(uint32_t)) + +// Offset for storing GPIO states, directly following the RTC Cache storage and checksum +# define EEPROM_GPIO_MCPPCF_START_OFFSET (EEPROM_RTC_CACHE_CHECKSUM_OFFSET + sizeof(uint32_t)) // Choose an arbitrary but fixed offset # define EEPROM_CUSTOM_START_OFFSET (2048) From 3f3205d29da20d6c2de53b9491b2a7ecbc42eb0f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 23 Aug 2025 21:05:45 +0200 Subject: [PATCH 13/44] [Storage] Code improvements --- src/src/Helpers/StringProvider.cpp | 10 ++++++++++ src/src/Helpers/StringProvider.h | 5 +++++ src/src/WebServer/HardwarePage.cpp | 8 ++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index c9f6bdecae..2f9df6cdcf 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -146,6 +146,11 @@ const __FlashStringHelper * getLabel(LabelType::Enum label) { case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return F("Show Unit of Measure"); #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + #if FEATURE_EEPROM_EXTERNAL + case LabelType::EEPROM_RESTORE_ON_COLDBOOT: return F("Cold boot: Restore Task Values from EEPROM"); + case LabelType::EEPROM_RESTORE_ON_WARMBOOT: return F("Warm boot: Restore Task Values from EEPROM"); + #endif // if FEATURE_EEPROM_EXTERNAL + case LabelType::BOOT_TYPE: return F("Last Boot Cause"); case LabelType::BOOT_COUNT: return F("Boot Count"); case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return F("Deep Sleep Alternative"); @@ -442,6 +447,11 @@ String getValue(LabelType::Enum label) { case LabelType::SHOW_UOM_ON_DEVICES_PAGE: return jsonBool(Settings.ShowUnitOfMeasureOnDevicesPage()); #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + #if FEATURE_EEPROM_EXTERNAL + case LabelType::EEPROM_RESTORE_ON_COLDBOOT: return jsonBool(Settings.RestoreUserVarsFromEEPROMOnColdBoot()); + case LabelType::EEPROM_RESTORE_ON_WARMBOOT: return jsonBool(Settings.RestoreUserVarsFromEEPROMOnWarmBoot()); + #endif // if FEATURE_EEPROM_EXTERNAL + case LabelType::BOOT_TYPE: return getLastBootCauseString(); case LabelType::BOOT_COUNT: break; case LabelType::DEEP_SLEEP_ALTERNATIVE_CALL: return jsonBool(Settings.UseAlternativeDeepSleep()); diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index f15d3bc819..c919678b8f 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -94,6 +94,11 @@ struct LabelType { SHOW_UOM_ON_DEVICES_PAGE, #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + #if FEATURE_EEPROM_EXTERNAL + EEPROM_RESTORE_ON_COLDBOOT, + EEPROM_RESTORE_ON_WARMBOOT, + #endif // if FEATURE_EEPROM_EXTERNAL + BOOT_TYPE, // Cold boot BOOT_COUNT, // 0 RESET_REASON, // Software/System restart diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index 7787814d3b..4701c76a68 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -129,8 +129,8 @@ void handle_hardware() { #if FEATURE_EEPROM_EXTERNAL Settings.EEPROMExternalType(getFormItemInt(F("eepromtype"), static_cast(EEPROMExternal_Type_e::AT24C256))); Settings.EEPROMExternalI2CAddress(getFormItemInt(F("i2c_eeprom"), 0)); - Settings.RestoreUserVarsFromEEPROMOnColdBoot(isFormItemChecked(F("lduvcbee"))); - Settings.RestoreUserVarsFromEEPROMOnWarmBoot(isFormItemChecked(F("lduvwbee"))); + Settings.RestoreUserVarsFromEEPROMOnColdBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_COLDBOOT)); + Settings.RestoreUserVarsFromEEPROMOnWarmBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_WARMBOOT)); # if FEATURE_I2CMULTIPLEXER @@ -384,8 +384,8 @@ void handle_hardware() { if (isEEPROMExternalWriteProtected()) { addHtml(F(" Write-protected!")); } - addFormCheckBox(F("Cold boot: Restore Task Values from EEPROM"), F("lduvcbee"), Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); - addFormCheckBox(F("Warm boot: Restore Task Values from EEPROM"), F("lduvwbee"), Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); + addFormCheckBox(LabelType::EEPROM_RESTORE_ON_COLDBOOT, Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); + addFormCheckBox(LabelType::EEPROM_RESTORE_ON_WARMBOOT, Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); } #endif // if FEATURE_EEPROM_EXTERNAL From 11e96346a6f380464919efa6e5a419c428ea8b58 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 25 Aug 2025 12:44:10 +0200 Subject: [PATCH 14/44] [Storage] Move Settings attributes to next available bits --- src/src/DataStructs/SettingsStruct.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index e6aa54ae0f..a059a72cf5 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -604,11 +604,11 @@ class SettingsStruct_tmpl uint32_t DisableSaveConfigAsTar : 1; // Bit 05 uint32_t PassiveWiFiScan : 1; // Bit 06 // inverted uint32_t ShowUnitOfMeasureOnDevicesPage : 1; // Bit 07 // inverted - uint32_t RestoreUserVarsFromEEPROMOnColdBoot : 1; // Bit 08 - uint32_t RestoreUserVarsFromEEPROMOnWarmBoot : 1; // Bit 09 + uint32_t unused_08 : 1; // Bit 08 + uint32_t unused_09 : 1; // Bit 09 uint32_t unused_10 : 1; // Bit 10 - uint32_t unused_11 : 1; // Bit 11 - uint32_t unused_12 : 1; // Bit 12 + uint32_t RestoreUserVarsFromEEPROMOnColdBoot : 1; // Bit 11 + uint32_t RestoreUserVarsFromEEPROMOnWarmBoot : 1; // Bit 12 uint32_t unused_13 : 1; // Bit 13 uint32_t unused_14 : 1; // Bit 14 uint32_t unused_15 : 1; // Bit 15 From 7c1c2632ca8bdd5920e49a5579317459eb66cd70 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 27 Aug 2025 19:41:15 +0200 Subject: [PATCH 15/44] [Storage] Limit Rules values storage to max. 1024 slots --- src/src/Helpers/EEPROMExternal.cpp | 4 ++-- src/src/Helpers/EEPROMExternal.h | 4 ++-- src/src/WebServer/HardwarePage.cpp | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/src/Helpers/EEPROMExternal.cpp index 679c34c5e5..20cf799e75 100644 --- a/src/src/Helpers/EEPROMExternal.cpp +++ b/src/src/Helpers/EEPROMExternal.cpp @@ -226,7 +226,7 @@ uint32_t getEEPROMAddressForTaskValue(taskIndex_t task, taskVarIndex_t varNr) { } /** - * EEPROM available number of slots + * EEPROM available number of slots, max 1024 * NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! */ uint32_t getEEPROMMaxSlots() { @@ -234,7 +234,7 @@ uint32_t getEEPROMMaxSlots() { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); if (eepromSize > 0) { - const uint32_t slotMax = ((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_uint32_t; + const uint32_t slotMax = min((unsigned long)(((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_uint32_t), 1024ul); return slotMax; } diff --git a/src/src/Helpers/EEPROMExternal.h b/src/src/Helpers/EEPROMExternal.h index 989c0a1198..efe9c2bd53 100644 --- a/src/src/Helpers/EEPROMExternal.h +++ b/src/src/Helpers/EEPROMExternal.h @@ -41,9 +41,9 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; // NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! # if FEATURE_STRING_VARIABLES -# define EEPROM_CUSTOM_DIVISOR (2) // Split in slots- and strings- halves +# define EEPROM_CUSTOM_DIVISOR (2u) // Split in slots- and strings- halves # else // if FEATURE_STRING_VARIABLES -# define EEPROM_CUSTOM_DIVISOR (1) // Use all for slots +# define EEPROM_CUSTOM_DIVISOR (1u) // Use all for slots # endif // if FEATURE_STRING_VARIABLES // Enable/disable some models diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index 4701c76a68..c663bf00d6 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -381,8 +381,12 @@ void handle_hardware() { const bool eepromChecked = checkEEPROMEnabled() > 0; addRowLabel(F("EEPROM Enabled")); addEnabled(eepromChecked); - if (isEEPROMExternalWriteProtected()) { + if (eepromChecked && isEEPROMExternalWriteProtected()) { addHtml(F(" Write-protected!")); + } + if (eepromChecked && !isEEPROMExternalWriteProtected()) { + addRowLabel(F("'WriteEE' slots available")); + addHtmlInt(getEEPROMMaxSlots()); } addFormCheckBox(LabelType::EEPROM_RESTORE_ON_COLDBOOT, Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); addFormCheckBox(LabelType::EEPROM_RESTORE_ON_WARMBOOT, Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); From 8c7a88a793e1af19f7dbe52cb7b3f95cfff25f01 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 27 Aug 2025 19:44:32 +0200 Subject: [PATCH 16/44] [Storage] Separate EEPROM and FRAM types in settings --- src/src/DataStructs/DeviceStruct.h | 2 +- src/src/DataStructs_templ/SettingsStruct.cpp | 4 +- src/src/Helpers/EEPROMExternal.cpp | 42 +++++++++++++++++--- src/src/Helpers/EEPROMExternal.h | 17 ++++++-- src/src/WebServer/HardwarePage.cpp | 22 ++++++++++ 5 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 8762fcf8ce..98ca671c23 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -52,7 +52,7 @@ #if FEATURE_EEPROM_EXTERNAL #define EEPROM_EXTERNAL_FLAGS_ADDRESS 0 // bit-offset for the I2C Address (8 bits) -#define EEPROM_EXTERNAL_FLAGS_SIZE 8 // bit-offset for the size-id of the EEPROM (3 bits) +#define EEPROM_EXTERNAL_FLAGS_SIZE 8 // bit-offset for the size-id of the EEPROM (4 bits) #define EEPROM_EXTERNAL_FLAGS_MUX 16 // bit-offset for the multiplexer flags of the EEPROM (16 bits) #define EEPROM_MUX_FLAGS_PORT 0 // bit-offset within multiplexerflags for the portnr/bits (8 bits) diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index 7f64bb962f..6322f7a21b 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -1160,11 +1160,11 @@ void SettingsStruct_tmpl::EEPROMExternalI2CMultiplexerFlags(uint16_t mu } template uint8_t SettingsStruct_tmpl::EEPROMExternalType() const { - return static_cast(get3BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE)); + return static_cast(get4BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE)); } template void SettingsStruct_tmpl::EEPROMExternalType(uint8_t type) { - set3BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE, type); + set4BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE, type); } #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/src/Helpers/EEPROMExternal.cpp index 20cf799e75..8244b05ef1 100644 --- a/src/src/Helpers/EEPROMExternal.cpp +++ b/src/src/Helpers/EEPROMExternal.cpp @@ -106,22 +106,29 @@ uint8_t selectEEPROMI2CBusAndMultiplexer() { uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { switch (type) { case EEPROMExternal_Type_e::AT24C256: + case EEPROMExternal_Type_e::MB85RC256: return 32768; case EEPROMExternal_Type_e::AT24C512: + case EEPROMExternal_Type_e::MB85RC512: return 65536; # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: + case EEPROMExternal_Type_e::MB85RC1M: return 131072; # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: + case EEPROMExternal_Type_e::MB85RC2M: return 262144; # endif // if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C32: + case EEPROMExternal_Type_e::MB85RC32: return 4096; case EEPROMExternal_Type_e::AT24C64: + case EEPROMExternal_Type_e::MB85RC64: return 8192; case EEPROMExternal_Type_e::AT24C128: + case EEPROMExternal_Type_e::MB85RC128: return 16384; } return 0; @@ -136,22 +143,29 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, switch (type) { case EEPROMExternal_Type_e::AT24C256: + case EEPROMExternal_Type_e::MB85RC256: pageSize = 64; case EEPROMExternal_Type_e::AT24C512: + case EEPROMExternal_Type_e::MB85RC512: pageSize = 128; # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: + case EEPROMExternal_Type_e::MB85RC1M: pageSize = 128; # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: + case EEPROMExternal_Type_e::MB85RC2M: pageSize = 128; # endif // if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C32: + case EEPROMExternal_Type_e::MB85RC32: pageSize = 32; case EEPROMExternal_Type_e::AT24C64: + case EEPROMExternal_Type_e::MB85RC64: pageSize = 32; case EEPROMExternal_Type_e::AT24C128: + case EEPROMExternal_Type_e::MB85RC128: pageSize = 64; } return getEEPROMSize(type); @@ -165,12 +179,12 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { switch (type) { case EEPROMExternal_Type_e::AT24C256: - return F("AT24C256 / MB85RC256"); + return F("AT24C256"); case EEPROMExternal_Type_e::AT24C512: - return F("AT24C512 / MB85RC512"); + return F("AT24C512"); # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: - return F("AT24C1024 / MB85RC1M"); + return F("AT24C1024"); # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: @@ -179,9 +193,27 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { case EEPROMExternal_Type_e::AT24C32: return F("AT24C32"); case EEPROMExternal_Type_e::AT24C64: - return F("AT24C64 / MB85RC64"); + return F("AT24C64"); case EEPROMExternal_Type_e::AT24C128: - return F("AT24C128 / MB85RC128"); + return F("AT24C128"); + case EEPROMExternal_Type_e::MB85RC256: + return F("MB85RC256"); + case EEPROMExternal_Type_e::MB85RC512: + return F("MB85RC512"); + # if EEPROM_SUPPORT_AT24C1024 + case EEPROMExternal_Type_e::MB85RC1M: + return F("MB85RC1M"); + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::MB85RC2M: + return F("MB85RC2M"); + # endif // if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::MB85RC32: + return F("MB85RC32"); + case EEPROMExternal_Type_e::MB85RC64: + return F("MB85RC64"); + case EEPROMExternal_Type_e::MB85RC128: + return F("MB85RC128"); } return F(""); # else // ifndef BUILD_NO_DEBUG diff --git a/src/src/Helpers/EEPROMExternal.h b/src/src/Helpers/EEPROMExternal.h index efe9c2bd53..90ee0c4dde 100644 --- a/src/src/Helpers/EEPROMExternal.h +++ b/src/src/Helpers/EEPROMExternal.h @@ -3,8 +3,8 @@ #if FEATURE_EEPROM_EXTERNAL -#include "../DataTypes/TaskIndex.h" -#include "../DataStructs/RTCCacheStruct.h" +# include "../DataTypes/TaskIndex.h" +# include "../DataStructs/RTCCacheStruct.h" # include @@ -50,7 +50,7 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; # define EEPROM_SUPPORT_AT24C1024 1 # define EEPROM_SUPPORT_AT24C2048 0 -// Supported AT24Cxxx devices +// Supported AT24Cxxx and MB85RCxxx devices enum class EEPROMExternal_Type_e : uint8_t { AT24C256 = 0, // Default, 32 kB AT24C512 = 1, // 64 kB @@ -63,6 +63,17 @@ enum class EEPROMExternal_Type_e : uint8_t { AT24C32 = 4, // 4 kB, not endorsed, but widely available types: AT24C64 = 5, // 8 kB AT24C128 = 6, // 16 kB + MB85RC256 = 7, // 32 kB + MB85RC512 = 8, // 64 kB + # if EEPROM_SUPPORT_AT24C1024 + MB85RC1M = 9, // 128 kB + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 + MB85RC2M = 10, // 256 kB (not supported yet) + # endif // if EEPROM_SUPPORT_AT24C2048 + MB85RC32 = 11, // 4 kB, not endorsed, but widely available types: + MB85RC64 = 12, // 8 kB + MB85RC128 = 13, // 16 kB }; uint8_t checkEEPROMEnabled(); diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index c663bf00d6..a53556731d 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -333,6 +333,17 @@ void handle_hardware() { getEEPROMName(EEPROMExternal_Type_e::AT24C32), getEEPROMName(EEPROMExternal_Type_e::AT24C64), getEEPROMName(EEPROMExternal_Type_e::AT24C128), + getEEPROMName(EEPROMExternal_Type_e::MB85RC256), + getEEPROMName(EEPROMExternal_Type_e::MB85RC512), + #if EEPROM_SUPPORT_AT24C1024 + getEEPROMName(EEPROMExternal_Type_e::MB85RC1M), + #endif // if EEPROM_SUPPORT_AT24C1024 + #if EEPROM_SUPPORT_AT24C2048 + getEEPROMName(EEPROMExternal_Type_e::MB85RC2M), + #endif // if EEPROM_SUPPORT_AT24C2048 + getEEPROMName(EEPROMExternal_Type_e::MB85RC32), + getEEPROMName(EEPROMExternal_Type_e::MB85RC64), + getEEPROMName(EEPROMExternal_Type_e::MB85RC128), }; const int eepromTypes[] = { static_cast(EEPROMExternal_Type_e::AT24C256), @@ -346,6 +357,17 @@ void handle_hardware() { static_cast(EEPROMExternal_Type_e::AT24C32), static_cast(EEPROMExternal_Type_e::AT24C64), static_cast(EEPROMExternal_Type_e::AT24C128), + static_cast(EEPROMExternal_Type_e::MB85RC256), + static_cast(EEPROMExternal_Type_e::MB85RC512), + #if EEPROM_SUPPORT_AT24C1024 + static_cast(EEPROMExternal_Type_e::MB85RC1M), + #endif // if EEPROM_SUPPORT_AT24C1024 + #if EEPROM_SUPPORT_AT24C2048 + static_cast(EEPROMExternal_Type_e::MB85RC2M), + #endif // if EEPROM_SUPPORT_AT24C2048 + static_cast(EEPROMExternal_Type_e::MB85RC32), + static_cast(EEPROMExternal_Type_e::MB85RC64), + static_cast(EEPROMExternal_Type_e::MB85RC128), }; constexpr uint8_t eepromSizeCount = NR_ELEMENTS(eepromTypes); FormSelectorOptions eepromSizeSelector(eepromSizeCount, eepromOptions, eepromTypes); From bd67cc8d30b3be4076ee589112b265906cd41000 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 27 Aug 2025 19:47:42 +0200 Subject: [PATCH 17/44] [Storage] Add setting per Task Value for saving in EEPROM (enabled by default) --- src/src/DataStructs/Caches.cpp | 18 ++++++++++++ src/src/DataStructs/Caches.h | 10 ++++++- .../DataStructs/ExtraTaskSettingsStruct.cpp | 13 +++++++++ src/src/DataStructs/ExtraTaskSettingsStruct.h | 12 ++++++++ src/src/Helpers/ESPEasyRTC.cpp | 27 +++++++++++++---- src/src/WebServer/DevicesPage.cpp | 29 +++++++++++++++++++ src/src/WebServer/EpromVarPage.cpp | 2 +- 7 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/src/DataStructs/Caches.cpp b/src/src/DataStructs/Caches.cpp index b3fc6ac5c8..b4604a6917 100644 --- a/src/src/DataStructs/Caches.cpp +++ b/src/src/DataStructs/Caches.cpp @@ -324,6 +324,21 @@ uint8_t Caches::getTaskVarCustomVType(taskIndex_t TaskIndex, #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE +#if FEATURE_EEPROM_EXTERNAL +uint8_t Caches::getTaskVarStoreInEEPROM(taskIndex_t TaskIndex, + taskVarIndex_t taskVarIndex) { + if (validTaskIndex(TaskIndex) && (validTaskVarIndex(taskVarIndex))) { + auto it = getExtraTaskSettings(TaskIndex); + + if (it != extraTaskSettings_cache.end()) { + return it->second.storeInEEPROM[taskVarIndex]; + } + } + return false; +} + +#endif // if FEATURE_EEPROM_EXTERNAL + void Caches::updateExtraTaskSettingsCache() { const taskIndex_t TaskIndex = ExtraTaskSettings.TaskIndex; @@ -374,6 +389,9 @@ void Caches::updateExtraTaskSettingsCache() #if FEATURE_CUSTOM_TASKVAR_VTYPE tmp.customVType[i] = ExtraTaskSettings.getTaskVarCustomVType(i); #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_EEPROM_EXTERNAL + tmp.storeInEEPROM[i] = ExtraTaskSettings.getTaskVarStoreInEEPROM(i); + #endif // if FEATURE_EEPROM_EXTERNAL } #ifdef ESP32 tmp.TaskDevicePluginConfigLong_index_used = 0; diff --git a/src/src/DataStructs/Caches.h b/src/src/DataStructs/Caches.h index 46fcd0f64f..f4ba0d166e 100644 --- a/src/src/DataStructs/Caches.h +++ b/src/src/DataStructs/Caches.h @@ -57,6 +57,9 @@ struct ExtraTaskSettings_cache_t { #if FEATURE_CUSTOM_TASKVAR_VTYPE uint8_t customVType[VARS_PER_TASK] = { 0 }; // single-value VType per taskValue #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_EEPROM_EXTERNAL + bool storeInEEPROM[VARS_PER_TASK]{}; + #endif // if FEATURE_EEPROM_EXTERNAL }; typedef std::map TaskIndexNameMap; @@ -123,7 +126,12 @@ struct Caches { uint8_t getTaskVarCustomVType(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex); #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE - + + #if FEATURE_EEPROM_EXTERNAL + uint8_t getTaskVarStoreInEEPROM(taskIndex_t taskIndex, + taskVarIndex_t taskVarIndex); + #endif // if FEATURE_EEPROM_EXTERNAL + // Update all cached values, except the checksum. void updateExtraTaskSettingsCache(); diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp index 1a474eafd5..a5a9366ea2 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp @@ -290,6 +290,19 @@ void ExtraTaskSettingsStruct::setTaskVarCustomVType(taskVarIndex_t taskVarIndex, } #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE +#if FEATURE_EEPROM_EXTERNAL +bool ExtraTaskSettingsStruct::getTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex) const { // Inverted + if (!validTaskVarIndex(taskVarIndex)) { return false; } + return !bitRead(VariousBits[taskVarIndex], 24); +} + +void ExtraTaskSettingsStruct::setTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex, + bool store) { // Inverted + if (validTaskVarIndex(taskVarIndex)) { + bitWrite(VariousBits[taskVarIndex], 24, !store); + } +} +#endif // if FEATURE_EEPROM_EXTERNAL void ExtraTaskSettingsStruct::populateDeviceValueNamesSeq( const __FlashStringHelper *valuename, diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.h b/src/src/DataStructs/ExtraTaskSettingsStruct.h index 687ebe5577..7b9609e4d9 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.h +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.h @@ -84,6 +84,12 @@ struct ExtraTaskSettingsStruct uint8_t customVType); #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_EEPROM_EXTERNAL + bool getTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex) const; + void setTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex, + bool store); + #endif // if FEATURE_EEPROM_EXTERNAL + void populateDeviceValueNamesSeq(const __FlashStringHelper *valuename, size_t nrValues, uint8_t defaultDecimals, @@ -102,6 +108,12 @@ struct ExtraTaskSettingsStruct float TaskDeviceMaxValue[VARS_PER_TASK]; float TaskDeviceErrorValue[VARS_PER_TASK]; uint32_t VariousBits[VARS_PER_TASK]; + /** Mapping of VariousBits: + * - 0..7 : PluginStats config (8 bits) + * - 8..15 : UnitOfMeasure index (8 bits) + * - 16..23 : CustomValueType index (8 bits) + * - 24 : Store value in EEPROM (1 bit, inverted) + */ }; diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 92b5528787..f3aa98f958 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -13,9 +13,13 @@ #include "../../ESPEasy_common.h" #if FEATURE_EEPROM_EXTERNAL +#include "../Globals/Cache.h" #include "../Helpers/EEPROMExternal.h" +#include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/Hardware_I2C.h" #include "../Helpers/StringConverter.h" + +uint8_t readDataForUserVars(size_t index); // Forward declaration #endif // if FEATURE_EEPROM_EXTERNAL #ifdef ESP8266 @@ -195,11 +199,13 @@ bool saveUserVarToRTC(bool initial) for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); if (taskValues != nullptr) { + LoadTaskSettings(task); for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - const size_t index = (task * VARS_PER_TASK) + varNr; - const uint32_t newData = taskValues->getUint32(varNr); // Only update EEPROM if data differs + const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) + ? taskValues->getUint32(varNr) + : std::numeric_limits::max(); // NaN when read as float const uint32_t addr = getEEPROMAddressForTaskValue(task, varNr); - if (newData != EEPROMExternal->readLong(addr)) { + if (newData != EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs EEPROMExternal->writeLong(addr, newData); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; @@ -208,8 +214,10 @@ bool saveUserVarToRTC(bool initial) } } } + // Calculate checksum for all stored values, not equal to the UserVar checksum! + const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, - UserVar.compute_CRC32()); + calcsum); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; startmicros = micros() - startmicros; @@ -295,14 +303,21 @@ bool readUserVarFromRTC() if (calcsum == checksum) { addLog(LOG_LEVEL_INFO, F("INIT : Restoring Task Values from EEPROM.")); result = true; + taskIndex_t lastTask = TASKS_MAX; for (size_t i = 0; i < (TASKS_MAX * VARS_PER_TASK) && result; ++i) { const taskIndex_t taskIndex = i / VARS_PER_TASK; const uint8_t varNr = i % VARS_PER_TASK; + if (taskIndex != lastTask) { + LoadTaskSettings(taskIndex); + lastTask = taskIndex; + } // Store in raw form, so we don't apply formula as we don't really know what type is required. const uint32_t addr = getEEPROMAddressForTaskValue(taskIndex, varNr); if (addr != std::numeric_limits::max()) { - TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(taskIndex); - taskValues->setUint32(varNr, EEPROMExternal->readLong(addr)); + if (Cache.getTaskVarStoreInEEPROM(taskIndex, varNr)) { // Restore only when enabled + TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(taskIndex); + taskValues->setUint32(varNr, EEPROMExternal->readLong(addr)); + } } else { result = false; } diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 70a2fc6c21..c8c015a3d4 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -29,6 +29,9 @@ # include "../Helpers/_Plugin_init.h" # include "../Helpers/_Plugin_SensorTypeHelper.h" # include "../Helpers/_Plugin_Helper_serial.h" +#if FEATURE_EEPROM_EXTERNAL +# include "../Helpers/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/I2C_Plugin_Helper.h" # include "../Helpers/StringConverter.h" @@ -426,6 +429,12 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task # if FEATURE_PLUGIN_FILTER ExtraTaskSettings.enablePluginFilter(varNr, isFormItemChecked(getPluginCustomArgName(F("TDFIL"), varNr))); # endif // if FEATURE_PLUGIN_FILTER + #if FEATURE_EEPROM_EXTERNAL + if (checkEEPROMEnabled() > 0) { + ExtraTaskSettings.setTaskVarStoreInEEPROM(varNr, isFormItemChecked(getPluginCustomArgName(F("TDEE"), varNr))); + } + #endif // if FEATURE_EEPROM_EXTERNAL + # if FEATURE_PLUGIN_STATS PluginStats_Config_t pluginStats_Config; pluginStats_Config.setEnabled(isFormItemChecked(getPluginCustomArgName(F("TDS"), varNr))); @@ -1705,6 +1714,13 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde ++colCount; } + #if FEATURE_EEPROM_EXTERNAL + if (checkEEPROMEnabled() > 0) { + html_table_header(F("Eeprom"), 30); + ++colCount; + } + #endif // if FEATURE_EEPROM_EXTERNAL + # if FEATURE_PLUGIN_STATS if (device.PluginStats) @@ -1776,6 +1792,19 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); } + #if FEATURE_EEPROM_EXTERNAL + if (checkEEPROMEnabled() > 0) { + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDEE"), varNr), + Cache.getTaskVarStoreInEEPROM(taskIndex, varNr), false + #if FEATURE_TOOLTIPS + , F("Store value in External EEPROM") + #endif // if FEATURE_TOOLTIPS + ); + } + #endif // if FEATURE_EEPROM_EXTERNAL + # if FEATURE_PLUGIN_STATS if (device.PluginStats) diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp index 54cc4f859b..ad6fdf415e 100644 --- a/src/src/WebServer/EpromVarPage.cpp +++ b/src/src/WebServer/EpromVarPage.cpp @@ -67,7 +67,7 @@ void handle_eepromvars() { const float value = EEPROMExternal->readFloat(addr); const uint32_t data = EEPROMExternal->readLong(addr); - if (isnan(value) || (addr == std::numeric_limits::max())) { + if (isnan(value) || (addr == std::numeric_limits::max()) || !ExtraTaskSettings.getTaskVarStoreInEEPROM(var)) { addHtml('-'); } else { addHtml(strformat(F("%s (0x%04x)"), floatToString(value, ExtraTaskSettings.TaskDeviceValueDecimals[var]), data)); From 6a3cc81cc486c677c06017baf885b98e3c06f594 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 27 Aug 2025 19:49:01 +0200 Subject: [PATCH 18/44] [Storage] Add [ReadEE#WP] to retrieve the Write Protected state of the EEPROM, 1 = write-protected --- src/src/Helpers/StringParser.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index 18448dbc3a..8db683d97a 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -211,6 +211,8 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us value = toString(readEEPROMSlot(slot)); } else if (valueName.equalsIgnoreCase(F("max"))) { value = getEEPROMMaxSlots(); + } else if (valueName.equalsIgnoreCase(F("wp"))) { + value = isEEPROMExternalWriteProtected() ? 1 : 0; } if (!value.isEmpty()) { transformValue( From a1134640689b8e956149856808a9ac3a8868f846 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 27 Aug 2025 20:36:19 +0200 Subject: [PATCH 19/44] [Storage] Simplify EpromVarPage by only showing enabled tasks, and not show hex display for 0 values --- src/src/WebServer/EpromVarPage.cpp | 61 +++++++++++++++++------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp index ad6fdf415e..c3ae9eb695 100644 --- a/src/src/WebServer/EpromVarPage.cpp +++ b/src/src/WebServer/EpromVarPage.cpp @@ -24,7 +24,8 @@ void handle_eepromvars() { if (!isLoggedIn()) { return; } - const bool showTasks = getFormItemInt(F("tasks"), 0) != 0; + const bool showTasks = getFormItemInt(F("tasks"), 1) == 1; + const bool allTasks = getFormItemInt(F("enabled"), 0) != 0; TXBuffer.startStream(); sendHeadandTail_stdtemplate(_HEAD); @@ -42,38 +43,46 @@ void handle_eepromvars() { html_TR(); // sub-table header - html_table_header(F("Task"), 300); - html_table_header(F("Value"), 500); - html_table_header(F("Content"), 400); + html_table_header(allTasks ? F("Task") : F("Task (Enabled)"), 300); + html_table_header(F("Value"), 500); + html_table_header(F("Content"), 400); html_table_header(F("")); for (taskIndex_t tsk = 0; tsk < TASKS_MAX; ++tsk) { - LoadTaskSettings(tsk); - html_TR_TD(); - addHtmlInt(tsk + 1); - addHtml(' '); - addHtml(getTaskDeviceName(tsk)); + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(tsk); - for (taskVarIndex_t var = 0; var < VARS_PER_TASK; ++var) { - if (var != 0) { - html_TR_TD(); - } - html_TD(); - addHtmlInt(var + 1); + if ((Settings.TaskDeviceEnabled[tsk] && validDeviceIndex(DeviceIndex)) || allTasks) { + LoadTaskSettings(tsk); + html_TR_TD(); + addHtmlInt(tsk + 1); addHtml(' '); - addHtml(getTaskValueName(tsk, var)); - html_TD(); - const uint32_t addr = getEEPROMAddressForTaskValue(tsk, var); - const float value = EEPROMExternal->readFloat(addr); - const uint32_t data = EEPROMExternal->readLong(addr); - - if (isnan(value) || (addr == std::numeric_limits::max()) || !ExtraTaskSettings.getTaskVarStoreInEEPROM(var)) { - addHtml('-'); - } else { - addHtml(strformat(F("%s (0x%04x)"), floatToString(value, ExtraTaskSettings.TaskDeviceValueDecimals[var]), data)); + addHtml(getTaskDeviceName(tsk)); + + for (taskVarIndex_t var = 0; var < VARS_PER_TASK; ++var) { + if (var != 0) { + html_TR_TD(); + } + html_TD(); + addHtmlInt(var + 1); + addHtml(' '); + addHtml(getTaskValueName(tsk, var)); + html_TD(); + const uint32_t addr = getEEPROMAddressForTaskValue(tsk, var); + const float value = EEPROMExternal->readFloat(addr); + const uint32_t data = EEPROMExternal->readLong(addr); + + if (isnan(value) || (addr == std::numeric_limits::max()) || !ExtraTaskSettings.getTaskVarStoreInEEPROM(var)) { + addHtml('-'); + } else { + addHtml(floatToString(value, ExtraTaskSettings.TaskDeviceValueDecimals[var])); + + if (!essentiallyZero(value)) { // Only show hex values for non-zero values to reduce eye-strain + addHtml(strformat(F(" (0x%04x)"), data)); + } + } } + delay(0); } - delay(0); } } html_TR(); From 1708c737cb1782c9b21293f182a5985bc6e69cdf Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 28 Aug 2025 13:08:27 +0200 Subject: [PATCH 20/44] [Storage] Move EEPROM code to ESPEasy::eeprom namespace, store and check EEPROM parameters --- .../eeprom}/Helpers/EEPROMExternal.cpp | 168 +++++++++++++++--- .../eeprom}/Helpers/EEPROMExternal.h | 57 ++++-- src/src/Commands/EEPROMExternal.cpp | 14 +- .../DataStructs/RTC_cache_handler_struct.cpp | 32 ++-- src/src/ESPEasyCore/ESPEasy_setup.cpp | 4 +- src/src/Helpers/ESPEasyRTC.cpp | 50 +++--- src/src/Helpers/Hardware_I2C.cpp | 56 +----- src/src/Helpers/StringParser.cpp | 8 +- src/src/WebServer/DevicesPage.cpp | 8 +- src/src/WebServer/EpromVarPage.cpp | 23 +-- src/src/WebServer/HardwarePage.cpp | 69 +++---- 11 files changed, 299 insertions(+), 190 deletions(-) rename src/{src => ESPEasy/eeprom}/Helpers/EEPROMExternal.cpp (61%) rename src/{src => ESPEasy/eeprom}/Helpers/EEPROMExternal.h (62%) diff --git a/src/src/Helpers/EEPROMExternal.cpp b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp similarity index 61% rename from src/src/Helpers/EEPROMExternal.cpp rename to src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp index 8244b05ef1..1c4bbb98ac 100644 --- a/src/src/Helpers/EEPROMExternal.cpp +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp @@ -1,16 +1,142 @@ #include "../Helpers/EEPROMExternal.h" -#include "../Globals/Settings.h" -#include "../Helpers/I2C_access.h" -#include "../../ESPEasy_common.h" -#include "../Helpers/StringConverter.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/I2C_access.h" +#include "../../../ESPEasy_common.h" +#include "../../../src/Helpers/StringConverter.h" #if FEATURE_EEPROM_EXTERNAL +namespace ESPEasy { +namespace eeprom { AT24CX *EEPROMExternal = nullptr; EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::Undefined; constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); +/** + * Initialize the external EEPROM device and variables + */ +void initializeEEPROMExternal() { + const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); + + if ((nullptr != ESPEasy::eeprom::EEPROMExternal) || (eepromAddress == 0)) { // Cleanup when turning off EEPROM + delete ESPEasy::eeprom::EEPROMExternal; + ESPEasy::eeprom::EEPROMExternal = nullptr; + ESPEasy::eeprom::EEPROMExternalWriteProtect = ESPEasy::eeprom::EEPROMExternal_WriteProtect_e::Undefined; + } + + if ((nullptr == ESPEasy::eeprom::EEPROMExternal) && (eepromAddress > 0)) { + const ESPEasy::eeprom::EEPROMExternal_Type_e eepromType = + static_cast(Settings.EEPROMExternalType()); + + if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + // We have an I2C device at this address, let's assume it's an EEPROM... + uint8_t pageSize = 0; + const uint32_t eepromSize = ESPEasy::eeprom::getEEPROMSize(eepromType, pageSize); + ESPEasy::eeprom::EEPROMExternal = new (std::nothrow) AT24CX(eepromAddress, pageSize, eepromSize); + + if (nullptr != ESPEasy::eeprom::EEPROMExternal) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: %s initialized at address 0x%02x"), + FsP(ESPEasy::eeprom::getEEPROMName(eepromType)), + eepromAddress)); + } + + ESPEasy::eeprom::checkEEPROMExternalWriteProtected(); + + if (ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { + addLog(LOG_LEVEL_INFO, concat(F("EEPROM: Write-protected! Status: "), + static_cast(ESPEasy::eeprom::checkEEPROMExternalWriteProtected()))); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat(F("EEPROM: Initialization of %s failed"), + FsP(ESPEasy::eeprom::getEEPROMName(eepromType)))); + } + } + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat(F("EEPROM: No %s found at address 0x%02x"), + FsP(ESPEasy::eeprom::getEEPROMName(eepromType)), + eepromAddress)); + } + } + + # if FEATURE_I2CMULTIPLEXER + I2CMultiplexerOff( + # if FEATURE_I2C_MULTIPLE + Settings.getI2CInterfaceEEPROM() + # else // if FEATURE_I2C_MULTIPLE + 0 + # endif // if FEATURE_I2C_MULTIPLE + ); // Restore the Multiplexer channel + # endif // if FEATURE_I2CMULTIPLEXER + } +} + +/** + * Check the stored parameters in the EEPROM with current data and settings + * - Version + * - Max. tasks + * - Vars per tasks + * - RTC Cache address + * - Pinstate address + */ +bool validateEEPROMExternalParameters() { + const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); + const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); + const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); + const uint32_t eepromRtcCacheParam = EEPROMExternal->readLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS); + const uint32_t eepromPinstateParam = EEPROMExternal->readLong(EEPROM_PARAMS_PINSTATE_ADDRESS); + + if ((EEPROM_PARAMS_CURRENT_VERSION == eepromVersionParam) && + (TASKS_MAX == eepromTasksMaxParam) && + (VARS_PER_TASK == eepromVarsPerTaskParam) && + (EEPROM_RTC_CACHE_START_OFFSET == eepromRtcCacheParam) && + (EEPROM_GPIO_PINSTATE_START_OFFSET == eepromPinstateParam) && + (EEPROM_GPIO_PINSTATE_END_OFFSET <= EEPROM_CUSTOM_START_OFFSET)) { + return true; + } + + return false; +} + +/** + * Update the stored parameters in EEPROM + * - Version + * - Max. tasks + * - Vars per tasks + * - RTC Cache address + * - Pinstate address + */ +void updateEEPROMExternalParameters() { + const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); + const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); + const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); + const uint32_t eepromRtcCacheParam = EEPROMExternal->readInt(EEPROM_PARAMS_RTC_CACHE_ADDRESS); + const uint32_t eepromPinstateParam = EEPROMExternal->readInt(EEPROM_PARAMS_PINSTATE_ADDRESS); + + if (EEPROM_PARAMS_CURRENT_VERSION != eepromVersionParam) { + EEPROMExternal->writeInt(EEPROM_PARAMS_VERSION_ADDRESS, EEPROM_PARAMS_CURRENT_VERSION); + } + + if (TASKS_MAX != eepromTasksMaxParam) { + EEPROMExternal->writeInt(EEPROM_PARAMS_TASKS_MAX, TASKS_MAX); + } + + if (VARS_PER_TASK != eepromVarsPerTaskParam) { + EEPROMExternal->writeInt(EEPROM_PARAMS_VARS_PER_TASK, VARS_PER_TASK); + } + + if (EEPROM_RTC_CACHE_START_OFFSET != eepromRtcCacheParam) { + EEPROMExternal->writeLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS, EEPROM_RTC_CACHE_START_OFFSET); + } + + if (EEPROM_GPIO_PINSTATE_START_OFFSET != eepromPinstateParam) { + EEPROMExternal->writeLong(EEPROM_PARAMS_PINSTATE_ADDRESS, EEPROM_GPIO_PINSTATE_START_OFFSET); + } +} + /** * Check if the EEPROM is properly initialized and enabled. * Returns the I2C address if all is OK @@ -107,29 +233,29 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { switch (type) { case EEPROMExternal_Type_e::AT24C256: case EEPROMExternal_Type_e::MB85RC256: - return 32768; + return 32768ul; case EEPROMExternal_Type_e::AT24C512: case EEPROMExternal_Type_e::MB85RC512: - return 65536; + return 65536ul; # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: case EEPROMExternal_Type_e::MB85RC1M: - return 131072; + return 131072ul; # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: case EEPROMExternal_Type_e::MB85RC2M: - return 262144; + return 262144ul; # endif // if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C32: case EEPROMExternal_Type_e::MB85RC32: - return 4096; + return 4096ul; case EEPROMExternal_Type_e::AT24C64: case EEPROMExternal_Type_e::MB85RC64: - return 8192; + return 8192ul; case EEPROMExternal_Type_e::AT24C128: case EEPROMExternal_Type_e::MB85RC128: - return 16384; + return 16384ul; } return 0; } @@ -144,29 +270,29 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, switch (type) { case EEPROMExternal_Type_e::AT24C256: case EEPROMExternal_Type_e::MB85RC256: - pageSize = 64; + pageSize = 64u; case EEPROMExternal_Type_e::AT24C512: case EEPROMExternal_Type_e::MB85RC512: - pageSize = 128; + pageSize = 128u; # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: case EEPROMExternal_Type_e::MB85RC1M: - pageSize = 128; + pageSize = 128u; # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: case EEPROMExternal_Type_e::MB85RC2M: - pageSize = 128; + pageSize = 128u; # endif // if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C32: case EEPROMExternal_Type_e::MB85RC32: - pageSize = 32; + pageSize = 32u; case EEPROMExternal_Type_e::AT24C64: case EEPROMExternal_Type_e::MB85RC64: - pageSize = 32; + pageSize = 32u; case EEPROMExternal_Type_e::AT24C128: case EEPROMExternal_Type_e::MB85RC128: - pageSize = 64; + pageSize = 64u; } return getEEPROMSize(type); } @@ -266,7 +392,8 @@ uint32_t getEEPROMMaxSlots() { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); if (eepromSize > 0) { - const uint32_t slotMax = min((unsigned long)(((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_uint32_t), 1024ul); + const uint32_t slotMax = min((unsigned long)(((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_uint32_t), + 1024ul); return slotMax; } @@ -303,5 +430,6 @@ float readEEPROMSlot(uint32_t slot) { } return 0.0f; } - +} // namespace eeprom +} // namespace ESPEasy #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h similarity index 62% rename from src/src/Helpers/EEPROMExternal.h rename to src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index 90ee0c4dde..071bdec32f 100644 --- a/src/src/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -1,13 +1,17 @@ #pragma once -#include "../../ESPEasy_common.h" +#include "../../../ESPEasy_common.h" #if FEATURE_EEPROM_EXTERNAL -# include "../DataTypes/TaskIndex.h" -# include "../DataStructs/RTCCacheStruct.h" +# include "../../../src/DataTypes/TaskIndex.h" +# include "../../../src/DataStructs/PortStatusStruct.h" +# include "../../../src/DataStructs/RTCCacheStruct.h" +# include "../../../src/DataStructs/RTCStruct.h" # include +namespace ESPEasy { +namespace eeprom { enum class EEPROMExternal_WriteProtect_e : uint8_t { Undefined = 0, ReadWrite = 1, @@ -17,9 +21,19 @@ enum class EEPROMExternal_WriteProtect_e : uint8_t { extern AT24CX *EEPROMExternal; extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; +# define EEPROM_PARAMS_CURRENT_VERSION (1) // Let's start with version 1 +# define EEPROM_PINSTATE_ELEMENT_COUNT (64) // Number of PinState elements (16 bytes each) to store in EEPROM + // Start writing the base RTC struct from this offset (not currently saving this to EEPROM) // TODO # define EEPROM_BASERTC_START_OFFSET (0) +// Some system parameters to check before restoring anything +# define EEPROM_PARAMS_VERSION_ADDRESS (32) +# define EEPROM_PARAMS_TASKS_MAX (EEPROM_PARAMS_VERSION_ADDRESS + sizeof(uint16_t)) +# define EEPROM_PARAMS_VARS_PER_TASK (EEPROM_PARAMS_TASKS_MAX + sizeof(uint16_t)) +# define EEPROM_PARAMS_RTC_CACHE_ADDRESS (EEPROM_PARAMS_VARS_PER_TASK + sizeof(uint16_t)) +# define EEPROM_PARAMS_PINSTATE_ADDRESS (EEPROM_PARAMS_RTC_CACHE_ADDRESS + sizeof(uint32_t)) + // Start writing the UserVar values from this offset, should be > sizeof(RTCStruct) that is 32 currently # define EEPROM_USERVAR_START_OFFSET (EEPROM_BASERTC_START_OFFSET + 128) @@ -33,8 +47,10 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; # define EEPROM_RTC_CACHE_META_CRC_OFFSET (EEPROM_RTC_CACHE_META_OFFSET + sizeof(RTC_cache_struct)) # define EEPROM_RTC_CACHE_CHECKSUM_OFFSET (EEPROM_RTC_CACHE_META_CRC_OFFSET + sizeof(uint32_t)) -// Offset for storing GPIO states, directly following the RTC Cache storage and checksum -# define EEPROM_GPIO_MCPPCF_START_OFFSET (EEPROM_RTC_CACHE_CHECKSUM_OFFSET + sizeof(uint32_t)) +// Offset for storing GPIO pinstates, directly following the RTC Cache storage and checksum +# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_RTC_CACHE_CHECKSUM_OFFSET + sizeof(uint32_t)) +# define EEPROM_PINSTATE_ELEMENT_SIZE (sizeof(uint32_t) + sizeof(portStatusStruct)) +# define EEPROM_GPIO_PINSTATE_END_OFFSET (EEPROM_GPIO_PINSTATE_START_OFFSET + (EEPROM_PINSTATE_ELEMENT_COUNT * EEPROM_PINSTATE_ELEMENT_SIZE)) // Choose an arbitrary but fixed offset # define EEPROM_CUSTOM_START_OFFSET (2048) @@ -52,30 +68,35 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; // Supported AT24Cxxx and MB85RCxxx devices enum class EEPROMExternal_Type_e : uint8_t { - AT24C256 = 0, // Default, 32 kB - AT24C512 = 1, // 64 kB + AT24C256 = 0, // Default, 32 kB + AT24C512 = 1, // 64 kB # if EEPROM_SUPPORT_AT24C1024 - AT24C1024 = 2, // 128 kB + AT24C1024 = 2, // 128 kB # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 - AT24C2048 = 3, // 256 kB (not supported yet) + AT24C2048 = 3, // 256 kB (not supported yet) # endif // if EEPROM_SUPPORT_AT24C2048 - AT24C32 = 4, // 4 kB, not endorsed, but widely available types: - AT24C64 = 5, // 8 kB - AT24C128 = 6, // 16 kB + AT24C32 = 4, // 4 kB, not endorsed, but widely available + AT24C64 = 5, // 8 kB + AT24C128 = 6, // 16 kB MB85RC256 = 7, // 32 kB MB85RC512 = 8, // 64 kB # if EEPROM_SUPPORT_AT24C1024 - MB85RC1M = 9, // 128 kB + MB85RC1M = 9, // 128 kB # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 - MB85RC2M = 10, // 256 kB (not supported yet) + MB85RC2M = 10, // 256 kB (not supported yet) # endif // if EEPROM_SUPPORT_AT24C2048 - MB85RC32 = 11, // 4 kB, not endorsed, but widely available types: - MB85RC64 = 12, // 8 kB - MB85RC128 = 13, // 16 kB + MB85RC32 = 11, // 4 kB, not endorsed, possibly not available + MB85RC64 = 12, // 8 kB + MB85RC128 = 13, // 16 kB }; +void initializeEEPROMExternal(); + +bool validateEEPROMExternalParameters(); +void updateEEPROMExternalParameters(); + uint8_t checkEEPROMEnabled(); EEPROMExternal_WriteProtect_e checkEEPROMExternalWriteProtected(bool forced = false); bool isEEPROMExternalWriteProtected(); @@ -94,4 +115,6 @@ uint32_t getEEPROMMaxSlots(); bool writeEEPROMSlot(uint32_t slot, float data); float readEEPROMSlot(uint32_t slot); +} // namespace eeprom +} // namespace ESPEasy #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Commands/EEPROMExternal.cpp b/src/src/Commands/EEPROMExternal.cpp index 92c94ed6bd..598fb044e7 100644 --- a/src/src/Commands/EEPROMExternal.cpp +++ b/src/src/Commands/EEPROMExternal.cpp @@ -1,5 +1,5 @@ #include "../Commands/EEPROMExternal.h" -#include "../Helpers/EEPROMExternal.h" +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #include "../../ESPEasy_common.h" @@ -21,19 +21,19 @@ const __FlashStringHelper* Command_writeEE(struct EventStruct *event, const char float value{}; if (validUIntFromString(parseString(Line, 2), slot) && validFloatFromString(parseString(Line, 3), value)) { - return return_command_boolean_result_flashstr(writeEEPROMSlot(slot, value)); + return return_command_boolean_result_flashstr(ESPEasy::eeprom::writeEEPROMSlot(slot, value)); } else if (equals(parseString(Line, 2), F("erase")) && equals(parseString(Line, 3), F("erase"))) { - for (uint32_t slot = 0; slot < getEEPROMMaxSlots(); ++slot) { - writeEEPROMSlot(slot, 0.0f); + for (uint32_t slot = 0; slot < ESPEasy::eeprom::getEEPROMMaxSlots(); ++slot) { + ESPEasy::eeprom::writeEEPROMSlot(slot, 0.0f); } addLog(LOG_LEVEL_INFO, F("EEPROM: All slot-values erased.")); return return_command_success_flashstr(); } else if (equals(parseString(Line, 2), F("check")) && equals(parseString(Line, 3), F("wp"))) { addLog(LOG_LEVEL_INFO, F("EEPROM: Check write-protect.")); - checkEEPROMExternalWriteProtected(true); + ESPEasy::eeprom::checkEEPROMExternalWriteProtected(true); - if (isEEPROMExternalWriteProtected()) { - addLog(LOG_LEVEL_INFO, concat(F("EEPROM: Write-protected! Status: "), static_cast(checkEEPROMExternalWriteProtected()))); + if (ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { + addLog(LOG_LEVEL_INFO, concat(F("EEPROM: Write-protected! Status: "), static_cast(ESPEasy::eeprom::checkEEPROMExternalWriteProtected()))); } return return_command_success_flashstr(); } diff --git a/src/src/DataStructs/RTC_cache_handler_struct.cpp b/src/src/DataStructs/RTC_cache_handler_struct.cpp index 7e76e497a1..30f4f335e9 100644 --- a/src/src/DataStructs/RTC_cache_handler_struct.cpp +++ b/src/src/DataStructs/RTC_cache_handler_struct.cpp @@ -12,7 +12,7 @@ #include "../ESPEasyCore/ESPEasy_Log.h" #if FEATURE_EEPROM_EXTERNAL -# include "../Helpers/EEPROMExternal.h" +# include "../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL #ifdef ESP8266 @@ -35,45 +35,45 @@ RTC_NOINIT_ATTR uint8_t RTC_cache_data[RTC_CACHE_DATA_SIZE]; **********************************************************************************************/ #if FEATURE_EEPROM_EXTERNAL void EEPROMSaveWritePos() { - const uint8_t eepromAddress = checkEEPROMEnabled(); + const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); if (eepromAddress > 0) { // Save new position - if (EEPROMExternal->readInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET) != RTC_cache.writePos) { - EEPROMExternal->writeInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET, RTC_cache.writePos); + if (ESPEasy::eeprom::EEPROMExternal->readInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET) != RTC_cache.writePos) { + ESPEasy::eeprom::EEPROMExternal->writeInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET, RTC_cache.writePos); } } } void EEPROMSaveMetaDataChecksum() { - const uint8_t eepromAddress = checkEEPROMEnabled(); + const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); if (eepromAddress > 0) { // Save metadata checksum - if (EEPROMExternal->readLong(EEPROM_RTC_CACHE_META_CRC_OFFSET) != RTC_cache.checksumMetadata) { - EEPROMExternal->writeLong(EEPROM_RTC_CACHE_META_CRC_OFFSET, RTC_cache.checksumMetadata); + if (ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_RTC_CACHE_META_CRC_OFFSET) != RTC_cache.checksumMetadata) { + ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_RTC_CACHE_META_CRC_OFFSET, RTC_cache.checksumMetadata); } } } void EEPROMSaveCacheChecksum() { - const uint8_t eepromAddress = checkEEPROMEnabled(); + const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); if (eepromAddress > 0) { // Save cache checksum - if (EEPROMExternal->readLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET) != RTC_cache.checksumData) { - EEPROMExternal->writeLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET, RTC_cache.checksumData); + if (ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET) != RTC_cache.checksumData) { + ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET, RTC_cache.checksumData); } } } void EEPROMSaveRTCcache() { - const uint8_t eepromAddress = checkEEPROMEnabled(); + const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); if (eepromAddress > 0) { size_t count{}; for (uint16_t idx = 0; idx < sizeof(RTC_cache); ++idx) { const uint32_t metaAddr = EEPROM_RTC_CACHE_META_OFFSET + idx; - const uint8_t oldData = EEPROMExternal->read(metaAddr); + const uint8_t oldData = ESPEasy::eeprom::EEPROMExternal->read(metaAddr); const uint8_t newData = reinterpret_cast(&RTC_cache)[idx]; if (oldData != newData) { - EEPROMExternal->write(metaAddr, newData); + ESPEasy::eeprom::EEPROMExternal->write(metaAddr, newData); ++count; } } @@ -293,15 +293,15 @@ bool RTC_cache_handler_struct::write(const uint8_t *data, unsigned int size) { } #if FEATURE_EEPROM_EXTERNAL - const uint8_t eepromAddress = checkEEPROMEnabled(); + const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); if (eepromAddress > 0) { size_t count{}; for (unsigned int idx = 0; idx < size; ++idx) { const uint32_t cacheAddr = EEPROM_RTC_CACHE_START_OFFSET + idx; - const uint8_t oldData = EEPROMExternal->read(cacheAddr); + const uint8_t oldData = ESPEasy::eeprom::EEPROMExternal->read(cacheAddr); const uint8_t newData = data[idx]; if (oldData != newData) { - EEPROMExternal->write(cacheAddr, newData); + ESPEasy::eeprom::EEPROMExternal->write(cacheAddr, newData); ++count; } } diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index ed1345ad78..dcf2a35aaa 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -35,7 +35,7 @@ #include "../WebServer/ESPEasy_WebServer.h" #if FEATURE_EEPROM_EXTERNAL -#include "../Helpers/EEPROMExternal.h" +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL #ifdef USE_RTOS_MULTITASKING @@ -402,7 +402,7 @@ void ESPEasy_setup() #endif // ifndef BUILD_NO_RAM_TRACKER #if FEATURE_EEPROM_EXTERNAL - if ((checkEEPROMEnabled() > 0) && + if ((ESPEasy::eeprom::checkEEPROMEnabled() > 0) && ((lastBootCause <= BOOT_CAUSE_COLD_BOOT) && Settings.RestoreUserVarsFromEEPROMOnColdBoot() || (lastBootCause <= BOOT_CAUSE_SOFT_RESTART) && Settings.RestoreUserVarsFromEEPROMOnWarmBoot()) ) { diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index f3aa98f958..0db0c20faf 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -13,8 +13,8 @@ #include "../../ESPEasy_common.h" #if FEATURE_EEPROM_EXTERNAL +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #include "../Globals/Cache.h" -#include "../Helpers/EEPROMExternal.h" #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/Hardware_I2C.h" #include "../Helpers/StringConverter.h" @@ -182,15 +182,18 @@ bool saveUserVarToRTC(bool initial) { #if FEATURE_EEPROM_EXTERNAL // Check if we have an external EEPROM available on the configured I2C bus & channel, and save all task values there - const uint8_t eepromAddress = checkEEPROMEnabled(); - if (!initial && (eepromAddress > 0) && !isEEPROMExternalWriteProtected()) { // EEPROM Configured and writable? + const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); + if (!initial && (eepromAddress > 0) && !ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { // EEPROM Configured and writable? - if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM #ifndef BUILD_NO_DEBUG uint32_t eepromWritten{}; uint32_t startmicros{}; #endif // ifndef BUILD_NO_DEBUG - const uint32_t checksum = EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); + // Update system parameters if not correct + ESPEasy::eeprom::updateEEPROMExternalParameters(); + + const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); if (UserVar.compute_CRC32() != checksum) { // Only save if data changed #ifndef BUILD_NO_DEBUG @@ -204,9 +207,9 @@ bool saveUserVarToRTC(bool initial) const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) ? taskValues->getUint32(varNr) : std::numeric_limits::max(); // NaN when read as float - const uint32_t addr = getEEPROMAddressForTaskValue(task, varNr); - if (newData != EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs - EEPROMExternal->writeLong(addr, newData); + const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); + if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs + ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; #endif // ifndef BUILD_NO_DEBUG @@ -216,8 +219,7 @@ bool saveUserVarToRTC(bool initial) } // Calculate checksum for all stored values, not equal to the UserVar checksum! const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); - EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, - calcsum); + ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; startmicros = micros() - startmicros; @@ -226,8 +228,10 @@ bool saveUserVarToRTC(bool initial) #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - const EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); - addLog(LOG_LEVEL_DEBUG, strformat(F("EEPROM: UserVar: %u bytes (%.2f ms) written to %s"), eepromWritten, startmicros / 1000.0f, FsP(getEEPROMName(eepromType)))); + const ESPEasy::eeprom::EEPROMExternal_Type_e eepromType = + static_cast(Settings.EEPROMExternalType()); + addLog(LOG_LEVEL_DEBUG, strformat(F("EEPROM: UserVar: %u bytes (%.2f ms) written to %s"), + eepromWritten, startmicros / 1000.0f, FsP(ESPEasy::eeprom::getEEPROMName(eepromType)))); } #endif // ifndef BUILD_NO_DEBUG @@ -273,8 +277,8 @@ bool saveUserVarToRTC(bool initial) #if FEATURE_EEPROM_EXTERNAL uint8_t readDataForUserVars(size_t index) { - if (nullptr != EEPROMExternal) { - return EEPROMExternal->read(EEPROM_USERVAR_START_OFFSET + index); + if (nullptr != ESPEasy::eeprom::EEPROMExternal) { + return ESPEasy::eeprom::EEPROMExternal->read(EEPROM_USERVAR_START_OFFSET + index); } return 0; } @@ -286,21 +290,23 @@ uint8_t readDataForUserVars(size_t index) { bool readUserVarFromRTC() { #if FEATURE_EEPROM_EXTERNAL - const uint8_t eepromAddress = checkEEPROMEnabled(); + const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); if (eepromAddress > 0) { // EEPROM Configured and restoring of Task Values enabled? bool result = false; - if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + // Check system parameters with last stored values + bool eepromParamsOK = ESPEasy::eeprom::validateEEPROMExternalParameters(); // Check checksum and if correct, restore UserVar values - const uint32_t checksum = EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); + const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: readUserVarFromRTC calculated: %u, expected: %u equal: %c"), - calcsum, checksum, calcsum == checksum ? 'Y' : 'n')); + addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: readUserVarFromRTC calculated: %u, expected: %u equal: %c, params: %c"), + calcsum, checksum, calcsum == checksum ? 'Y' : 'n', eepromParamsOK ? 'Y' : 'n')); } #endif // ifndef BUILD_NO_DEBUG - if (calcsum == checksum) { + if (eepromParamsOK && (calcsum == checksum)) { addLog(LOG_LEVEL_INFO, F("INIT : Restoring Task Values from EEPROM.")); result = true; taskIndex_t lastTask = TASKS_MAX; @@ -312,11 +318,11 @@ bool readUserVarFromRTC() lastTask = taskIndex; } // Store in raw form, so we don't apply formula as we don't really know what type is required. - const uint32_t addr = getEEPROMAddressForTaskValue(taskIndex, varNr); + const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(taskIndex, varNr); if (addr != std::numeric_limits::max()) { if (Cache.getTaskVarStoreInEEPROM(taskIndex, varNr)) { // Restore only when enabled TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(taskIndex); - taskValues->setUint32(varNr, EEPROMExternal->readLong(addr)); + taskValues->setUint32(varNr, ESPEasy::eeprom::EEPROMExternal->readLong(addr)); } } else { result = false; diff --git a/src/src/Helpers/Hardware_I2C.cpp b/src/src/Helpers/Hardware_I2C.cpp index ab4cebec6c..b1c87a0962 100644 --- a/src/src/Helpers/Hardware_I2C.cpp +++ b/src/src/Helpers/Hardware_I2C.cpp @@ -8,7 +8,7 @@ #include "../Helpers/StringConverter.h" #if FEATURE_EEPROM_EXTERNAL -# include "../Helpers/EEPROMExternal.h" +# include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL #include @@ -86,59 +86,7 @@ void initI2C() { } #if FEATURE_EEPROM_EXTERNAL - const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); - - if ((nullptr != EEPROMExternal) || (eepromAddress == 0)) { // Cleanup when turning off EEPROM - delete EEPROMExternal; - EEPROMExternal = nullptr; - EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::Undefined; - } - - if ((nullptr == EEPROMExternal) && (eepromAddress > 0)) { - const EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); - - if (0 != selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM - // We have an I2C device at this address, let's assume it's an EEPROM... - uint8_t pageSize = 0; - const uint32_t eepromSize = getEEPROMSize(eepromType, pageSize); - EEPROMExternal = new (std::nothrow) AT24CX(eepromAddress, pageSize, eepromSize); - - if (nullptr != EEPROMExternal) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: %s initialized at address 0x%02x"), - FsP(getEEPROMName(eepromType)), - eepromAddress)); - } - - checkEEPROMExternalWriteProtected(); - - if (isEEPROMExternalWriteProtected()) { - addLog(LOG_LEVEL_INFO, concat(F("EEPROM: Write-protected! Status: "), static_cast(checkEEPROMExternalWriteProtected()))); - } - } else { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, strformat(F("EEPROM: Initialization of %s failed"), - FsP(getEEPROMName(eepromType)))); - } - } - } else { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, strformat(F("EEPROM: No %s found at address 0x%02x"), - FsP(getEEPROMName(eepromType)), - eepromAddress)); - } - } - - # if FEATURE_I2CMULTIPLEXER - I2CMultiplexerOff( - # if FEATURE_I2C_MULTIPLE - Settings.getI2CInterfaceEEPROM() - # else // if FEATURE_I2C_MULTIPLE - 0 - # endif // if FEATURE_I2C_MULTIPLE - ); // Restore the Multiplexer channel - # endif // if FEATURE_I2CMULTIPLEXER - } + ESPEasy::eeprom::initializeEEPROMExternal(); #endif // if FEATURE_EEPROM_EXTERNAL I2CSelectHighClockSpeed(0); // Select first interface by default diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index 8db683d97a..71f04cfaa5 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -22,7 +22,7 @@ #include "../Helpers/StringGenerator_GPIO.h" #if FEATURE_EEPROM_EXTERNAL -#include "../Helpers/EEPROMExternal.h" +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL /********************************************************************************************\ @@ -208,11 +208,11 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us uint32_t slot{}; String value; if (validUIntFromString(valueName, slot)) { - value = toString(readEEPROMSlot(slot)); + value = toString(ESPEasy::eeprom::readEEPROMSlot(slot)); } else if (valueName.equalsIgnoreCase(F("max"))) { - value = getEEPROMMaxSlots(); + value = ESPEasy::eeprom::getEEPROMMaxSlots(); } else if (valueName.equalsIgnoreCase(F("wp"))) { - value = isEEPROMExternalWriteProtected() ? 1 : 0; + value = ESPEasy::eeprom::isEEPROMExternalWriteProtected() ? 1 : 0; } if (!value.isEmpty()) { transformValue( diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index c8c015a3d4..ddaf5cbf3b 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -30,7 +30,7 @@ # include "../Helpers/_Plugin_SensorTypeHelper.h" # include "../Helpers/_Plugin_Helper_serial.h" #if FEATURE_EEPROM_EXTERNAL -# include "../Helpers/EEPROMExternal.h" +# include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/I2C_Plugin_Helper.h" @@ -430,7 +430,7 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task ExtraTaskSettings.enablePluginFilter(varNr, isFormItemChecked(getPluginCustomArgName(F("TDFIL"), varNr))); # endif // if FEATURE_PLUGIN_FILTER #if FEATURE_EEPROM_EXTERNAL - if (checkEEPROMEnabled() > 0) { + if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { ExtraTaskSettings.setTaskVarStoreInEEPROM(varNr, isFormItemChecked(getPluginCustomArgName(F("TDEE"), varNr))); } #endif // if FEATURE_EEPROM_EXTERNAL @@ -1715,7 +1715,7 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde } #if FEATURE_EEPROM_EXTERNAL - if (checkEEPROMEnabled() > 0) { + if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { html_table_header(F("Eeprom"), 30); ++colCount; } @@ -1793,7 +1793,7 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde } #if FEATURE_EEPROM_EXTERNAL - if (checkEEPROMEnabled() > 0) { + if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { html_TD(); addCheckBox( getPluginCustomArgName(F("TDEE"), varNr), diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp index c3ae9eb695..6f0869d07e 100644 --- a/src/src/WebServer/EpromVarPage.cpp +++ b/src/src/WebServer/EpromVarPage.cpp @@ -10,7 +10,7 @@ #include "../Globals/Settings.h" #include "../Globals/ExtraTaskSettings.h" -#include "../Helpers/EEPROMExternal.h" +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/StringConverter.h" @@ -30,13 +30,16 @@ void handle_eepromvars() { TXBuffer.startStream(); sendHeadandTail_stdtemplate(_HEAD); - if (checkEEPROMEnabled() > 0) { + if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { // the table header html_table_class_normal(); html_TR(); - html_table_header(F("External EEPROM"), 300); - html_table_header(getEEPROMName(static_cast(Settings.EEPROMExternalType())), 500); - html_table_header(isEEPROMExternalWriteProtected() ? F("Write-protected!") : F(""), 400); + html_table_header(F("External EEPROM"), + 300); + html_table_header(ESPEasy::eeprom::getEEPROMName(static_cast(Settings.EEPROMExternalType())), + 500); + html_table_header(ESPEasy::eeprom::isEEPROMExternalWriteProtected() ? F("Write-protected!") : F(""), + 400); html_table_header(F("")); if (showTasks) { @@ -67,9 +70,9 @@ void handle_eepromvars() { addHtml(' '); addHtml(getTaskValueName(tsk, var)); html_TD(); - const uint32_t addr = getEEPROMAddressForTaskValue(tsk, var); - const float value = EEPROMExternal->readFloat(addr); - const uint32_t data = EEPROMExternal->readLong(addr); + const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(tsk, var); + const float value = ESPEasy::eeprom::EEPROMExternal->readFloat(addr); + const uint32_t data = ESPEasy::eeprom::EEPROMExternal->readLong(addr); if (isnan(value) || (addr == std::numeric_limits::max()) || !ExtraTaskSettings.getTaskVarStoreInEEPROM(var)) { addHtml('-'); @@ -93,11 +96,11 @@ void handle_eepromvars() { html_table_header(F("")); html_table_header(F("")); - const uint32_t maxSlots = getEEPROMMaxSlots(); + const uint32_t maxSlots = ESPEasy::eeprom::getEEPROMMaxSlots(); uint32_t count{}; for (uint32_t slot = 0; slot < maxSlots; ++slot) { - const float value = readEEPROMSlot(slot); + const float value = ESPEasy::eeprom::readEEPROMSlot(slot); if (slot % 50 == 0) { delay(0); } diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index a53556731d..7270321784 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -26,7 +26,7 @@ #endif // if FEATURE_I2C_MULTIPLE #if FEATURE_EEPROM_EXTERNAL -#include "../Helpers/EEPROMExternal.h" +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #include "../WebServer/DevicesPage.h" // For using ShowI2CMultiplexerUI() and GetI2CMultiplexerFromPage() #endif // if FEATURE_EEPROM_EXTERNAL @@ -127,7 +127,8 @@ void handle_hardware() { #endif // if FEATURE_I2CMULTIPLEXER && !FEATURE_I2C_MULTIPLE && FEATURE_EEPROM_EXTERNAL #if FEATURE_EEPROM_EXTERNAL - Settings.EEPROMExternalType(getFormItemInt(F("eepromtype"), static_cast(EEPROMExternal_Type_e::AT24C256))); + Settings.EEPROMExternalType(getFormItemInt(F("eepromtype"), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C256))); Settings.EEPROMExternalI2CAddress(getFormItemInt(F("i2c_eeprom"), 0)); Settings.RestoreUserVarsFromEEPROMOnColdBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_COLDBOOT)); Settings.RestoreUserVarsFromEEPROMOnWarmBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_WARMBOOT)); @@ -322,52 +323,52 @@ void handle_hardware() { { addFormSubHeader(F("External I2C EEPROM")); const __FlashStringHelper*eepromOptions[] = { - getEEPROMName(EEPROMExternal_Type_e::AT24C256), - getEEPROMName(EEPROMExternal_Type_e::AT24C512), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C256), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C512), #if EEPROM_SUPPORT_AT24C1024 - getEEPROMName(EEPROMExternal_Type_e::AT24C1024), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C1024), #endif // if EEPROM_SUPPORT_AT24C1024 #if EEPROM_SUPPORT_AT24C2048 - getEEPROMName(EEPROMExternal_Type_e::AT24C2048), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C2048), #endif // if EEPROM_SUPPORT_AT24C2048 - getEEPROMName(EEPROMExternal_Type_e::AT24C32), - getEEPROMName(EEPROMExternal_Type_e::AT24C64), - getEEPROMName(EEPROMExternal_Type_e::AT24C128), - getEEPROMName(EEPROMExternal_Type_e::MB85RC256), - getEEPROMName(EEPROMExternal_Type_e::MB85RC512), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C32), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C64), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C128), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC256), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC512), #if EEPROM_SUPPORT_AT24C1024 - getEEPROMName(EEPROMExternal_Type_e::MB85RC1M), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC1M), #endif // if EEPROM_SUPPORT_AT24C1024 #if EEPROM_SUPPORT_AT24C2048 - getEEPROMName(EEPROMExternal_Type_e::MB85RC2M), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC2M), #endif // if EEPROM_SUPPORT_AT24C2048 - getEEPROMName(EEPROMExternal_Type_e::MB85RC32), - getEEPROMName(EEPROMExternal_Type_e::MB85RC64), - getEEPROMName(EEPROMExternal_Type_e::MB85RC128), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC32), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC64), + getEEPROMName(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC128), }; const int eepromTypes[] = { - static_cast(EEPROMExternal_Type_e::AT24C256), - static_cast(EEPROMExternal_Type_e::AT24C512), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C256), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C512), #if EEPROM_SUPPORT_AT24C1024 - static_cast(EEPROMExternal_Type_e::AT24C1024), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C1024), #endif // if EEPROM_SUPPORT_AT24C1024 #if EEPROM_SUPPORT_AT24C2048 - static_cast(EEPROMExternal_Type_e::AT24C2048), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C2048), #endif // if EEPROM_SUPPORT_AT24C2048 - static_cast(EEPROMExternal_Type_e::AT24C32), - static_cast(EEPROMExternal_Type_e::AT24C64), - static_cast(EEPROMExternal_Type_e::AT24C128), - static_cast(EEPROMExternal_Type_e::MB85RC256), - static_cast(EEPROMExternal_Type_e::MB85RC512), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C32), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C64), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C128), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC256), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC512), #if EEPROM_SUPPORT_AT24C1024 - static_cast(EEPROMExternal_Type_e::MB85RC1M), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC1M), #endif // if EEPROM_SUPPORT_AT24C1024 #if EEPROM_SUPPORT_AT24C2048 - static_cast(EEPROMExternal_Type_e::MB85RC2M), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC2M), #endif // if EEPROM_SUPPORT_AT24C2048 - static_cast(EEPROMExternal_Type_e::MB85RC32), - static_cast(EEPROMExternal_Type_e::MB85RC64), - static_cast(EEPROMExternal_Type_e::MB85RC128), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC32), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC64), + static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::MB85RC128), }; constexpr uint8_t eepromSizeCount = NR_ELEMENTS(eepromTypes); FormSelectorOptions eepromSizeSelector(eepromSizeCount, eepromOptions, eepromTypes); @@ -400,15 +401,15 @@ void handle_hardware() { get8BitFromUL(eepromMux, EEPROM_MUX_FLAGS_PORT)); // Re-used from DevicesPage #endif // if FEATURE_I2CMULTIPLEXER - const bool eepromChecked = checkEEPROMEnabled() > 0; + const bool eepromChecked = ESPEasy::eeprom::checkEEPROMEnabled() > 0; addRowLabel(F("EEPROM Enabled")); addEnabled(eepromChecked); - if (eepromChecked && isEEPROMExternalWriteProtected()) { + if (eepromChecked && ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { addHtml(F(" Write-protected!")); } - if (eepromChecked && !isEEPROMExternalWriteProtected()) { + if (eepromChecked && !ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { addRowLabel(F("'WriteEE' slots available")); - addHtmlInt(getEEPROMMaxSlots()); + addHtmlInt(ESPEasy::eeprom::getEEPROMMaxSlots()); } addFormCheckBox(LabelType::EEPROM_RESTORE_ON_COLDBOOT, Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); addFormCheckBox(LabelType::EEPROM_RESTORE_ON_WARMBOOT, Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); From 495f9024a036c4ecba8bd9736677f6d260d6bc5b Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 28 Aug 2025 15:00:26 +0200 Subject: [PATCH 21/44] [Storage] Store and check real UserVar checksum, temp. enable some EEPROM logging at INFO level --- src/ESPEasy/eeprom/Helpers/EEPROMExternal.h | 3 ++- src/src/Helpers/ESPEasyRTC.cpp | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index 071bdec32f..c3de989b09 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -39,9 +39,10 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; // Write the UserVar-checksum from this offset, right after the UserVar values # define EEPROM_USERVAR_CHECKSUM_OFFSET (EEPROM_USERVAR_START_OFFSET + (TASKS_MAX * VARS_PER_TASK * sizeof(uint32_t))) +# define EEPROM_USERVAR_REAL_CHECKSUM (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) // Even when not enabled, we still reserve the space -# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) +# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_REAL_CHECKSUM + sizeof(uint32_t)) # define EEPROM_RTC_CACHE_WRITEPOS_OFFSET (EEPROM_RTC_CACHE_START_OFFSET + RTC_CACHE_DATA_SIZE) # define EEPROM_RTC_CACHE_META_OFFSET (EEPROM_RTC_CACHE_WRITEPOS_OFFSET + sizeof(uint16_t)) # define EEPROM_RTC_CACHE_META_CRC_OFFSET (EEPROM_RTC_CACHE_META_OFFSET + sizeof(RTC_cache_struct)) diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 0db0c20faf..91c4070955 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -193,7 +193,7 @@ bool saveUserVarToRTC(bool initial) // Update system parameters if not correct ESPEasy::eeprom::updateEEPROMExternalParameters(); - const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); + const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_REAL_CHECKSUM); if (UserVar.compute_CRC32() != checksum) { // Only save if data changed #ifndef BUILD_NO_DEBUG @@ -218,19 +218,27 @@ bool saveUserVarToRTC(bool initial) } } // Calculate checksum for all stored values, not equal to the UserVar checksum! - const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); - ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); + ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_REAL_CHECKSUM, checksum); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; + #endif // ifndef BUILD_NO_DEBUG + const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); + if (calcsum != ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET)) { + ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); + #ifndef BUILD_NO_DEBUG + eepromWritten += sizeof_uint32_t; + #endif // ifndef BUILD_NO_DEBUG + } + #ifndef BUILD_NO_DEBUG startmicros = micros() - startmicros; #endif // ifndef BUILD_NO_DEBUG } #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { // FIXME LOG_LEVEL_DEBUG const ESPEasy::eeprom::EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); - addLog(LOG_LEVEL_DEBUG, strformat(F("EEPROM: UserVar: %u bytes (%.2f ms) written to %s"), + addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: UserVar: %u bytes (%.2f ms) written to %s"), eepromWritten, startmicros / 1000.0f, FsP(ESPEasy::eeprom::getEEPROMName(eepromType)))); } #endif // ifndef BUILD_NO_DEBUG @@ -301,7 +309,7 @@ bool readUserVarFromRTC() const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { // FIXME LOG_LEVEL_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: readUserVarFromRTC calculated: %u, expected: %u equal: %c, params: %c"), calcsum, checksum, calcsum == checksum ? 'Y' : 'n', eepromParamsOK ? 'Y' : 'n')); } From a89685c8dd1c43a3807a381022a3f7ec78f6cd6f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 28 Aug 2025 20:10:26 +0200 Subject: [PATCH 22/44] [Storage] Fix compilation for builds without P146 or C016 included --- src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp | 15 +++++++++++++-- src/ESPEasy/eeprom/Helpers/EEPROMExternal.h | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp index 1c4bbb98ac..6f429bcdf4 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp @@ -86,13 +86,18 @@ bool validateEEPROMExternalParameters() { const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); - const uint32_t eepromRtcCacheParam = EEPROMExternal->readLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS); const uint32_t eepromPinstateParam = EEPROMExternal->readLong(EEPROM_PARAMS_PINSTATE_ADDRESS); + # if FEATURE_RTC_CACHE_STORAGE + const uint32_t eepromRtcCacheParam = EEPROMExternal->readLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS); + # endif // if FEATURE_RTC_CACHE_STORAGE + if ((EEPROM_PARAMS_CURRENT_VERSION == eepromVersionParam) && (TASKS_MAX == eepromTasksMaxParam) && (VARS_PER_TASK == eepromVarsPerTaskParam) && + # if FEATURE_RTC_CACHE_STORAGE (EEPROM_RTC_CACHE_START_OFFSET == eepromRtcCacheParam) && + # endif // if FEATURE_RTC_CACHE_STORAGE (EEPROM_GPIO_PINSTATE_START_OFFSET == eepromPinstateParam) && (EEPROM_GPIO_PINSTATE_END_OFFSET <= EEPROM_CUSTOM_START_OFFSET)) { return true; @@ -113,9 +118,12 @@ void updateEEPROMExternalParameters() { const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); - const uint32_t eepromRtcCacheParam = EEPROMExternal->readInt(EEPROM_PARAMS_RTC_CACHE_ADDRESS); const uint32_t eepromPinstateParam = EEPROMExternal->readInt(EEPROM_PARAMS_PINSTATE_ADDRESS); + # if FEATURE_RTC_CACHE_STORAGE + const uint32_t eepromRtcCacheParam = EEPROMExternal->readInt(EEPROM_PARAMS_RTC_CACHE_ADDRESS); + # endif // if FEATURE_RTC_CACHE_STORAGE + if (EEPROM_PARAMS_CURRENT_VERSION != eepromVersionParam) { EEPROMExternal->writeInt(EEPROM_PARAMS_VERSION_ADDRESS, EEPROM_PARAMS_CURRENT_VERSION); } @@ -128,9 +136,12 @@ void updateEEPROMExternalParameters() { EEPROMExternal->writeInt(EEPROM_PARAMS_VARS_PER_TASK, VARS_PER_TASK); } + # if FEATURE_RTC_CACHE_STORAGE + if (EEPROM_RTC_CACHE_START_OFFSET != eepromRtcCacheParam) { EEPROMExternal->writeLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS, EEPROM_RTC_CACHE_START_OFFSET); } + # endif // if FEATURE_RTC_CACHE_STORAGE if (EEPROM_GPIO_PINSTATE_START_OFFSET != eepromPinstateParam) { EEPROMExternal->writeLong(EEPROM_PARAMS_PINSTATE_ADDRESS, EEPROM_GPIO_PINSTATE_START_OFFSET); diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index c3de989b09..62a7f82b55 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -41,15 +41,22 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; # define EEPROM_USERVAR_CHECKSUM_OFFSET (EEPROM_USERVAR_START_OFFSET + (TASKS_MAX * VARS_PER_TASK * sizeof(uint32_t))) # define EEPROM_USERVAR_REAL_CHECKSUM (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) +# if FEATURE_RTC_CACHE_STORAGE + // Even when not enabled, we still reserve the space -# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_REAL_CHECKSUM + sizeof(uint32_t)) -# define EEPROM_RTC_CACHE_WRITEPOS_OFFSET (EEPROM_RTC_CACHE_START_OFFSET + RTC_CACHE_DATA_SIZE) -# define EEPROM_RTC_CACHE_META_OFFSET (EEPROM_RTC_CACHE_WRITEPOS_OFFSET + sizeof(uint16_t)) -# define EEPROM_RTC_CACHE_META_CRC_OFFSET (EEPROM_RTC_CACHE_META_OFFSET + sizeof(RTC_cache_struct)) -# define EEPROM_RTC_CACHE_CHECKSUM_OFFSET (EEPROM_RTC_CACHE_META_CRC_OFFSET + sizeof(uint32_t)) +# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_REAL_CHECKSUM + sizeof(uint32_t)) +# define EEPROM_RTC_CACHE_WRITEPOS_OFFSET (EEPROM_RTC_CACHE_START_OFFSET + RTC_CACHE_DATA_SIZE) +# define EEPROM_RTC_CACHE_META_OFFSET (EEPROM_RTC_CACHE_WRITEPOS_OFFSET + sizeof(uint16_t)) +# define EEPROM_RTC_CACHE_META_CRC_OFFSET (EEPROM_RTC_CACHE_META_OFFSET + sizeof(RTC_cache_struct)) +# define EEPROM_RTC_CACHE_CHECKSUM_OFFSET (EEPROM_RTC_CACHE_META_CRC_OFFSET + sizeof(uint32_t)) +# endif // if FEATURE_RTC_CACHE_STORAGE // Offset for storing GPIO pinstates, directly following the RTC Cache storage and checksum -# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_RTC_CACHE_CHECKSUM_OFFSET + sizeof(uint32_t)) +# if FEATURE_RTC_CACHE_STORAGE +# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_RTC_CACHE_CHECKSUM_OFFSET + sizeof(uint32_t)) +# else // if FEATURE_RTC_CACHE_STORAGE +# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_USERVAR_REAL_CHECKSUM + sizeof(uint32_t)) +# endif // if FEATURE_RTC_CACHE_STORAGE # define EEPROM_PINSTATE_ELEMENT_SIZE (sizeof(uint32_t) + sizeof(portStatusStruct)) # define EEPROM_GPIO_PINSTATE_END_OFFSET (EEPROM_GPIO_PINSTATE_START_OFFSET + (EEPROM_PINSTATE_ELEMENT_COUNT * EEPROM_PINSTATE_ELEMENT_SIZE)) From 276d574cd411f198f2e94fca2eb48ea44eb7a1b9 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 29 Aug 2025 21:50:21 +0200 Subject: [PATCH 23/44] [Storage] Revert choice of saving all Task Values: Only selected Task Values will be saved in EEPROM --- src/src/DataStructs/ExtraTaskSettingsStruct.cpp | 8 ++++---- src/src/DataStructs/ExtraTaskSettingsStruct.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp index a5a9366ea2..1d8f5ede8c 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp @@ -291,15 +291,15 @@ void ExtraTaskSettingsStruct::setTaskVarCustomVType(taskVarIndex_t taskVarIndex, #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE #if FEATURE_EEPROM_EXTERNAL -bool ExtraTaskSettingsStruct::getTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex) const { // Inverted +bool ExtraTaskSettingsStruct::getTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex) const { if (!validTaskVarIndex(taskVarIndex)) { return false; } - return !bitRead(VariousBits[taskVarIndex], 24); + return bitRead(VariousBits[taskVarIndex], 24); } void ExtraTaskSettingsStruct::setTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex, - bool store) { // Inverted + bool store) { if (validTaskVarIndex(taskVarIndex)) { - bitWrite(VariousBits[taskVarIndex], 24, !store); + bitWrite(VariousBits[taskVarIndex], 24, store); } } #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.h b/src/src/DataStructs/ExtraTaskSettingsStruct.h index 7b9609e4d9..5a1d04ba5e 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.h +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.h @@ -112,7 +112,7 @@ struct ExtraTaskSettingsStruct * - 0..7 : PluginStats config (8 bits) * - 8..15 : UnitOfMeasure index (8 bits) * - 16..23 : CustomValueType index (8 bits) - * - 24 : Store value in EEPROM (1 bit, inverted) + * - 24 : Store value in EEPROM (1 bit) */ }; From bb3463818622ae0b37ce0090794a6a61d2707401 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 30 Aug 2025 14:19:37 +0200 Subject: [PATCH 24/44] [Storage] Remove unneeded UserVar checksum write --- src/ESPEasy/eeprom/Helpers/EEPROMExternal.h | 5 +- src/src/Helpers/ESPEasyRTC.cpp | 58 +++++++++------------ 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index 62a7f82b55..f79357c5de 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -39,12 +39,11 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; // Write the UserVar-checksum from this offset, right after the UserVar values # define EEPROM_USERVAR_CHECKSUM_OFFSET (EEPROM_USERVAR_START_OFFSET + (TASKS_MAX * VARS_PER_TASK * sizeof(uint32_t))) -# define EEPROM_USERVAR_REAL_CHECKSUM (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) # if FEATURE_RTC_CACHE_STORAGE // Even when not enabled, we still reserve the space -# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_REAL_CHECKSUM + sizeof(uint32_t)) +# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) # define EEPROM_RTC_CACHE_WRITEPOS_OFFSET (EEPROM_RTC_CACHE_START_OFFSET + RTC_CACHE_DATA_SIZE) # define EEPROM_RTC_CACHE_META_OFFSET (EEPROM_RTC_CACHE_WRITEPOS_OFFSET + sizeof(uint16_t)) # define EEPROM_RTC_CACHE_META_CRC_OFFSET (EEPROM_RTC_CACHE_META_OFFSET + sizeof(RTC_cache_struct)) @@ -55,7 +54,7 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; # if FEATURE_RTC_CACHE_STORAGE # define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_RTC_CACHE_CHECKSUM_OFFSET + sizeof(uint32_t)) # else // if FEATURE_RTC_CACHE_STORAGE -# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_USERVAR_REAL_CHECKSUM + sizeof(uint32_t)) +# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) # endif // if FEATURE_RTC_CACHE_STORAGE # define EEPROM_PINSTATE_ELEMENT_SIZE (sizeof(uint32_t) + sizeof(portStatusStruct)) # define EEPROM_GPIO_PINSTATE_END_OFFSET (EEPROM_GPIO_PINSTATE_START_OFFSET + (EEPROM_PINSTATE_ELEMENT_COUNT * EEPROM_PINSTATE_ELEMENT_SIZE)) diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 91c4070955..135ae1382d 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -193,48 +193,38 @@ bool saveUserVarToRTC(bool initial) // Update system parameters if not correct ESPEasy::eeprom::updateEEPROMExternalParameters(); - const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_REAL_CHECKSUM); - - if (UserVar.compute_CRC32() != checksum) { // Only save if data changed - #ifndef BUILD_NO_DEBUG - startmicros = micros(); - #endif // ifndef BUILD_NO_DEBUG - for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { - const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); - if (taskValues != nullptr) { - LoadTaskSettings(task); - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) - ? taskValues->getUint32(varNr) - : std::numeric_limits::max(); // NaN when read as float - const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); - if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs - ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); - #ifndef BUILD_NO_DEBUG - eepromWritten += sizeof_uint32_t; - #endif // ifndef BUILD_NO_DEBUG - } + #ifndef BUILD_NO_DEBUG + startmicros = micros(); + #endif // ifndef BUILD_NO_DEBUG + for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { + const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); + if (taskValues != nullptr) { + LoadTaskSettings(task); + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) + ? taskValues->getUint32(varNr) + : std::numeric_limits::max(); // NaN when read as float + const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); + if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs + ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); + #ifndef BUILD_NO_DEBUG + eepromWritten += sizeof_uint32_t; + #endif // ifndef BUILD_NO_DEBUG } } } - // Calculate checksum for all stored values, not equal to the UserVar checksum! - ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_REAL_CHECKSUM, checksum); + } + // Calculate checksum for all stored values, not equal to the UserVar checksum! + const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); + if (calcsum != ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET)) { + ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); #ifndef BUILD_NO_DEBUG eepromWritten += sizeof_uint32_t; #endif // ifndef BUILD_NO_DEBUG - const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); - if (calcsum != ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET)) { - ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); - #ifndef BUILD_NO_DEBUG - eepromWritten += sizeof_uint32_t; - #endif // ifndef BUILD_NO_DEBUG - } - #ifndef BUILD_NO_DEBUG - startmicros = micros() - startmicros; - #endif // ifndef BUILD_NO_DEBUG } - #ifndef BUILD_NO_DEBUG + startmicros = micros() - startmicros; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { // FIXME LOG_LEVEL_DEBUG const ESPEasy::eeprom::EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); From a8f25c8fe87b5358319bfb37e006eb128b3a740d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 30 Aug 2025 21:07:00 +0200 Subject: [PATCH 25/44] [Storage] Save only Enabled tasks values in EEPROM --- src/src/Helpers/ESPEasyRTC.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 135ae1382d..2d9ee04fc4 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -191,25 +191,28 @@ bool saveUserVarToRTC(bool initial) uint32_t startmicros{}; #endif // ifndef BUILD_NO_DEBUG // Update system parameters if not correct + const bool paramsOk = ESPEasy::eeprom::validateEEPROMExternalParameters(); ESPEasy::eeprom::updateEEPROMExternalParameters(); #ifndef BUILD_NO_DEBUG startmicros = micros(); #endif // ifndef BUILD_NO_DEBUG for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { - const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); - if (taskValues != nullptr) { - LoadTaskSettings(task); - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) - ? taskValues->getUint32(varNr) - : std::numeric_limits::max(); // NaN when read as float - const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); - if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs - ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); - #ifndef BUILD_NO_DEBUG - eepromWritten += sizeof_uint32_t; - #endif // ifndef BUILD_NO_DEBUG + if (Settings.TaskDeviceEnabled[task] || !paramsOk) { // Only check enabled tasks or when re-writing the params + const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); + if (taskValues != nullptr) { + LoadTaskSettings(task); + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) + ? taskValues->getUint32(varNr) + : std::numeric_limits::max(); // NaN when read as float + const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); + if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs + ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); + #ifndef BUILD_NO_DEBUG + eepromWritten += sizeof_uint32_t; + #endif // ifndef BUILD_NO_DEBUG + } } } } From d969c223268820c26982774e838932b3284d5d01 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 30 Aug 2025 22:39:47 +0200 Subject: [PATCH 26/44] [Storage] Add some missing round brackets --- src/src/ESPEasyCore/ESPEasy_setup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index dcf2a35aaa..a647531a92 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -403,8 +403,8 @@ void ESPEasy_setup() #if FEATURE_EEPROM_EXTERNAL if ((ESPEasy::eeprom::checkEEPROMEnabled() > 0) && - ((lastBootCause <= BOOT_CAUSE_COLD_BOOT) && Settings.RestoreUserVarsFromEEPROMOnColdBoot() || - (lastBootCause <= BOOT_CAUSE_SOFT_RESTART) && Settings.RestoreUserVarsFromEEPROMOnWarmBoot()) + (((lastBootCause <= BOOT_CAUSE_COLD_BOOT) && Settings.RestoreUserVarsFromEEPROMOnColdBoot()) || + ((lastBootCause <= BOOT_CAUSE_SOFT_RESTART) && Settings.RestoreUserVarsFromEEPROMOnWarmBoot())) ) { readUserVarFromRTC(); // Once more to fetch UserVar data from now available EEPROM/FRAM } From f40334e6a77a7fda5350a2de87dcc7162598cdcf Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 31 Aug 2025 22:27:07 +0200 Subject: [PATCH 27/44] [Storage] Handle EEPROM saving in background, with optional save interval --- src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp | 156 ++++++++++++++++++ src/ESPEasy/eeprom/Helpers/EEPROMExternal.h | 44 +++++ src/src/CustomBuild/define_plugin_sets.h | 23 +++ src/src/DataStructs/DeviceStruct.h | 2 + src/src/DataStructs/SettingsStruct.h | 5 +- src/src/DataStructs_templ/SettingsStruct.cpp | 8 + src/src/Helpers/ESPEasyRTC.cpp | 91 +++++----- src/src/Helpers/ESPEasy_checks.cpp | 2 +- src/src/Helpers/PeriodicalActions.cpp | 7 + src/src/Helpers/StringProvider.cpp | 2 + src/src/Helpers/StringProvider.h | 1 + src/src/WebServer/HardwarePage.cpp | 2 + 12 files changed, 302 insertions(+), 41 deletions(-) diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp index 6f429bcdf4..224bb3bd44 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp @@ -441,6 +441,162 @@ float readEEPROMSlot(uint32_t slot) { } return 0.0f; } + +# if FEATURE_EEPROM_BACKGROUND + +// A map of tasks to be executed +std::map EEPROMTaskMap; +uint16_t EEPROMSaveDelaySeconds{}; + +/** + * Convert TaskType to text + */ +const __FlashStringHelper* TaskDataTypeToString(EEPROMExternalTaskType_e type) { + switch (type) { + case EEPROMExternalTaskType_e::None: return F(""); + case EEPROMExternalTaskType_e::UserVars: return F("UserVars"); + case EEPROMExternalTaskType_e::ValueSlots: return F("ValueSlots"); + case EEPROMExternalTaskType_e::C016Caches: return F("C016 Cache elements"); + case EEPROMExternalTaskType_e::PinStates: return F("Pinstates"); + } + return F(""); +} + +/** + * Perform the actual task, bij calling the provided function + */ +void EEPROM_execute_task(void *parameter) { + EEPROMExternalTaskData*_task_data = static_cast(parameter); + + if ((_task_data->status == EEPROMExternalTaskState_e::Starting) && (nullptr != _task_data->function)) { + _task_data->status = EEPROMExternalTaskState_e::Processing; + + selectEEPROMI2CBusAndMultiplexer(); + + _task_data->timer.setNow(); + + // Blocking operation + _task_data->data = _task_data->function(); + + // Results are in + _task_data->duration = _task_data->timer.millisPassedSince(); + + # if FEATURE_I2CMULTIPLEXER + I2CMultiplexerOff( + # if FEATURE_I2C_MULTIPLE + Settings.getI2CInterfaceEEPROM() + # else // if FEATURE_I2C_MULTIPLE + 0 + # endif // if FEATURE_I2C_MULTIPLE + ); // Restore the Multiplexer channel + # endif // if FEATURE_I2CMULTIPLEXER + + _task_data->status = EEPROMExternalTaskState_e::Ready; + } + + _task_data->function = nullptr; // Don't run again accidently + # if FEATURE_EEPROM_RTOS_TASK + _task_data->taskHandle = NULL; + vTaskDelete(_task_data->taskHandle); + # endif // if FEATURE_EEPROM_RTOS_TASK +} + +/** + * Insert a task into the taskmap, 1 per task type, ignore new task when same type is already scheduled to run, or currently running + */ +bool EEPROMAddTask(EEPROMExternalTaskType_e type, + EEPROMExternalTaskData taskData) { + auto task = EEPROMTaskMap.find(type); + + // Is a task is already in progress, skip until the next AddTask + if ((task == EEPROMTaskMap.end()) || (EEPROMExternalTaskState_e::Available == task->second.status)) { + EEPROMTaskMap[type] = taskData; // Insert task + task = EEPROMTaskMap.find(type); + + if (task != EEPROMTaskMap.end()) { // Upserted successfully + task->second.status = EEPROMExternalTaskState_e::Starting; + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, + strformat(F("EEPROM: AddTask upserted for %s, status %u (tasks: %u)" + # if FEATURE_EEPROM_RTOS_TASK + " (RTOS)" + # endif // if FEATURE_EEPROM_RTOS_TASK + ), + FsP(TaskDataTypeToString(task->second.type)), static_cast(task->second.status), EEPROMTaskMap.size())); + } + # endif // ifndef BUILD_NO_DEBUG + } else { + addLog(LOG_LEVEL_ERROR, F("EEPROM: Task not inserted")); + } + + # if FEATURE_EEPROM_RTOS_TASK + EEPROMExternalLoop(); // Kick off if it's a RTOS task + # endif // if FEATURE_EEPROM_RTOS_TASK + return true; + } + return false; +} + +/** + * Check the task map for tasks to run and start that, also report about finished tasks, and clean those for re-use + */ +bool EEPROMExternalLoop() { + if (0 != EEPROMSaveDelaySeconds) { + EEPROMSaveDelaySeconds--; + return false; + } + EEPROMSaveDelaySeconds = Settings.EEPROMSaveDelaySeconds(); + + for (auto task = EEPROMTaskMap.begin(); task != EEPROMTaskMap.end(); ++task) { + if ((EEPROMExternalTaskState_e::Ready == task->second.status) || + (EEPROMExternalTaskState_e::Error == task->second.status)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: Write %s %s %d in %u msec." + # if FEATURE_EEPROM_RTOS_TASK + " (RTOS)" + # endif // if FEATURE_EEPROM_RTOS_TASK + ), + FsP(TaskDataTypeToString(task->second.type)), + FsP(EEPROMExternalTaskState_e::Ready == task->second.status ? F("success") : F("failed")), + task->second.data, + task->second.duration)); + } + task->second.status = EEPROMExternalTaskState_e::Available; + } + + if (EEPROMExternalTaskState_e::Starting == task->second.status) { + if (nullptr != task->second.function) { + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat(F("EEPROM: Loop starting task: %s"), FsP(TaskDataTypeToString(task->second.type)))); + } + # endif // ifndef BUILD_NO_DEBUG + # if FEATURE_EEPROM_RTOS_TASK + xTaskCreatePinnedToCore( + EEPROM_execute_task, // Function that should be called + "EEPROM.write()", // Name of the task (for debugging) + 4000, // Stack size (bytes) + &task->second, // Parameter to pass + 1, // Task priority + &task->second.taskHandle, // Task handle + xPortGetCoreID() // Core you want to run the task on (0 or 1) + ); + # else // if FEATURE_EEPROM_RTOS_TASK + EEPROM_execute_task(&task->second); + # endif // if FEATURE_EEPROM_RTOS_TASK + } else { + task->second.status = EEPROMExternalTaskState_e::Available; // Recycle + } + } + } + return false; +} + +# endif // if FEATURE_EEPROM_BACKGROUND } // namespace eeprom } // namespace ESPEasy #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index f79357c5de..101f66f83a 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -7,6 +7,8 @@ # include "../../../src/DataStructs/PortStatusStruct.h" # include "../../../src/DataStructs/RTCCacheStruct.h" # include "../../../src/DataStructs/RTCStruct.h" +# include "../../../src/Helpers/LongTermTimer.h" +# include # include @@ -122,6 +124,48 @@ uint32_t getEEPROMMaxSlots(); bool writeEEPROMSlot(uint32_t slot, float data); float readEEPROMSlot(uint32_t slot); + +# if FEATURE_EEPROM_BACKGROUND +enum class EEPROMExternalTaskState_e : uint8_t { + Available = 0, + Starting = 1, + Processing = 2, + Ready = 3, + Error = 4, +}; + +enum class EEPROMExternalTaskType_e : uint8_t { + None = 0, + UserVars = 1, + ValueSlots = 2, + C016Caches = 3, + PinStates = 4, +}; + +struct EEPROMExternalTaskData { + EEPROMExternalTaskState_e status = EEPROMExternalTaskState_e::Available; + EEPROMExternalTaskType_e type = EEPROMExternalTaskType_e::None; + uint32_t (*function)() = nullptr; + LongTermTimer timer; + uint32_t duration{}; + int32_t data{}; + + # if FEATURE_EEPROM_RTOS_TASK + + // This is C-code, so not set to nullptr, but to NULL + TaskHandle_t taskHandle = NULL; + # endif // if FEATURE_EEPROM_RTOS_TASK +}; + +const __FlashStringHelper* TaskDataTypeToString(EEPROMExternalTaskType_e type); +bool EEPROMAddTask(EEPROMExternalTaskType_e type, + EEPROMExternalTaskData taskData); +bool EEPROMExternalLoop(); + +extern std::map EEPROMTaskMap; +extern uint16_t EEPROMSaveDelaySeconds; + +# endif // if FEATURE_EEPROM_BACKGROUND } // namespace eeprom } // namespace ESPEasy #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 7bb3660f74..769e8f3b3e 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3781,6 +3781,29 @@ To create/register a plugin, you have to : #endif #endif #endif // ifndef FEATURE_EEPROM_EXTERNAL +#ifndef FEATURE_EEPROM_BACKGROUND + #ifdef ESP32 + #define FEATURE_EEPROM_BACKGROUND 1 + #ifndef FEATURE_EEPROM_RTOS_TASK + #define FEATURE_EEPROM_RTOS_TASK 0 // I2C is not thread-safe + #endif + #endif // ifdef ESP32 + #ifdef ESP8266 + #ifdef LIMIT_BUILD_SIZE + #define FEATURE_EEPROM_BACKGROUND 0 // Disabled for limited builds on ESP8266 + #else + #define FEATURE_EEPROM_BACKGROUND 1 // Enabled by default on ESP8266 + #endif + #endif // ifdef ESP8266 +#endif // ifndef FEATURE_EEPROM_BACKGROUND +#if defined(ESP8266) && defined(FEATURE_EEPROM_RTOS_TASK) && FEATURE_EEPROM_RTOS_TASK + #undef FEATURE_EEPROM_RTOS_TASK + #define FEATURE_EEPROM_RTOS_TASK 0 // Not supported on ESP8266 +#endif // if defined(ESP8266) && defined(FEATURE_EEPROM_RTOS_TASK) && FEATURE_EEPROM_RTOS_TASK +#ifndef FEATURE_EEPROM_RTOS_TASK + #define FEATURE_EEPROM_RTOS_TASK 0 +#endif // ifndef FEATURE_EEPROM_RTOS_TASK + //-------------------HTTPResponseParser Section---------------- #ifndef FEATURE_THINGSPEAK_EVENT diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 98ca671c23..79ce6428e8 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -58,6 +58,8 @@ #define EEPROM_MUX_FLAGS_PORT 0 // bit-offset within multiplexerflags for the portnr/bits (8 bits) #define EEPROM_MUX_FLAGS_MULTI 8 // bit-offset within multiplexerflags for bits or port (1 bit) +#define EEPROM_SAVEOPTIONS_DELAY 0 // bit-offset within EEPROMSaveOptions for the Save Delay (seconds) + #endif // if FEATURE_EEPROM_EXTERNAL /*********************************************************************************************\ diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index a059a72cf5..34ef360440 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -362,6 +362,8 @@ class SettingsStruct_tmpl void EEPROMExternalI2CMultiplexerFlags(uint16_t muxFlags); uint8_t EEPROMExternalType() const; void EEPROMExternalType(uint8_t type); + uint8_t EEPROMSaveDelaySeconds() const; + void EEPROMSaveDelaySeconds(uint8_t seconds); #endif #if FEATURE_I2CMULTIPLEXER @@ -468,7 +470,8 @@ class SettingsStruct_tmpl int8_t I2C3_Multiplexer_Type = I2C_MULTIPLEXER_NONE; int8_t I2C3_Multiplexer_Addr = -1; int8_t I2C3_Multiplexer_ResetPin = -1; - unsigned int OLD_TaskDeviceID[N_TASKS - 7] = {0}; //UNUSED: this can be reused + uint32_t EEPROMSaveOptions = 0; + unsigned int OLD_TaskDeviceID[N_TASKS - 8] = {0}; //UNUSED: this can be reused // FIXME TD-er: When used on ESP8266, this conversion union may not work // It might work as it is 32-bit in size. diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index 6322f7a21b..bf995d60d3 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -1166,6 +1166,14 @@ template void SettingsStruct_tmpl::EEPROMExternalType(uint8_t type) { set4BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE, type); } +template +uint8_t SettingsStruct_tmpl::EEPROMSaveDelaySeconds() const { + return get8BitFromUL(EEPROMSaveOptions, EEPROM_SAVEOPTIONS_DELAY); +} +template +void SettingsStruct_tmpl::EEPROMSaveDelaySeconds(uint8_t seconds) { + set8BitToUL(EEPROMSaveOptions, EEPROM_SAVEOPTIONS_DELAY, seconds); +} #endif // if FEATURE_EEPROM_EXTERNAL #if FEATURE_I2CMULTIPLEXER diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 2d9ee04fc4..1b58c00b97 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -171,6 +171,47 @@ bool readFromRTC() return RTC.ID1 == 0xAA && RTC.ID2 == 0x55; } +#if FEATURE_EEPROM_EXTERNAL +uint32_t saveUserVarToEEPROM() { + uint32_t eepromWritten{}; + // Update system parameters if not correct + const bool paramsOk = ESPEasy::eeprom::validateEEPROMExternalParameters(); + + if (!paramsOk) { + ESPEasy::eeprom::updateEEPROMExternalParameters(); + } + + for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { + if (Settings.TaskDeviceEnabled[task] || !paramsOk) { // Only check enabled tasks or when re-writing the params + const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); + if (taskValues != nullptr) { + LoadTaskSettings(task); + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) + ? taskValues->getUint32(varNr) + : std::numeric_limits::max(); // NaN when read as float + const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); + if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs + ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); + eepromWritten += sizeof_uint32_t; + } + } + } + } + } + + if (eepromWritten) { // Did we change anything? Only then do the slow operation of CRC calculation by reading all bytes + // Calculate checksum for all stored values, not equal to the UserVar checksum! + const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); + if (calcsum != ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET)) { + ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); + eepromWritten += sizeof_uint32_t; + } + } + return eepromWritten; +} +#endif // if FEATURE_EEPROM_EXTERNAL + /********************************************************************************************\ Save values to RTC memory \*********************************************************************************************/ @@ -186,45 +227,17 @@ bool saveUserVarToRTC(bool initial) if (!initial && (eepromAddress > 0) && !ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { // EEPROM Configured and writable? if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + #if FEATURE_EEPROM_BACKGROUND + ESPEasy::eeprom::EEPROMExternalTaskData taskdata; + taskdata.type = ESPEasy::eeprom::EEPROMExternalTaskType_e::UserVars; + taskdata.function = saveUserVarToEEPROM; + ESPEasy::eeprom::EEPROMAddTask(ESPEasy::eeprom::EEPROMExternalTaskType_e::UserVars, taskdata); + #else // if FEATURE_EEPROM_BACKGROUND #ifndef BUILD_NO_DEBUG - uint32_t eepromWritten{}; - uint32_t startmicros{}; - #endif // ifndef BUILD_NO_DEBUG - // Update system parameters if not correct - const bool paramsOk = ESPEasy::eeprom::validateEEPROMExternalParameters(); - ESPEasy::eeprom::updateEEPROMExternalParameters(); - - #ifndef BUILD_NO_DEBUG - startmicros = micros(); - #endif // ifndef BUILD_NO_DEBUG - for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { - if (Settings.TaskDeviceEnabled[task] || !paramsOk) { // Only check enabled tasks or when re-writing the params - const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); - if (taskValues != nullptr) { - LoadTaskSettings(task); - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) - ? taskValues->getUint32(varNr) - : std::numeric_limits::max(); // NaN when read as float - const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); - if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs - ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); - #ifndef BUILD_NO_DEBUG - eepromWritten += sizeof_uint32_t; - #endif // ifndef BUILD_NO_DEBUG - } - } - } - } - } - // Calculate checksum for all stored values, not equal to the UserVar checksum! - const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); - if (calcsum != ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET)) { - ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); - #ifndef BUILD_NO_DEBUG - eepromWritten += sizeof_uint32_t; - #endif // ifndef BUILD_NO_DEBUG - } + uint32_t startmicros = micros(); + const uint32_t eepromWritten = + #endif + saveUserVarToEEPROM(); #ifndef BUILD_NO_DEBUG startmicros = micros() - startmicros; @@ -235,7 +248,7 @@ bool saveUserVarToRTC(bool initial) eepromWritten, startmicros / 1000.0f, FsP(ESPEasy::eeprom::getEEPROMName(eepromType)))); } #endif // ifndef BUILD_NO_DEBUG - + #endif // if FEATURE_EEPROM_BACKGROUND } #if FEATURE_I2CMULTIPLEXER I2CMultiplexerOff( diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index f2ab659359..2452316500 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -185,7 +185,7 @@ void run_compiletime_checks() { static_assert(198u == offsetof(SettingsStruct, TaskDeviceNumber), "NOTIFICATION_MAX has changed?"); // All settings related to N_TASKS - static_assert((228 + TASKS_MAX) == offsetof(SettingsStruct, OLD_TaskDeviceID), ""); // 32-bit alignment, so offset of 2 bytes. + static_assert((232 + TASKS_MAX) == offsetof(SettingsStruct, OLD_TaskDeviceID), ""); // 32-bit alignment, so offset of 2 bytes. static_assert((200 + (67 * TASKS_MAX)) == offsetof(SettingsStruct, ControllerEnabled), ""); // Used to compute true offset. diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 8c0f3bc22c..4606a8dc06 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -43,6 +43,9 @@ #include "../../ESPEasy_fdwdecl.h" #endif +#if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND #define PLUGIN_ID_MQTT_IMPORT 37 @@ -170,6 +173,10 @@ void runOncePerSecond() PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummy); // unsigned long elapsed = micros() - start; + #if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND + ESPEasy::eeprom::EEPROMExternalLoop(); + #endif // if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND + // I2C Watchdog feed if (Settings.WDI2CAddress != 0) diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index 2f9df6cdcf..9a47c7c5f1 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -149,6 +149,7 @@ const __FlashStringHelper * getLabel(LabelType::Enum label) { #if FEATURE_EEPROM_EXTERNAL case LabelType::EEPROM_RESTORE_ON_COLDBOOT: return F("Cold boot: Restore Task Values from EEPROM"); case LabelType::EEPROM_RESTORE_ON_WARMBOOT: return F("Warm boot: Restore Task Values from EEPROM"); + case LabelType::EEPROM_SAVE_DELAY: return F("Save interval (seconds)"); #endif // if FEATURE_EEPROM_EXTERNAL case LabelType::BOOT_TYPE: return F("Last Boot Cause"); @@ -450,6 +451,7 @@ String getValue(LabelType::Enum label) { #if FEATURE_EEPROM_EXTERNAL case LabelType::EEPROM_RESTORE_ON_COLDBOOT: return jsonBool(Settings.RestoreUserVarsFromEEPROMOnColdBoot()); case LabelType::EEPROM_RESTORE_ON_WARMBOOT: return jsonBool(Settings.RestoreUserVarsFromEEPROMOnWarmBoot()); + case LabelType::EEPROM_SAVE_DELAY: return toString(Settings.EEPROMSaveDelaySeconds()); #endif // if FEATURE_EEPROM_EXTERNAL case LabelType::BOOT_TYPE: return getLastBootCauseString(); diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index c919678b8f..e781b79954 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -97,6 +97,7 @@ struct LabelType { #if FEATURE_EEPROM_EXTERNAL EEPROM_RESTORE_ON_COLDBOOT, EEPROM_RESTORE_ON_WARMBOOT, + EEPROM_SAVE_DELAY, #endif // if FEATURE_EEPROM_EXTERNAL BOOT_TYPE, // Cold boot diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index 7270321784..bc26f97501 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -132,6 +132,7 @@ void handle_hardware() { Settings.EEPROMExternalI2CAddress(getFormItemInt(F("i2c_eeprom"), 0)); Settings.RestoreUserVarsFromEEPROMOnColdBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_COLDBOOT)); Settings.RestoreUserVarsFromEEPROMOnWarmBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_WARMBOOT)); + Settings.EEPROMSaveDelaySeconds(getFormItemInt(LabelType::EEPROM_SAVE_DELAY)); # if FEATURE_I2CMULTIPLEXER @@ -413,6 +414,7 @@ void handle_hardware() { } addFormCheckBox(LabelType::EEPROM_RESTORE_ON_COLDBOOT, Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); addFormCheckBox(LabelType::EEPROM_RESTORE_ON_WARMBOOT, Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); + addFormNumericBox(LabelType::EEPROM_SAVE_DELAY, Settings.EEPROMSaveDelaySeconds(), 0, 250); } #endif // if FEATURE_EEPROM_EXTERNAL From 6f1a12b86353abe65e05fab2d56ae3b600e3171d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 1 Sep 2025 23:43:27 +0200 Subject: [PATCH 28/44] [Storage] Fixes and improvements for Params check --- src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp | 19 +++++++++++++----- src/ESPEasy/eeprom/Helpers/EEPROMExternal.h | 8 ++++++-- src/src/Helpers/ESPEasyRTC.cpp | 20 ++++++++++--------- src/src/Helpers/ESPEasy_checks.cpp | 9 ++++++++- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp index 224bb3bd44..e589f9c03c 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp @@ -10,6 +10,8 @@ namespace ESPEasy { namespace eeprom { AT24CX *EEPROMExternal = nullptr; EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect = EEPROMExternal_WriteProtect_e::Undefined; +bool EEPROMParamsOkState{}; +LongTermTimer EEPROMParamsOkTimer; constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); @@ -82,12 +84,19 @@ void initializeEEPROMExternal() { * - RTC Cache address * - Pinstate address */ -bool validateEEPROMExternalParameters() { +bool validateEEPROMExternalParameters(bool force) { + if (!force && (EEPROMParamsOkTimer.millisPassedSince() < EEPROM_PARAMSOK_STATE_TIMEOUT)) { // When called within timeout return cached + // result + return EEPROMParamsOkState; + } const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); const uint32_t eepromPinstateParam = EEPROMExternal->readLong(EEPROM_PARAMS_PINSTATE_ADDRESS); + EEPROMParamsOkTimer.setNow(); + EEPROMParamsOkState = false; + # if FEATURE_RTC_CACHE_STORAGE const uint32_t eepromRtcCacheParam = EEPROMExternal->readLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS); # endif // if FEATURE_RTC_CACHE_STORAGE @@ -100,10 +109,10 @@ bool validateEEPROMExternalParameters() { # endif // if FEATURE_RTC_CACHE_STORAGE (EEPROM_GPIO_PINSTATE_START_OFFSET == eepromPinstateParam) && (EEPROM_GPIO_PINSTATE_END_OFFSET <= EEPROM_CUSTOM_START_OFFSET)) { - return true; + EEPROMParamsOkState = true; } - return false; + return EEPROMParamsOkState; } /** @@ -118,10 +127,10 @@ void updateEEPROMExternalParameters() { const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); - const uint32_t eepromPinstateParam = EEPROMExternal->readInt(EEPROM_PARAMS_PINSTATE_ADDRESS); + const uint32_t eepromPinstateParam = EEPROMExternal->readLong(EEPROM_PARAMS_PINSTATE_ADDRESS); # if FEATURE_RTC_CACHE_STORAGE - const uint32_t eepromRtcCacheParam = EEPROMExternal->readInt(EEPROM_PARAMS_RTC_CACHE_ADDRESS); + const uint32_t eepromRtcCacheParam = EEPROMExternal->readLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS); # endif // if FEATURE_RTC_CACHE_STORAGE if (EEPROM_PARAMS_CURRENT_VERSION != eepromVersionParam) { diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index 101f66f83a..68c2a14781 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -22,9 +22,11 @@ enum class EEPROMExternal_WriteProtect_e : uint8_t { extern AT24CX *EEPROMExternal; extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; +extern bool EEPROMParamsOkState; +extern LongTermTimer EEPROMParamsOkTimer; # define EEPROM_PARAMS_CURRENT_VERSION (1) // Let's start with version 1 -# define EEPROM_PINSTATE_ELEMENT_COUNT (64) // Number of PinState elements (16 bytes each) to store in EEPROM +# define EEPROM_PINSTATE_ELEMENT_COUNT (48) // Number of PinState elements (16 bytes each) to store in EEPROM // Start writing the base RTC struct from this offset (not currently saving this to EEPROM) // TODO # define EEPROM_BASERTC_START_OFFSET (0) @@ -75,6 +77,8 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; # define EEPROM_SUPPORT_AT24C1024 1 # define EEPROM_SUPPORT_AT24C2048 0 +# define EEPROM_PARAMSOK_STATE_TIMEOUT (180000) // 3 minutes + // Supported AT24Cxxx and MB85RCxxx devices enum class EEPROMExternal_Type_e : uint8_t { AT24C256 = 0, // Default, 32 kB @@ -103,7 +107,7 @@ enum class EEPROMExternal_Type_e : uint8_t { void initializeEEPROMExternal(); -bool validateEEPROMExternalParameters(); +bool validateEEPROMExternalParameters(bool force = false); void updateEEPROMExternalParameters(); uint8_t checkEEPROMEnabled(); diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index 1b58c00b97..ca3303d5a2 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -185,15 +185,17 @@ uint32_t saveUserVarToEEPROM() { if (Settings.TaskDeviceEnabled[task] || !paramsOk) { // Only check enabled tasks or when re-writing the params const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); if (taskValues != nullptr) { - LoadTaskSettings(task); for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - const uint32_t newData = Cache.getTaskVarStoreInEEPROM(task, varNr) - ? taskValues->getUint32(varNr) - : std::numeric_limits::max(); // NaN when read as float - const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); - if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs - ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); - eepromWritten += sizeof_uint32_t; + const bool storeValue = Cache.getTaskVarStoreInEEPROM(task, varNr); + if (!paramsOk || storeValue) { + const uint32_t newData = storeValue + ? taskValues->getUint32(varNr) + : std::numeric_limits::max(); // NaN when read as float + const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); + if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs + ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); + eepromWritten += sizeof_uint32_t; + } } } } @@ -310,7 +312,7 @@ bool readUserVarFromRTC() if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM // Check system parameters with last stored values - bool eepromParamsOK = ESPEasy::eeprom::validateEEPROMExternalParameters(); + const bool eepromParamsOK = ESPEasy::eeprom::validateEEPROMExternalParameters(true); // Check checksum and if correct, restore UserVar values const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index 2452316500..9e8a93a4b1 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -47,6 +47,9 @@ #include "../DataStructs/NotificationSettingsStruct.h" #endif // if FEATURE_NOTIFIER +#if FEATURE_EEPROM_EXTERNAL +#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" +#endif // if FEATURE_EEPROM_EXTERNAL // ******************************************************************************** // Check struct sizes at compile time @@ -197,8 +200,12 @@ void run_compiletime_checks() { // to determine nr of bits in a struct. static_assert(GPIO_DIRECTION_NR_BITS== NR_BITS(static_cast(gpio_direction::gpio_direction_MAX)), "Correct GPIO_DIRECTION_NR_BITS"); + #if FEATURE_EEPROM_EXTERNAL + static_assert(EEPROM_CUSTOM_START_OFFSET > EEPROM_GPIO_PINSTATE_END_OFFSET, "EEPROM GPIO Pinstates overlap with Slots values!"); + #endif // if FEATURE_EEPROM_EXTERNAL - #endif + + #endif // ifndef LIMIT_BUILD_SIZE } #ifndef LIMIT_BUILD_SIZE From c95689c373fe273ad96c4f479015949c3e80bc2a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 6 Apr 2026 20:03:18 +0200 Subject: [PATCH 29/44] [Storage] Reduce functionality to only support writeee command and readee variable retrieval --- src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp | 294 +++--------------- src/ESPEasy/eeprom/Helpers/EEPROMExternal.h | 104 +------ src/src/Commands/EEPROMExternal.cpp | 22 +- src/src/CustomBuild/define_plugin_sets.h | 22 -- src/src/DataStructs/Caches.cpp | 18 -- src/src/DataStructs/Caches.h | 7 - src/src/DataStructs/DeviceStruct.h | 2 - .../DataStructs/ExtraTaskSettingsStruct.cpp | 13 - src/src/DataStructs/ExtraTaskSettingsStruct.h | 5 - .../DataStructs/RTC_cache_handler_struct.cpp | 86 ----- src/src/DataStructs/SettingsStruct.h | 14 +- src/src/DataStructs_templ/SettingsStruct.cpp | 22 +- src/src/ESPEasyCore/ESPEasy_setup.cpp | 13 - src/src/Helpers/ESPEasyRTC.cpp | 159 ---------- src/src/Helpers/ESPEasy_checks.cpp | 9 - src/src/Helpers/PeriodicalActions.cpp | 8 - src/src/Helpers/StringParser.cpp | 4 + src/src/Helpers/StringProvider.cpp | 15 - src/src/Helpers/StringProvider.h | 6 - src/src/WebServer/DevicesPage.cpp | 28 -- src/src/WebServer/EpromVarPage.cpp | 62 +--- src/src/WebServer/HardwarePage.cpp | 6 - 22 files changed, 100 insertions(+), 819 deletions(-) diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp index e589f9c03c..0e6d06ce5d 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp @@ -13,7 +13,7 @@ EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect = EEPROMExternal_WriteP bool EEPROMParamsOkState{}; LongTermTimer EEPROMParamsOkTimer; -constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); +constexpr uint32_t sizeof_eeprom_slot = sizeof(double); /** * Initialize the external EEPROM device and variables @@ -89,26 +89,12 @@ bool validateEEPROMExternalParameters(bool force) { // result return EEPROMParamsOkState; } - const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); - const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); - const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); - const uint32_t eepromPinstateParam = EEPROMExternal->readLong(EEPROM_PARAMS_PINSTATE_ADDRESS); + const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); EEPROMParamsOkTimer.setNow(); EEPROMParamsOkState = false; - # if FEATURE_RTC_CACHE_STORAGE - const uint32_t eepromRtcCacheParam = EEPROMExternal->readLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS); - # endif // if FEATURE_RTC_CACHE_STORAGE - - if ((EEPROM_PARAMS_CURRENT_VERSION == eepromVersionParam) && - (TASKS_MAX == eepromTasksMaxParam) && - (VARS_PER_TASK == eepromVarsPerTaskParam) && - # if FEATURE_RTC_CACHE_STORAGE - (EEPROM_RTC_CACHE_START_OFFSET == eepromRtcCacheParam) && - # endif // if FEATURE_RTC_CACHE_STORAGE - (EEPROM_GPIO_PINSTATE_START_OFFSET == eepromPinstateParam) && - (EEPROM_GPIO_PINSTATE_END_OFFSET <= EEPROM_CUSTOM_START_OFFSET)) { + if (EEPROM_PARAMS_CURRENT_VERSION == eepromVersionParam) { EEPROMParamsOkState = true; } @@ -118,43 +104,13 @@ bool validateEEPROMExternalParameters(bool force) { /** * Update the stored parameters in EEPROM * - Version - * - Max. tasks - * - Vars per tasks - * - RTC Cache address - * - Pinstate address */ void updateEEPROMExternalParameters() { - const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); - const uint16_t eepromTasksMaxParam = EEPROMExternal->readInt(EEPROM_PARAMS_TASKS_MAX); - const uint16_t eepromVarsPerTaskParam = EEPROMExternal->readInt(EEPROM_PARAMS_VARS_PER_TASK); - const uint32_t eepromPinstateParam = EEPROMExternal->readLong(EEPROM_PARAMS_PINSTATE_ADDRESS); - - # if FEATURE_RTC_CACHE_STORAGE - const uint32_t eepromRtcCacheParam = EEPROMExternal->readLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS); - # endif // if FEATURE_RTC_CACHE_STORAGE + const uint16_t eepromVersionParam = EEPROMExternal->readInt(EEPROM_PARAMS_VERSION_ADDRESS); if (EEPROM_PARAMS_CURRENT_VERSION != eepromVersionParam) { EEPROMExternal->writeInt(EEPROM_PARAMS_VERSION_ADDRESS, EEPROM_PARAMS_CURRENT_VERSION); } - - if (TASKS_MAX != eepromTasksMaxParam) { - EEPROMExternal->writeInt(EEPROM_PARAMS_TASKS_MAX, TASKS_MAX); - } - - if (VARS_PER_TASK != eepromVarsPerTaskParam) { - EEPROMExternal->writeInt(EEPROM_PARAMS_VARS_PER_TASK, VARS_PER_TASK); - } - - # if FEATURE_RTC_CACHE_STORAGE - - if (EEPROM_RTC_CACHE_START_OFFSET != eepromRtcCacheParam) { - EEPROMExternal->writeLong(EEPROM_PARAMS_RTC_CACHE_ADDRESS, EEPROM_RTC_CACHE_START_OFFSET); - } - # endif // if FEATURE_RTC_CACHE_STORAGE - - if (EEPROM_GPIO_PINSTATE_START_OFFSET != eepromPinstateParam) { - EEPROMExternal->writeLong(EEPROM_PARAMS_PINSTATE_ADDRESS, EEPROM_GPIO_PINSTATE_START_OFFSET); - } } /** @@ -213,9 +169,7 @@ EEPROMExternal_WriteProtect_e checkEEPROMExternalWriteProtected(bool forced) { /** * Is the EEPROM WriteProtected? */ -bool isEEPROMExternalWriteProtected() { - return EEPROMExternal_WriteProtect_e::ReadWrite != checkEEPROMExternalWriteProtected(); -} +bool isEEPROMExternalWriteProtected() { return EEPROMExternal_WriteProtect_e::ReadWrite != checkEEPROMExternalWriteProtected(); } /** * Switch to I2C Bus and multiplexer channel of External EEPROM @@ -250,7 +204,8 @@ uint8_t selectEEPROMI2CBusAndMultiplexer() { * EEPROM size in bytes */ uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { - switch (type) { + switch (type) + { case EEPROMExternal_Type_e::AT24C256: case EEPROMExternal_Type_e::MB85RC256: return 32768ul; @@ -287,7 +242,8 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, uint8_t & pageSize) { pageSize = 0; - switch (type) { + switch (type) + { case EEPROMExternal_Type_e::AT24C256: case EEPROMExternal_Type_e::MB85RC256: pageSize = 64u; @@ -321,21 +277,20 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, * EEPROM/FRAM name */ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { - # ifndef BUILD_NO_DEBUG - - switch (type) { + switch (type) + { case EEPROMExternal_Type_e::AT24C256: return F("AT24C256"); case EEPROMExternal_Type_e::AT24C512: return F("AT24C512"); - # if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: return F("AT24C1024"); - # endif // if EEPROM_SUPPORT_AT24C1024 - # if EEPROM_SUPPORT_AT24C2048 + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: return F("AT24C2048"); - # endif // if EEPROM_SUPPORT_AT24C2048 + # endif // if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C32: return F("AT24C32"); case EEPROMExternal_Type_e::AT24C64: @@ -346,14 +301,14 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { return F("MB85RC256"); case EEPROMExternal_Type_e::MB85RC512: return F("MB85RC512"); - # if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::MB85RC1M: return F("MB85RC1M"); - # endif // if EEPROM_SUPPORT_AT24C1024 - # if EEPROM_SUPPORT_AT24C2048 + # endif // if EEPROM_SUPPORT_AT24C1024 + # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::MB85RC2M: return F("MB85RC2M"); - # endif // if EEPROM_SUPPORT_AT24C2048 + # endif // if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::MB85RC32: return F("MB85RC32"); case EEPROMExternal_Type_e::MB85RC64: @@ -362,9 +317,6 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { return F("MB85RC128"); } return F(""); - # else // ifndef BUILD_NO_DEBUG - return F("EEPROM/FRAM"); - # endif // ifndef BUILD_NO_DEBUG } /** @@ -375,7 +327,7 @@ uint32_t getEEPROMAddressForSlot(uint32_t slot) { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); if ((eepromSize > 0) && (slot < getEEPROMMaxSlots())) { - const uint32_t slotAddr = EEPROM_CUSTOM_START_OFFSET + (slot * sizeof_uint32_t); + const uint32_t slotAddr = EEPROM_CUSTOM_START_OFFSET + (slot * sizeof_eeprom_slot); if (slotAddr < eepromSize) { return slotAddr; @@ -386,34 +338,14 @@ uint32_t getEEPROMAddressForSlot(uint32_t slot) { } /** - * EEPROM address for task and varnr or 0xFFFF when error - */ -uint32_t getEEPROMAddressForTaskValue(taskIndex_t task, taskVarIndex_t varNr) { - if (checkEEPROMEnabled() > 0) { - const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); - - if ((eepromSize > 0) && validTaskIndex(task) && validTaskVarIndex(varNr)) { - const uint32_t slotAddr = EEPROM_USERVAR_START_OFFSET + (((task * VARS_PER_TASK) + varNr) * sizeof_uint32_t); - - if (slotAddr < eepromSize) { - return slotAddr; - } - } - } - return std::numeric_limits::max(); -} - -/** - * EEPROM available number of slots, max 1024 - * NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! + * EEPROM available number of slots, max use all available space minus some administrative bytes */ uint32_t getEEPROMMaxSlots() { if (checkEEPROMEnabled() > 0) { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); if (eepromSize > 0) { - const uint32_t slotMax = min((unsigned long)(((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_uint32_t), - 1024ul); + const uint32_t slotMax = (unsigned long)(((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_eeprom_slot); return slotMax; } @@ -424,15 +356,27 @@ uint32_t getEEPROMMaxSlots() { /** * EEPROM write value to slot if the slot is valid */ +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + bool writeEEPROMSlot(uint32_t slot, - float data) { + double data) +# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + +bool writeEEPROMSlot(uint32_t slot, + float data) +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +{ const uint32_t addr = getEEPROMAddressForSlot(slot); if ((addr != std::numeric_limits::max()) && !isEEPROMExternalWriteProtected()) { - const float oldData = EEPROMExternal->readLong(addr); + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + const double oldData = EEPROMExternal->readDouble(addr); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + const float oldData = EEPROMExternal->readDouble(addr); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE if (!essentiallyEqual(oldData, data)) { - EEPROMExternal->writeFloat(addr, data); + EEPROMExternal->writeDouble(addr, data); // Always write double size! } return true; } @@ -442,170 +386,30 @@ bool writeEEPROMSlot(uint32_t slot, /** * EEPROM read value from slot or 0 when invalid */ -float readEEPROMSlot(uint32_t slot) { +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + +double readEEPROMSlot(uint32_t slot) { const uint32_t addr = getEEPROMAddressForSlot(slot); if (addr != std::numeric_limits::max()) { - return EEPROMExternal->readFloat(addr); + return EEPROMExternal->readDouble(addr); } - return 0.0f; + return 0.0; } -# if FEATURE_EEPROM_BACKGROUND - -// A map of tasks to be executed -std::map EEPROMTaskMap; -uint16_t EEPROMSaveDelaySeconds{}; +# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -/** - * Convert TaskType to text - */ -const __FlashStringHelper* TaskDataTypeToString(EEPROMExternalTaskType_e type) { - switch (type) { - case EEPROMExternalTaskType_e::None: return F(""); - case EEPROMExternalTaskType_e::UserVars: return F("UserVars"); - case EEPROMExternalTaskType_e::ValueSlots: return F("ValueSlots"); - case EEPROMExternalTaskType_e::C016Caches: return F("C016 Cache elements"); - case EEPROMExternalTaskType_e::PinStates: return F("Pinstates"); - } - return F(""); -} - -/** - * Perform the actual task, bij calling the provided function - */ -void EEPROM_execute_task(void *parameter) { - EEPROMExternalTaskData*_task_data = static_cast(parameter); - - if ((_task_data->status == EEPROMExternalTaskState_e::Starting) && (nullptr != _task_data->function)) { - _task_data->status = EEPROMExternalTaskState_e::Processing; - - selectEEPROMI2CBusAndMultiplexer(); - - _task_data->timer.setNow(); - - // Blocking operation - _task_data->data = _task_data->function(); - - // Results are in - _task_data->duration = _task_data->timer.millisPassedSince(); - - # if FEATURE_I2CMULTIPLEXER - I2CMultiplexerOff( - # if FEATURE_I2C_MULTIPLE - Settings.getI2CInterfaceEEPROM() - # else // if FEATURE_I2C_MULTIPLE - 0 - # endif // if FEATURE_I2C_MULTIPLE - ); // Restore the Multiplexer channel - # endif // if FEATURE_I2CMULTIPLEXER - - _task_data->status = EEPROMExternalTaskState_e::Ready; - } - - _task_data->function = nullptr; // Don't run again accidently - # if FEATURE_EEPROM_RTOS_TASK - _task_data->taskHandle = NULL; - vTaskDelete(_task_data->taskHandle); - # endif // if FEATURE_EEPROM_RTOS_TASK -} - -/** - * Insert a task into the taskmap, 1 per task type, ignore new task when same type is already scheduled to run, or currently running - */ -bool EEPROMAddTask(EEPROMExternalTaskType_e type, - EEPROMExternalTaskData taskData) { - auto task = EEPROMTaskMap.find(type); - - // Is a task is already in progress, skip until the next AddTask - if ((task == EEPROMTaskMap.end()) || (EEPROMExternalTaskState_e::Available == task->second.status)) { - EEPROMTaskMap[type] = taskData; // Insert task - task = EEPROMTaskMap.find(type); - - if (task != EEPROMTaskMap.end()) { // Upserted successfully - task->second.status = EEPROMExternalTaskState_e::Starting; - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, - strformat(F("EEPROM: AddTask upserted for %s, status %u (tasks: %u)" - # if FEATURE_EEPROM_RTOS_TASK - " (RTOS)" - # endif // if FEATURE_EEPROM_RTOS_TASK - ), - FsP(TaskDataTypeToString(task->second.type)), static_cast(task->second.status), EEPROMTaskMap.size())); - } - # endif // ifndef BUILD_NO_DEBUG - } else { - addLog(LOG_LEVEL_ERROR, F("EEPROM: Task not inserted")); - } +float readEEPROMSlot(uint32_t slot) { + const uint32_t addr = getEEPROMAddressForSlot(slot); - # if FEATURE_EEPROM_RTOS_TASK - EEPROMExternalLoop(); // Kick off if it's a RTOS task - # endif // if FEATURE_EEPROM_RTOS_TASK - return true; + if (addr != std::numeric_limits::max()) { + return EEPROMExternal->readDouble(addr); // Always read double size! } - return false; + return 0.0f; } -/** - * Check the task map for tasks to run and start that, also report about finished tasks, and clean those for re-use - */ -bool EEPROMExternalLoop() { - if (0 != EEPROMSaveDelaySeconds) { - EEPROMSaveDelaySeconds--; - return false; - } - EEPROMSaveDelaySeconds = Settings.EEPROMSaveDelaySeconds(); - - for (auto task = EEPROMTaskMap.begin(); task != EEPROMTaskMap.end(); ++task) { - if ((EEPROMExternalTaskState_e::Ready == task->second.status) || - (EEPROMExternalTaskState_e::Error == task->second.status)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: Write %s %s %d in %u msec." - # if FEATURE_EEPROM_RTOS_TASK - " (RTOS)" - # endif // if FEATURE_EEPROM_RTOS_TASK - ), - FsP(TaskDataTypeToString(task->second.type)), - FsP(EEPROMExternalTaskState_e::Ready == task->second.status ? F("success") : F("failed")), - task->second.data, - task->second.duration)); - } - task->second.status = EEPROMExternalTaskState_e::Available; - } - - if (EEPROMExternalTaskState_e::Starting == task->second.status) { - if (nullptr != task->second.function) { - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, strformat(F("EEPROM: Loop starting task: %s"), FsP(TaskDataTypeToString(task->second.type)))); - } - # endif // ifndef BUILD_NO_DEBUG - # if FEATURE_EEPROM_RTOS_TASK - xTaskCreatePinnedToCore( - EEPROM_execute_task, // Function that should be called - "EEPROM.write()", // Name of the task (for debugging) - 4000, // Stack size (bytes) - &task->second, // Parameter to pass - 1, // Task priority - &task->second.taskHandle, // Task handle - xPortGetCoreID() // Core you want to run the task on (0 or 1) - ); - # else // if FEATURE_EEPROM_RTOS_TASK - EEPROM_execute_task(&task->second); - # endif // if FEATURE_EEPROM_RTOS_TASK - } else { - task->second.status = EEPROMExternalTaskState_e::Available; // Recycle - } - } - } - return false; -} +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -# endif // if FEATURE_EEPROM_BACKGROUND } // namespace eeprom } // namespace ESPEasy #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index 68c2a14781..634f0bc34d 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -4,11 +4,7 @@ #if FEATURE_EEPROM_EXTERNAL # include "../../../src/DataTypes/TaskIndex.h" -# include "../../../src/DataStructs/PortStatusStruct.h" -# include "../../../src/DataStructs/RTCCacheStruct.h" -# include "../../../src/DataStructs/RTCStruct.h" # include "../../../src/Helpers/LongTermTimer.h" -# include # include @@ -18,6 +14,7 @@ enum class EEPROMExternal_WriteProtect_e : uint8_t { Undefined = 0, ReadWrite = 1, ReadOnly = 2, + }; extern AT24CX *EEPROMExternal; @@ -25,53 +22,17 @@ extern EEPROMExternal_WriteProtect_e EEPROMExternalWriteProtect; extern bool EEPROMParamsOkState; extern LongTermTimer EEPROMParamsOkTimer; -# define EEPROM_PARAMS_CURRENT_VERSION (1) // Let's start with version 1 -# define EEPROM_PINSTATE_ELEMENT_COUNT (48) // Number of PinState elements (16 bytes each) to store in EEPROM +# define EEPROM_PARAMS_CURRENT_VERSION (1) // Let's start with version 1 -// Start writing the base RTC struct from this offset (not currently saving this to EEPROM) // TODO +// Start from this offset # define EEPROM_BASERTC_START_OFFSET (0) // Some system parameters to check before restoring anything # define EEPROM_PARAMS_VERSION_ADDRESS (32) -# define EEPROM_PARAMS_TASKS_MAX (EEPROM_PARAMS_VERSION_ADDRESS + sizeof(uint16_t)) -# define EEPROM_PARAMS_VARS_PER_TASK (EEPROM_PARAMS_TASKS_MAX + sizeof(uint16_t)) -# define EEPROM_PARAMS_RTC_CACHE_ADDRESS (EEPROM_PARAMS_VARS_PER_TASK + sizeof(uint16_t)) -# define EEPROM_PARAMS_PINSTATE_ADDRESS (EEPROM_PARAMS_RTC_CACHE_ADDRESS + sizeof(uint32_t)) - -// Start writing the UserVar values from this offset, should be > sizeof(RTCStruct) that is 32 currently -# define EEPROM_USERVAR_START_OFFSET (EEPROM_BASERTC_START_OFFSET + 128) - -// Write the UserVar-checksum from this offset, right after the UserVar values -# define EEPROM_USERVAR_CHECKSUM_OFFSET (EEPROM_USERVAR_START_OFFSET + (TASKS_MAX * VARS_PER_TASK * sizeof(uint32_t))) - -# if FEATURE_RTC_CACHE_STORAGE - -// Even when not enabled, we still reserve the space -# define EEPROM_RTC_CACHE_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) -# define EEPROM_RTC_CACHE_WRITEPOS_OFFSET (EEPROM_RTC_CACHE_START_OFFSET + RTC_CACHE_DATA_SIZE) -# define EEPROM_RTC_CACHE_META_OFFSET (EEPROM_RTC_CACHE_WRITEPOS_OFFSET + sizeof(uint16_t)) -# define EEPROM_RTC_CACHE_META_CRC_OFFSET (EEPROM_RTC_CACHE_META_OFFSET + sizeof(RTC_cache_struct)) -# define EEPROM_RTC_CACHE_CHECKSUM_OFFSET (EEPROM_RTC_CACHE_META_CRC_OFFSET + sizeof(uint32_t)) -# endif // if FEATURE_RTC_CACHE_STORAGE - -// Offset for storing GPIO pinstates, directly following the RTC Cache storage and checksum -# if FEATURE_RTC_CACHE_STORAGE -# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_RTC_CACHE_CHECKSUM_OFFSET + sizeof(uint32_t)) -# else // if FEATURE_RTC_CACHE_STORAGE -# define EEPROM_GPIO_PINSTATE_START_OFFSET (EEPROM_USERVAR_CHECKSUM_OFFSET + sizeof(uint32_t)) -# endif // if FEATURE_RTC_CACHE_STORAGE -# define EEPROM_PINSTATE_ELEMENT_SIZE (sizeof(uint32_t) + sizeof(portStatusStruct)) -# define EEPROM_GPIO_PINSTATE_END_OFFSET (EEPROM_GPIO_PINSTATE_START_OFFSET + (EEPROM_PINSTATE_ELEMENT_COUNT * EEPROM_PINSTATE_ELEMENT_SIZE)) - -// Choose an arbitrary but fixed offset -# define EEPROM_CUSTOM_START_OFFSET (2048) - -// NB: Only first half of EEPROM_CUSTOM_START_OFFSET available for slots when String Variables feature enabled! -# if FEATURE_STRING_VARIABLES -# define EEPROM_CUSTOM_DIVISOR (2u) // Split in slots- and strings- halves -# else // if FEATURE_STRING_VARIABLES -# define EEPROM_CUSTOM_DIVISOR (1u) // Use all for slots -# endif // if FEATURE_STRING_VARIABLES + +// Start writing the Custom slot values from this offset so we have some room for settings, if needed +# define EEPROM_CUSTOM_START_OFFSET (EEPROM_BASERTC_START_OFFSET + 128) +# define EEPROM_CUSTOM_DIVISOR (1u) // Use all for slots // Enable/disable some models # define EEPROM_SUPPORT_AT24C1024 1 @@ -103,6 +64,7 @@ enum class EEPROMExternal_Type_e : uint8_t { MB85RC32 = 11, // 4 kB, not endorsed, possibly not available MB85RC64 = 12, // 8 kB MB85RC128 = 13, // 16 kB + }; void initializeEEPROMExternal(); @@ -122,54 +84,18 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type); uint32_t getEEPROMAddressForSlot(uint32_t slot); -uint32_t getEEPROMAddressForTaskValue(taskIndex_t task, - taskVarIndex_t varNr); + uint32_t getEEPROMMaxSlots(); +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +bool writeEEPROMSlot(uint32_t slot, + double data); +double readEEPROMSlot(uint32_t slot); +# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE bool writeEEPROMSlot(uint32_t slot, float data); float readEEPROMSlot(uint32_t slot); +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -# if FEATURE_EEPROM_BACKGROUND -enum class EEPROMExternalTaskState_e : uint8_t { - Available = 0, - Starting = 1, - Processing = 2, - Ready = 3, - Error = 4, -}; - -enum class EEPROMExternalTaskType_e : uint8_t { - None = 0, - UserVars = 1, - ValueSlots = 2, - C016Caches = 3, - PinStates = 4, -}; - -struct EEPROMExternalTaskData { - EEPROMExternalTaskState_e status = EEPROMExternalTaskState_e::Available; - EEPROMExternalTaskType_e type = EEPROMExternalTaskType_e::None; - uint32_t (*function)() = nullptr; - LongTermTimer timer; - uint32_t duration{}; - int32_t data{}; - - # if FEATURE_EEPROM_RTOS_TASK - - // This is C-code, so not set to nullptr, but to NULL - TaskHandle_t taskHandle = NULL; - # endif // if FEATURE_EEPROM_RTOS_TASK -}; - -const __FlashStringHelper* TaskDataTypeToString(EEPROMExternalTaskType_e type); -bool EEPROMAddTask(EEPROMExternalTaskType_e type, - EEPROMExternalTaskData taskData); -bool EEPROMExternalLoop(); - -extern std::map EEPROMTaskMap; -extern uint16_t EEPROMSaveDelaySeconds; - -# endif // if FEATURE_EEPROM_BACKGROUND } // namespace eeprom } // namespace ESPEasy #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Commands/EEPROMExternal.cpp b/src/src/Commands/EEPROMExternal.cpp index 598fb044e7..da332d51df 100644 --- a/src/src/Commands/EEPROMExternal.cpp +++ b/src/src/Commands/EEPROMExternal.cpp @@ -18,22 +18,36 @@ const __FlashStringHelper* Command_writeEE(struct EventStruct *event, const char *Line) { uint32_t slot{}; - float value{}; - if (validUIntFromString(parseString(Line, 2), slot) && validFloatFromString(parseString(Line, 3), value)) { + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + double value{}; + bool validValue = validDoubleFromString(parseString(Line, 3), value); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + float value{}; + bool validValue = validFloatFromString(parseString(Line, 3), value); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + if (validUIntFromString(parseString(Line, 2), slot) && validValue) { return return_command_boolean_result_flashstr(ESPEasy::eeprom::writeEEPROMSlot(slot, value)); } else if (equals(parseString(Line, 2), F("erase")) && equals(parseString(Line, 3), F("erase"))) { for (uint32_t slot = 0; slot < ESPEasy::eeprom::getEEPROMMaxSlots(); ++slot) { + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + ESPEasy::eeprom::writeEEPROMSlot(slot, 0.0); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE ESPEasy::eeprom::writeEEPROMSlot(slot, 0.0f); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + if (slot % 50 == 0) { delay(0); } } addLog(LOG_LEVEL_INFO, F("EEPROM: All slot-values erased.")); return return_command_success_flashstr(); } else if (equals(parseString(Line, 2), F("check")) && equals(parseString(Line, 3), F("wp"))) { addLog(LOG_LEVEL_INFO, F("EEPROM: Check write-protect.")); ESPEasy::eeprom::checkEEPROMExternalWriteProtected(true); - + if (ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { - addLog(LOG_LEVEL_INFO, concat(F("EEPROM: Write-protected! Status: "), static_cast(ESPEasy::eeprom::checkEEPROMExternalWriteProtected()))); + addLog(LOG_LEVEL_INFO, + concat(F("EEPROM: Write-protected! Status: "), static_cast(ESPEasy::eeprom::checkEEPROMExternalWriteProtected()))); } return return_command_success_flashstr(); } diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 92bdc56104..2ac5f20246 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -4323,28 +4323,6 @@ To create/register a plugin, you have to : #endif #endif #endif // ifndef FEATURE_EEPROM_EXTERNAL -#ifndef FEATURE_EEPROM_BACKGROUND - #ifdef ESP32 - #define FEATURE_EEPROM_BACKGROUND 1 - #ifndef FEATURE_EEPROM_RTOS_TASK - #define FEATURE_EEPROM_RTOS_TASK 0 // I2C is not thread-safe - #endif - #endif // ifdef ESP32 - #ifdef ESP8266 - #ifdef LIMIT_BUILD_SIZE - #define FEATURE_EEPROM_BACKGROUND 0 // Disabled for limited builds on ESP8266 - #else - #define FEATURE_EEPROM_BACKGROUND 1 // Enabled by default on ESP8266 - #endif - #endif // ifdef ESP8266 -#endif // ifndef FEATURE_EEPROM_BACKGROUND -#if defined(ESP8266) && defined(FEATURE_EEPROM_RTOS_TASK) && FEATURE_EEPROM_RTOS_TASK - #undef FEATURE_EEPROM_RTOS_TASK - #define FEATURE_EEPROM_RTOS_TASK 0 // Not supported on ESP8266 -#endif // if defined(ESP8266) && defined(FEATURE_EEPROM_RTOS_TASK) && FEATURE_EEPROM_RTOS_TASK -#ifndef FEATURE_EEPROM_RTOS_TASK - #define FEATURE_EEPROM_RTOS_TASK 0 -#endif // ifndef FEATURE_EEPROM_RTOS_TASK #ifndef FEATURE_PLUGIN_LIST #ifdef ESP32 diff --git a/src/src/DataStructs/Caches.cpp b/src/src/DataStructs/Caches.cpp index f5bf17de88..29f76b27eb 100644 --- a/src/src/DataStructs/Caches.cpp +++ b/src/src/DataStructs/Caches.cpp @@ -340,21 +340,6 @@ uint8_t Caches::getTaskVarStateClass(taskIndex_t TaskIndex, #endif // if FEATURE_MQTT_STATE_CLASS -#if FEATURE_EEPROM_EXTERNAL -uint8_t Caches::getTaskVarStoreInEEPROM(taskIndex_t TaskIndex, - taskVarIndex_t taskVarIndex) { - if (validTaskIndex(TaskIndex) && (validTaskVarIndex(taskVarIndex))) { - auto it = getExtraTaskSettings(TaskIndex); - - if (it != extraTaskSettings_cache.end()) { - return it->second.storeInEEPROM[taskVarIndex]; - } - } - return false; -} - -#endif // if FEATURE_EEPROM_EXTERNAL - void Caches::updateExtraTaskSettingsCache() { const taskIndex_t TaskIndex = ExtraTaskSettings.TaskIndex; @@ -408,9 +393,6 @@ void Caches::updateExtraTaskSettingsCache() #if FEATURE_MQTT_STATE_CLASS tmp.mqttStateClass[i] = ExtraTaskSettings.getTaskVarStateClass(i); #endif // if FEATURE_MQTT_STATE_CLASS - #if FEATURE_EEPROM_EXTERNAL - tmp.storeInEEPROM[i] = ExtraTaskSettings.getTaskVarStoreInEEPROM(i); - #endif // if FEATURE_EEPROM_EXTERNAL } #ifdef ESP32 tmp.TaskDevicePluginConfigLong_index_used = 0; diff --git a/src/src/DataStructs/Caches.h b/src/src/DataStructs/Caches.h index 46019bc1da..31f9e5b606 100644 --- a/src/src/DataStructs/Caches.h +++ b/src/src/DataStructs/Caches.h @@ -60,9 +60,6 @@ struct ExtraTaskSettings_cache_t { #if FEATURE_MQTT_STATE_CLASS uint8_t mqttStateClass[VARS_PER_TASK] = { 0 }; // state_class = None, Measurement, Measurement Angle, Total, Total Increasing #endif // if FEATURE_MQTT_STATE_CLASS - #if FEATURE_EEPROM_EXTERNAL - bool storeInEEPROM[VARS_PER_TASK]{}; - #endif // if FEATURE_EEPROM_EXTERNAL }; typedef std::map TaskIndexNameMap; @@ -136,10 +133,6 @@ struct Caches { uint8_t getTaskVarStateClass(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex); #endif // if FEATURE_MQTT_STATE_CLASS - #if FEATURE_EEPROM_EXTERNAL - uint8_t getTaskVarStoreInEEPROM(taskIndex_t taskIndex, - taskVarIndex_t taskVarIndex); - #endif // if FEATURE_EEPROM_EXTERNAL // Update all cached values, except the checksum. void updateExtraTaskSettingsCache(); diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index ff70c86fdf..5bf20e3aa4 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -62,8 +62,6 @@ #define EEPROM_MUX_FLAGS_PORT 0 // bit-offset within multiplexerflags for the portnr/bits (8 bits) #define EEPROM_MUX_FLAGS_MULTI 8 // bit-offset within multiplexerflags for bits or port (1 bit) -#define EEPROM_SAVEOPTIONS_DELAY 0 // bit-offset within EEPROMSaveOptions for the Save Delay (seconds) - #endif // if FEATURE_EEPROM_EXTERNAL // Stored in Settings.I2C_SPI_bus_Flags !!! #define SPI_FLAGS_TASK_BUS_NUMBER 0 // 2 bit, stores the configured bus for a task diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp index 768d0113a6..8cfc37c6ad 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp @@ -303,19 +303,6 @@ void ExtraTaskSettingsStruct::setTaskVarStateClass(taskVarIndex_t taskVarIndex, } } #endif // if FEATURE_MQTT_STATE_CLASS -#if FEATURE_EEPROM_EXTERNAL -bool ExtraTaskSettingsStruct::getTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex) const { - if (!validTaskVarIndex(taskVarIndex)) { return false; } - return bitRead(VariousBits[taskVarIndex], 27); -} - -void ExtraTaskSettingsStruct::setTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex, - bool store) { - if (validTaskVarIndex(taskVarIndex)) { - bitWrite(VariousBits[taskVarIndex], 27, store); - } -} -#endif // if FEATURE_EEPROM_EXTERNAL void ExtraTaskSettingsStruct::populateDeviceValueNamesSeq( diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.h b/src/src/DataStructs/ExtraTaskSettingsStruct.h index 5265f813a1..dc5ad44614 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.h +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.h @@ -89,11 +89,6 @@ struct ExtraTaskSettingsStruct void setTaskVarStateClass(taskVarIndex_t taskVarIndex, uint8_t stateClass); #endif // if FEATURE_MQTT_STATE_CLASS - #if FEATURE_EEPROM_EXTERNAL - bool getTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex) const; - void setTaskVarStoreInEEPROM(taskVarIndex_t taskVarIndex, - bool store); - #endif // if FEATURE_EEPROM_EXTERNAL void populateDeviceValueNamesSeq(const __FlashStringHelper *valuename, size_t nrValues, diff --git a/src/src/DataStructs/RTC_cache_handler_struct.cpp b/src/src/DataStructs/RTC_cache_handler_struct.cpp index 9df700ba3d..4703692c93 100644 --- a/src/src/DataStructs/RTC_cache_handler_struct.cpp +++ b/src/src/DataStructs/RTC_cache_handler_struct.cpp @@ -11,10 +11,6 @@ #include "../ESPEasyCore/ESPEasy_backgroundtasks.h" #include "../ESPEasyCore/ESPEasy_Log.h" -#if FEATURE_EEPROM_EXTERNAL -# include "../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#endif // if FEATURE_EEPROM_EXTERNAL - #ifdef ESP8266 # include #endif // ifdef ESP8266 @@ -30,61 +26,6 @@ RTC_NOINIT_ATTR RTC_cache_struct RTC_cache; RTC_NOINIT_ATTR uint8_t RTC_cache_data[RTC_CACHE_DATA_SIZE]; #endif // ifdef ESP32 -/********************************************************************************************** - * EEPROM Helper function for RTC Cache - **********************************************************************************************/ -#if FEATURE_EEPROM_EXTERNAL -void EEPROMSaveWritePos() { - const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); - if (eepromAddress > 0) { - // Save new position - if (ESPEasy::eeprom::EEPROMExternal->readInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET) != RTC_cache.writePos) { - ESPEasy::eeprom::EEPROMExternal->writeInt(EEPROM_RTC_CACHE_WRITEPOS_OFFSET, RTC_cache.writePos); - } - } -} - -void EEPROMSaveMetaDataChecksum() { - const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); - if (eepromAddress > 0) { - // Save metadata checksum - if (ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_RTC_CACHE_META_CRC_OFFSET) != RTC_cache.checksumMetadata) { - ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_RTC_CACHE_META_CRC_OFFSET, RTC_cache.checksumMetadata); - } - } -} - -void EEPROMSaveCacheChecksum() { - const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); - if (eepromAddress > 0) { - // Save cache checksum - if (ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET) != RTC_cache.checksumData) { - ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_RTC_CACHE_CHECKSUM_OFFSET, RTC_cache.checksumData); - } - } -} - -void EEPROMSaveRTCcache() { - const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); - if (eepromAddress > 0) { - size_t count{}; - for (uint16_t idx = 0; idx < sizeof(RTC_cache); ++idx) { - const uint32_t metaAddr = EEPROM_RTC_CACHE_META_OFFSET + idx; - const uint8_t oldData = ESPEasy::eeprom::EEPROMExternal->read(metaAddr); - const uint8_t newData = reinterpret_cast(&RTC_cache)[idx]; - if (oldData != newData) { - ESPEasy::eeprom::EEPROMExternal->write(metaAddr, newData); - ++count; - } - } - // Save metadata checksum - EEPROMSaveMetaDataChecksum(); - // Save cache checksum - EEPROMSaveCacheChecksum(); - } -} -#endif // if FEATURE_EEPROM_EXTERNAL - /********************************************************************************************\ RTC located cache \*********************************************************************************************/ @@ -283,24 +224,6 @@ bool RTC_cache_handler_struct::write(const uint8_t *data, unsigned int size) { ++RTC_cache.writePos; } - #if FEATURE_EEPROM_EXTERNAL - const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); - if (eepromAddress > 0) { - size_t count{}; - for (unsigned int idx = 0; idx < size; ++idx) { - const uint32_t cacheAddr = EEPROM_RTC_CACHE_START_OFFSET + idx; - const uint8_t oldData = ESPEasy::eeprom::EEPROMExternal->read(cacheAddr); - const uint8_t newData = data[idx]; - if (oldData != newData) { - ESPEasy::eeprom::EEPROMExternal->write(cacheAddr, newData); - ++count; - } - } - // Save new position - EEPROMSaveWritePos(); - } - #endif // if FEATURE_EEPROM_EXTERNAL - // Now store the updated part of the buffer to the RTC memory. // Pad some extra bytes around it to allow sample sizes not multiple of 4 bytes. int startOffset = RTC_cache.writePos - size; @@ -581,10 +504,6 @@ bool RTC_cache_handler_struct::saveRTCcache(unsigned int startOffset, size_t nrB RTC_cache.checksumData = getDataChecksum(); RTC_cache.checksumMetadata = calc_CRC32(reinterpret_cast(&RTC_cache), sizeof(RTC_cache) - sizeof(uint32_t)); - #if FEATURE_EEPROM_EXTERNAL - EEPROMSaveRTCcache(); - #endif // if FEATURE_EEPROM_EXTERNAL - #ifdef ESP32 return true; #endif // ifdef ESP32 @@ -653,11 +572,6 @@ void RTC_cache_handler_struct::clearRTCcacheData() { RTC_cache_data[i] = 0; } RTC_cache.writePos = 0; - - #if FEATURE_EEPROM_EXTERNAL - // Save new position - EEPROMSaveWritePos(); - #endif // if FEATURE_EEPROM_EXTERNAL } // Return true if any cache file found diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 867aef212c..496ee5bc6f 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -225,12 +225,6 @@ class SettingsStruct_tmpl inline void ShowUnitOfMeasureOnDevicesPage(bool value) { VariousBits_2.ShowUnitOfMeasureOnDevicesPage = !value; } #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - #if FEATURE_EEPROM_EXTERNAL - bool RestoreUserVarsFromEEPROMOnColdBoot() const { return VariousBits_2.RestoreUserVarsFromEEPROMOnColdBoot; } - void RestoreUserVarsFromEEPROMOnColdBoot(bool value) { VariousBits_2.RestoreUserVarsFromEEPROMOnColdBoot = value; } - bool RestoreUserVarsFromEEPROMOnWarmBoot() const { return VariousBits_2.RestoreUserVarsFromEEPROMOnWarmBoot; } - void RestoreUserVarsFromEEPROMOnWarmBoot(bool value) { VariousBits_2.RestoreUserVarsFromEEPROMOnWarmBoot = value; } - #endif // if FEATURE_EEPROM_EXTERNAL #if CONFIG_SOC_WIFI_SUPPORT_5G wifi_band_mode_t WiFi_band_mode() const { if (VariousBits_2.WiFi_band_mode == 0) return WIFI_BAND_MODE_AUTO; @@ -428,9 +422,7 @@ class SettingsStruct_tmpl void EEPROMExternalI2CMultiplexerFlags(uint16_t muxFlags); uint8_t EEPROMExternalType() const; void EEPROMExternalType(uint8_t type); - uint8_t EEPROMSaveDelaySeconds() const; - void EEPROMSaveDelaySeconds(uint8_t seconds); - #endif + #endif // if FEATURE_EEPROM_EXTERNAL #if FEATURE_I2CMULTIPLEXER int8_t getI2CMultiplexerType(uint8_t i2cBus) const; @@ -758,8 +750,8 @@ class SettingsStruct_tmpl uint32_t ShowUnitOfMeasureOnDevicesPage : 1; // Bit 07 // inverted uint32_t WiFi_band_mode : 2; // Bit 08 & 09 uint32_t WiFi_AP_enable_NAPT : 1; // Bit 10 // inverted - uint32_t RestoreUserVarsFromEEPROMOnColdBoot : 1; // Bit 11 - uint32_t RestoreUserVarsFromEEPROMOnWarmBoot : 1; // Bit 12 + uint32_t unused_11 : 1; // Bit 11 + uint32_t unused_12 : 1; // Bit 12 uint32_t MQTTConnectInBackground : 1; // Bit 13 // inverted uint32_t StartAPfallback_NoCredentials : 1; // Bit 14 // inverted diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index ce43fd9faf..6dd738d65e 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -1355,7 +1355,7 @@ uint8_t SettingsStruct_tmpl::getI2CInterfacePCFMCP() const { } #if FEATURE_EEPROM_EXTERNAL -template +template uint8_t SettingsStruct_tmpl::getI2CInterfaceEEPROM() const { return get3BitFromUL(I2C_peripheral_bus, I2C_PERIPHERAL_BUS_EEPROM); } @@ -1363,38 +1363,30 @@ uint8_t SettingsStruct_tmpl::getI2CInterfaceEEPROM() const { #endif // if FEATURE_I2C_MULTIPLE #if FEATURE_EEPROM_EXTERNAL -template +template uint8_t SettingsStruct_tmpl::EEPROMExternalI2CAddress() const { return get8BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_ADDRESS); } -template +template void SettingsStruct_tmpl::EEPROMExternalI2CAddress(uint8_t address) { set8BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_ADDRESS, address); } -template +template uint16_t SettingsStruct_tmpl::EEPROMExternalI2CMultiplexerFlags() const { return get16BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_MUX); } -template +template void SettingsStruct_tmpl::EEPROMExternalI2CMultiplexerFlags(uint16_t muxFlags) { set16BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_MUX, muxFlags); } -template +template uint8_t SettingsStruct_tmpl::EEPROMExternalType() const { return static_cast(get4BitFromUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE)); } -template +template void SettingsStruct_tmpl::EEPROMExternalType(uint8_t type) { set4BitToUL(EEPROMExternalFlags, EEPROM_EXTERNAL_FLAGS_SIZE, type); } -template -uint8_t SettingsStruct_tmpl::EEPROMSaveDelaySeconds() const { - return get8BitFromUL(EEPROMSaveOptions, EEPROM_SAVEOPTIONS_DELAY); -} -template -void SettingsStruct_tmpl::EEPROMSaveDelaySeconds(uint8_t seconds) { - set8BitToUL(EEPROMSaveOptions, EEPROM_SAVEOPTIONS_DELAY, seconds); -} #endif // if FEATURE_EEPROM_EXTERNAL #if FEATURE_I2CMULTIPLEXER diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index 025bf9a842..6a4d9288dc 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -35,10 +35,6 @@ #include "../Helpers/StringGenerator_System.h" #include "../WebServer/ESPEasy_WebServer.h" -#if FEATURE_EEPROM_EXTERNAL -#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#endif // if FEATURE_EEPROM_EXTERNAL - #ifdef USE_RTOS_MULTITASKING # include "../Helpers/Networking.h" # include "../Helpers/PeriodicalActions.h" @@ -404,15 +400,6 @@ void ESPEasy_setup() logMemUsageAfter(F("hardwareInit()")); #endif // ifndef BUILD_NO_RAM_TRACKER - #if FEATURE_EEPROM_EXTERNAL - if ((ESPEasy::eeprom::checkEEPROMEnabled() > 0) && - (((lastBootCause <= BOOT_CAUSE_COLD_BOOT) && Settings.RestoreUserVarsFromEEPROMOnColdBoot()) || - ((lastBootCause <= BOOT_CAUSE_SOFT_RESTART) && Settings.RestoreUserVarsFromEEPROMOnWarmBoot())) - ) { - readUserVarFromRTC(); // Once more to fetch UserVar data from now available EEPROM/FRAM - } - #endif // if FEATURE_EEPROM_EXTERNAL - node_time.restoreFromRTC(); Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index ca3303d5a2..a73153487d 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -12,16 +12,6 @@ #include "../Helpers/CRC_functions.h" #include "../../ESPEasy_common.h" -#if FEATURE_EEPROM_EXTERNAL -#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#include "../Globals/Cache.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Hardware_I2C.h" -#include "../Helpers/StringConverter.h" - -uint8_t readDataForUserVars(size_t index); // Forward declaration -#endif // if FEATURE_EEPROM_EXTERNAL - #ifdef ESP8266 #include #endif @@ -171,49 +161,6 @@ bool readFromRTC() return RTC.ID1 == 0xAA && RTC.ID2 == 0x55; } -#if FEATURE_EEPROM_EXTERNAL -uint32_t saveUserVarToEEPROM() { - uint32_t eepromWritten{}; - // Update system parameters if not correct - const bool paramsOk = ESPEasy::eeprom::validateEEPROMExternalParameters(); - - if (!paramsOk) { - ESPEasy::eeprom::updateEEPROMExternalParameters(); - } - - for (taskIndex_t task = 0; task < TASKS_MAX; ++task) { - if (Settings.TaskDeviceEnabled[task] || !paramsOk) { // Only check enabled tasks or when re-writing the params - const TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(task); - if (taskValues != nullptr) { - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - const bool storeValue = Cache.getTaskVarStoreInEEPROM(task, varNr); - if (!paramsOk || storeValue) { - const uint32_t newData = storeValue - ? taskValues->getUint32(varNr) - : std::numeric_limits::max(); // NaN when read as float - const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(task, varNr); - if (newData != ESPEasy::eeprom::EEPROMExternal->readLong(addr)) { // Only update EEPROM if data differs - ESPEasy::eeprom::EEPROMExternal->writeLong(addr, newData); - eepromWritten += sizeof_uint32_t; - } - } - } - } - } - } - - if (eepromWritten) { // Did we change anything? Only then do the slow operation of CRC calculation by reading all bytes - // Calculate checksum for all stored values, not equal to the UserVar checksum! - const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); - if (calcsum != ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET)) { - ESPEasy::eeprom::EEPROMExternal->writeLong(EEPROM_USERVAR_CHECKSUM_OFFSET, calcsum); - eepromWritten += sizeof_uint32_t; - } - } - return eepromWritten; -} -#endif // if FEATURE_EEPROM_EXTERNAL - /********************************************************************************************\ Save values to RTC memory \*********************************************************************************************/ @@ -223,47 +170,6 @@ bool saveUserVarToRTC() { bool saveUserVarToRTC(bool initial) { - #if FEATURE_EEPROM_EXTERNAL - // Check if we have an external EEPROM available on the configured I2C bus & channel, and save all task values there - const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); - if (!initial && (eepromAddress > 0) && !ESPEasy::eeprom::isEEPROMExternalWriteProtected()) { // EEPROM Configured and writable? - - if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM - #if FEATURE_EEPROM_BACKGROUND - ESPEasy::eeprom::EEPROMExternalTaskData taskdata; - taskdata.type = ESPEasy::eeprom::EEPROMExternalTaskType_e::UserVars; - taskdata.function = saveUserVarToEEPROM; - ESPEasy::eeprom::EEPROMAddTask(ESPEasy::eeprom::EEPROMExternalTaskType_e::UserVars, taskdata); - #else // if FEATURE_EEPROM_BACKGROUND - #ifndef BUILD_NO_DEBUG - uint32_t startmicros = micros(); - const uint32_t eepromWritten = - #endif - saveUserVarToEEPROM(); - #ifndef BUILD_NO_DEBUG - startmicros = micros() - startmicros; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { // FIXME LOG_LEVEL_DEBUG - const ESPEasy::eeprom::EEPROMExternal_Type_e eepromType = - static_cast(Settings.EEPROMExternalType()); - addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: UserVar: %u bytes (%.2f ms) written to %s"), - eepromWritten, startmicros / 1000.0f, FsP(ESPEasy::eeprom::getEEPROMName(eepromType)))); - } - #endif // ifndef BUILD_NO_DEBUG - #endif // if FEATURE_EEPROM_BACKGROUND - } - #if FEATURE_I2CMULTIPLEXER - I2CMultiplexerOff( - #if FEATURE_I2C_MULTIPLE - Settings.getI2CInterfaceEEPROM() - #else //if FEATURE_I2C_MULTIPLE - 0 - #endif // if FEATURE_I2C_MULTIPLE - ); // Restore the Multiplexer channel - #endif // if FEATURE_I2CMULTIPLEXER - } - #endif // if FEATURE_EEPROM_EXTERNAL - // ESP8266 has the RTC struct stored in memory which we must actively fetch // ESP32 Uses a temp structure which is mapped to the RTC address range. #if defined(ESP32) @@ -291,76 +197,11 @@ bool saveUserVarToRTC(bool initial) #endif } -#if FEATURE_EEPROM_EXTERNAL -uint8_t readDataForUserVars(size_t index) { - if (nullptr != ESPEasy::eeprom::EEPROMExternal) { - return ESPEasy::eeprom::EEPROMExternal->read(EEPROM_USERVAR_START_OFFSET + index); - } - return 0; -} -#endif // if FEATURE_EEPROM_EXTERNAL - /********************************************************************************************\ Read RTC struct from RTC memory \*********************************************************************************************/ bool readUserVarFromRTC() { - #if FEATURE_EEPROM_EXTERNAL - const uint8_t eepromAddress = ESPEasy::eeprom::checkEEPROMEnabled(); - if (eepromAddress > 0) { // EEPROM Configured and restoring of Task Values enabled? - bool result = false; - - if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM - // Check system parameters with last stored values - const bool eepromParamsOK = ESPEasy::eeprom::validateEEPROMExternalParameters(true); - // Check checksum and if correct, restore UserVar values - const uint32_t checksum = ESPEasy::eeprom::EEPROMExternal->readLong(EEPROM_USERVAR_CHECKSUM_OFFSET); - const uint32_t calcsum = calc_CRC32(readDataForUserVars, TASKS_MAX * VARS_PER_TASK * sizeof_uint32_t); - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { // FIXME LOG_LEVEL_DEBUG - addLog(LOG_LEVEL_INFO, strformat(F("EEPROM: readUserVarFromRTC calculated: %u, expected: %u equal: %c, params: %c"), - calcsum, checksum, calcsum == checksum ? 'Y' : 'n', eepromParamsOK ? 'Y' : 'n')); - } - #endif // ifndef BUILD_NO_DEBUG - if (eepromParamsOK && (calcsum == checksum)) { - addLog(LOG_LEVEL_INFO, F("INIT : Restoring Task Values from EEPROM.")); - result = true; - taskIndex_t lastTask = TASKS_MAX; - for (size_t i = 0; i < (TASKS_MAX * VARS_PER_TASK) && result; ++i) { - const taskIndex_t taskIndex = i / VARS_PER_TASK; - const uint8_t varNr = i % VARS_PER_TASK; - if (taskIndex != lastTask) { - LoadTaskSettings(taskIndex); - lastTask = taskIndex; - } - // Store in raw form, so we don't apply formula as we don't really know what type is required. - const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(taskIndex, varNr); - if (addr != std::numeric_limits::max()) { - if (Cache.getTaskVarStoreInEEPROM(taskIndex, varNr)) { // Restore only when enabled - TaskValues_Data_t* taskValues = UserVar.getRawTaskValues_Data(taskIndex); - taskValues->setUint32(varNr, ESPEasy::eeprom::EEPROMExternal->readLong(addr)); - } - } else { - result = false; - } - } - } - } - #if FEATURE_I2CMULTIPLEXER - I2CMultiplexerOff( - #if FEATURE_I2C_MULTIPLE - Settings.getI2CInterfaceEEPROM() - #else //if FEATURE_I2C_MULTIPLE - 0 - #endif // if FEATURE_I2C_MULTIPLE - ); // Restore the Multiplexer channel - #endif // if FEATURE_I2CMULTIPLEXER - if (result) { - return true; // We did all that was needed - } - } - #endif // if FEATURE_EEPROM_EXTERNAL - // ESP8266 has the RTC struct stored in memory which we must actively fetch // ESP32 Uses a temp structure which is mapped to the RTC address range. #if defined(ESP32) diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index 97713d4d3c..12c4659766 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -47,10 +47,6 @@ #include "../DataStructs/NotificationSettingsStruct.h" #endif // if FEATURE_NOTIFIER -#if FEATURE_EEPROM_EXTERNAL -#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#endif // if FEATURE_EEPROM_EXTERNAL - // ******************************************************************************** // Check struct sizes at compile time // Usage: @@ -193,11 +189,6 @@ void run_compiletime_checks() { // to determine nr of bits in a struct. static_assert(GPIO_DIRECTION_NR_BITS== NR_BITS(static_cast(gpio_direction::gpio_direction_MAX)), "Correct GPIO_DIRECTION_NR_BITS"); - #if FEATURE_EEPROM_EXTERNAL - static_assert(EEPROM_CUSTOM_START_OFFSET > EEPROM_GPIO_PINSTATE_END_OFFSET, "EEPROM GPIO Pinstates overlap with Slots values!"); - #endif // if FEATURE_EEPROM_EXTERNAL - - #endif // ifndef LIMIT_BUILD_SIZE } diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index c7daef17de..d31ec79f1b 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -45,10 +45,6 @@ #include "../../ESPEasy_fdwdecl.h" #endif -#if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND -#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#endif // if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND - #define PLUGIN_ID_MQTT_IMPORT 37 @@ -209,10 +205,6 @@ void runOncePerSecond() PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummy); // unsigned long elapsed = micros() - start; - #if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND - ESPEasy::eeprom::EEPROMExternalLoop(); - #endif // if FEATURE_EEPROM_EXTERNAL && FEATURE_EEPROM_BACKGROUND - #if FEATURE_NETWORK_STATS for (ESPEasy::net::networkIndex_t x = 0; x < NETWORK_MAX; x++) { if (Settings.getNetworkEnabled(x)) { diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index 86c95fb38c..26a1cf542d 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -209,7 +209,11 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us uint32_t slot{}; String value; if (validUIntFromString(valueName, slot)) { + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = doubleToString(ESPEasy::eeprom::readEEPROMSlot(slot)); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE value = toString(ESPEasy::eeprom::readEEPROMSlot(slot)); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (valueName.equalsIgnoreCase(F("max"))) { value = ESPEasy::eeprom::getEEPROMMaxSlots(); } else if (valueName.equalsIgnoreCase(F("wp"))) { diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index ddaeeebe25..5103e950e6 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -484,21 +484,6 @@ KeyValueStruct getKeyValue(LabelType::Enum label, bool extendedValue) } #endif // if FEATURE_MQTT_CONNECT_BACKGROUND - #if FEATURE_EEPROM_EXTERNAL - case LabelType::EEPROM_RESTORE_ON_COLDBOOT: - { - return KeyValueStruct(F("Cold boot: Restore Task Values from EEPROM"), Settings.RestoreUserVarsFromEEPROMOnColdBoot()); - } - case LabelType::EEPROM_RESTORE_ON_WARMBOOT: - { - return KeyValueStruct(F("Warm boot: Restore Task Values from EEPROM"), Settings.RestoreUserVarsFromEEPROMOnWarmBoot()); - } - case LabelType::EEPROM_SAVE_DELAY: - { - return KeyValueStruct(F("Save interval (seconds)"), Settings.EEPROMSaveDelaySeconds()); - } - #endif // if FEATURE_EEPROM_EXTERNAL - #if CONFIG_SOC_WIFI_SUPPORT_5G case LabelType::WIFI_BAND_MODE: { diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index 42224c1849..1594f63c95 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -113,12 +113,6 @@ struct LabelType { MQTT_CONNECT_IN_BACKGROUND, #endif // if FEATURE_MQTT_CONNECT_BACKGROUND - #if FEATURE_EEPROM_EXTERNAL - EEPROM_RESTORE_ON_COLDBOOT, - EEPROM_RESTORE_ON_WARMBOOT, - EEPROM_SAVE_DELAY, - #endif // if FEATURE_EEPROM_EXTERNAL - BOOT_TYPE, // Cold boot BOOT_COUNT, // 0 RESET_REASON, // Software/System restart diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 140a89c5a9..048ea21cea 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -29,9 +29,6 @@ # include "../Helpers/_Plugin_init.h" # include "../Helpers/_Plugin_SensorTypeHelper.h" # include "../Helpers/_Plugin_Helper_serial.h" -#if FEATURE_EEPROM_EXTERNAL -# include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#endif // if FEATURE_EEPROM_EXTERNAL # include "../Helpers/ESPEasy_Storage.h" # include "../Helpers/I2C_Plugin_Helper.h" # include "../Helpers/SPI_Helper.h" @@ -456,11 +453,6 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task # if FEATURE_PLUGIN_FILTER ExtraTaskSettings.enablePluginFilter(varNr, isFormItemChecked(getPluginCustomArgName(F("TDFIL"), varNr))); # endif // if FEATURE_PLUGIN_FILTER - #if FEATURE_EEPROM_EXTERNAL - if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { - ExtraTaskSettings.setTaskVarStoreInEEPROM(varNr, isFormItemChecked(getPluginCustomArgName(F("TDEE"), varNr))); - } - #endif // if FEATURE_EEPROM_EXTERNAL # if FEATURE_PLUGIN_STATS PluginStats_Config_t pluginStats_Config; @@ -1844,13 +1836,6 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde ++colCount; } - #if FEATURE_EEPROM_EXTERNAL - if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { - html_table_header(F("Eeprom"), 30); - ++colCount; - } - #endif // if FEATURE_EEPROM_EXTERNAL - # if FEATURE_PLUGIN_STATS if (device.PluginStats) @@ -1990,19 +1975,6 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); } - #if FEATURE_EEPROM_EXTERNAL - if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDEE"), varNr), - Cache.getTaskVarStoreInEEPROM(taskIndex, varNr), false - #if FEATURE_TOOLTIPS - , F("Store value in External EEPROM") - #endif // if FEATURE_TOOLTIPS - ); - } - #endif // if FEATURE_EEPROM_EXTERNAL - # if FEATURE_PLUGIN_STATS if (device.PluginStats) diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp index 6f0869d07e..cd3d3ead3c 100644 --- a/src/src/WebServer/EpromVarPage.cpp +++ b/src/src/WebServer/EpromVarPage.cpp @@ -15,18 +15,11 @@ #include "../Helpers/StringConverter.h" -// #if FEATURE_STRING_VARIABLES -// # include "../Helpers/StringParser.h" -// #endif // if - #if FEATURE_EEPROM_EXTERNAL void handle_eepromvars() { if (!isLoggedIn()) { return; } - const bool showTasks = getFormItemInt(F("tasks"), 1) == 1; - const bool allTasks = getFormItemInt(F("enabled"), 0) != 0; - TXBuffer.startStream(); sendHeadandTail_stdtemplate(_HEAD); @@ -42,52 +35,6 @@ void handle_eepromvars() { 400); html_table_header(F("")); - if (showTasks) { - html_TR(); - - // sub-table header - html_table_header(allTasks ? F("Task") : F("Task (Enabled)"), 300); - html_table_header(F("Value"), 500); - html_table_header(F("Content"), 400); - html_table_header(F("")); - - for (taskIndex_t tsk = 0; tsk < TASKS_MAX; ++tsk) { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(tsk); - - if ((Settings.TaskDeviceEnabled[tsk] && validDeviceIndex(DeviceIndex)) || allTasks) { - LoadTaskSettings(tsk); - html_TR_TD(); - addHtmlInt(tsk + 1); - addHtml(' '); - addHtml(getTaskDeviceName(tsk)); - - for (taskVarIndex_t var = 0; var < VARS_PER_TASK; ++var) { - if (var != 0) { - html_TR_TD(); - } - html_TD(); - addHtmlInt(var + 1); - addHtml(' '); - addHtml(getTaskValueName(tsk, var)); - html_TD(); - const uint32_t addr = ESPEasy::eeprom::getEEPROMAddressForTaskValue(tsk, var); - const float value = ESPEasy::eeprom::EEPROMExternal->readFloat(addr); - const uint32_t data = ESPEasy::eeprom::EEPROMExternal->readLong(addr); - - if (isnan(value) || (addr == std::numeric_limits::max()) || !ExtraTaskSettings.getTaskVarStoreInEEPROM(var)) { - addHtml('-'); - } else { - addHtml(floatToString(value, ExtraTaskSettings.TaskDeviceValueDecimals[var])); - - if (!essentiallyZero(value)) { // Only show hex values for non-zero values to reduce eye-strain - addHtml(strformat(F(" (0x%04x)"), data)); - } - } - } - delay(0); - } - } - } html_TR(); // sub-table header @@ -109,14 +56,15 @@ void handle_eepromvars() { html_TR_TD(); addHtmlInt(slot); html_TD(); + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + addHtml(doubleToString(value)); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE addHtml(toString(value)); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE html_TD(2); } } - // TODO: List String values in EEPROM - // TODO: List C016 Cache entries in EEPROM - addTableSeparator(F("Summary"), 3, 2); html_TR_TD(); @@ -137,8 +85,6 @@ void handle_eepromvars() { } html_TD(); - // TODO: List String summary in EEPROM - html_end_table(); } else { addHtml(F("External EEPROM not enabled.")); diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index c7b8c541b7..dcb0203f92 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -65,9 +65,6 @@ void handle_hardware() { Settings.EEPROMExternalType(getFormItemInt(F("eepromtype"), static_cast(ESPEasy::eeprom::EEPROMExternal_Type_e::AT24C256))); Settings.EEPROMExternalI2CAddress(getFormItemInt(F("i2c_eeprom"), 0)); - Settings.RestoreUserVarsFromEEPROMOnColdBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_COLDBOOT)); - Settings.RestoreUserVarsFromEEPROMOnWarmBoot(isFormItemChecked(LabelType::EEPROM_RESTORE_ON_WARMBOOT)); - Settings.EEPROMSaveDelaySeconds(getFormItemInt(LabelType::EEPROM_SAVE_DELAY)); # if FEATURE_I2CMULTIPLEXER @@ -228,9 +225,6 @@ void handle_hardware() { addRowLabel(F("'WriteEE' slots available")); addHtmlInt(ESPEasy::eeprom::getEEPROMMaxSlots()); } - addFormCheckBox(getLabel(LabelType::EEPROM_RESTORE_ON_COLDBOOT), getInternalLabel(LabelType::EEPROM_RESTORE_ON_COLDBOOT), Settings.RestoreUserVarsFromEEPROMOnColdBoot() && eepromChecked, !eepromChecked); - addFormCheckBox(getLabel(LabelType::EEPROM_RESTORE_ON_WARMBOOT), getInternalLabel(LabelType::EEPROM_RESTORE_ON_WARMBOOT), Settings.RestoreUserVarsFromEEPROMOnWarmBoot() && eepromChecked, !eepromChecked); - addFormNumericBox(getLabel(LabelType::EEPROM_SAVE_DELAY), getInternalLabel(LabelType::EEPROM_SAVE_DELAY), Settings.EEPROMSaveDelaySeconds(), 0, 250); } #endif // if FEATURE_EEPROM_EXTERNAL From 86e15ccb41466d7b7f9d300b1f0ee70dfd6d411d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 10 Aug 2026 23:16:17 +0200 Subject: [PATCH 30/44] [Storage] Add support for WriteRTC.../[ReadRTC#...] storing values in RTC SRAM --- lib/Adafruit_RTClib/src/RTClib.cpp | 40 +++ lib/Adafruit_RTClib/src/RTClib.h | 3 + src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp | 36 +-- src/ESPEasy/eeprom/Helpers/EEPROMExternal.h | 13 +- src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp | 234 ++++++++++++++++++ src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.h | 30 +++ src/src/Commands/EEPROMExternal.cpp | 18 +- src/src/Commands/InternalCommands.cpp | 6 + src/src/Commands/InternalCommands_decoder.cpp | 3 + src/src/Commands/InternalCommands_decoder.h | 3 + src/src/Commands/RTCSRAMStorage.cpp | 47 ++++ src/src/Commands/RTCSRAMStorage.h | 8 + src/src/CustomBuild/define_plugin_sets.h | 22 ++ src/src/DataStructs/ExtraTaskSettingsStruct.h | 1 - src/src/DataTypes/TimeSource.cpp | 1 + src/src/DataTypes/TimeSource.h | 3 +- src/src/Helpers/ESPEasyRTC.cpp | 14 +- src/src/Helpers/ESPEasyRTC.h | 1 - src/src/Helpers/ESPEasy_time.cpp | 2 + src/src/Helpers/StringParser.cpp | 32 +++ src/src/WebServer/AdvancedConfigPage.cpp | 3 +- src/src/WebServer/ESPEasy_WebServer.cpp | 4 +- src/src/WebServer/EepromVarPage.h | 4 +- src/src/WebServer/EpromVarPage.cpp | 110 ++++++-- src/src/WebServer/ToolsPage.cpp | 6 +- static/espeasy.js | 2 + static/espeasy.min.js | 2 +- 27 files changed, 560 insertions(+), 88 deletions(-) create mode 100644 src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp create mode 100644 src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.h create mode 100644 src/src/Commands/RTCSRAMStorage.cpp create mode 100644 src/src/Commands/RTCSRAMStorage.h diff --git a/lib/Adafruit_RTClib/src/RTClib.cpp b/lib/Adafruit_RTClib/src/RTClib.cpp index 22a83ced95..e465752c2c 100644 --- a/lib/Adafruit_RTClib/src/RTClib.cpp +++ b/lib/Adafruit_RTClib/src/RTClib.cpp @@ -1864,3 +1864,43 @@ bool RTC_DS3231::isEnabled32K(void) { read_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG, RTCWireBus); return (status >> 0x03) & 0x1; } + +/**************************************************************************/ +/*! + @brief Read data from the DS3232's NVRAM + @param buf Pointer to a buffer to store the data - make sure it's large + enough to hold size bytes + @param size Number of bytes to read + @param address Starting NVRAM address, from 0 to 235 +*/ +/**************************************************************************/ +void RTC_DS3231::readnvram(uint8_t *buf, uint8_t size, uint8_t address) { + int addrByte = DS3232_NVRAM + address; + RTCWireBus->beginTransmission(DS3231_ADDRESS); + RTCWireBus->_I2C_WRITE(addrByte); + RTCWireBus->endTransmission(); + + RTCWireBus->requestFrom((uint8_t)DS3231_ADDRESS, size); + for (uint8_t pos = 0; pos < size; ++pos) { + buf[pos] = RTCWireBus->_I2C_READ(); + } +} + +/**************************************************************************/ +/*! + @brief Write data to the DS3232 NVRAM + @param address Starting NVRAM address, from 0 to 235 + @param buf Pointer to buffer containing the data to write + @param size Number of bytes in buf to write to NVRAM +*/ +/**************************************************************************/ +void RTC_DS3231::writenvram(uint8_t address, uint8_t *buf, uint8_t size) { + int addrByte = DS3232_NVRAM + address; + RTCWireBus->beginTransmission(DS3231_ADDRESS); + RTCWireBus->_I2C_WRITE(addrByte); + for (uint8_t pos = 0; pos < size; ++pos) { + RTCWireBus->_I2C_WRITE(buf[pos]); + } + RTCWireBus->endTransmission(); +} + diff --git a/lib/Adafruit_RTClib/src/RTClib.h b/lib/Adafruit_RTClib/src/RTClib.h index cf696ad90e..6642b25f94 100644 --- a/lib/Adafruit_RTClib/src/RTClib.h +++ b/lib/Adafruit_RTClib/src/RTClib.h @@ -57,6 +57,7 @@ class TimeSpan; #define DS3231_TEMPERATUREREG \ 0x11 ///< Temperature register (high byte - low byte is at 0x12), 10-bit ///< temperature value +#define DS3232_NVRAM 0x14 ///< Start of RAM registers - 236 bytes, 0x14 to 0xFF /** Constants */ #define SECONDS_PER_DAY 86400L ///< 60 * 60 * 24 @@ -347,6 +348,8 @@ class RTC_DS3231 { void disable32K(void); bool isEnabled32K(void); float getTemperature(); // in Celsius degree + void readnvram(uint8_t *buf, uint8_t size, uint8_t address); + void writenvram(uint8_t address, uint8_t *buf, uint8_t size); protected: TwoWire *RTCWireBus; diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp index 0e6d06ce5d..0a43f5918c 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp @@ -356,24 +356,13 @@ uint32_t getEEPROMMaxSlots() { /** * EEPROM write value to slot if the slot is valid */ -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - -bool writeEEPROMSlot(uint32_t slot, - double data) -# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - -bool writeEEPROMSlot(uint32_t slot, - float data) -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +bool writeEEPROMSlot(uint32_t slot, + ESPEASY_RULES_FLOAT_TYPE data) { const uint32_t addr = getEEPROMAddressForSlot(slot); if ((addr != std::numeric_limits::max()) && !isEEPROMExternalWriteProtected()) { - # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - const double oldData = EEPROMExternal->readDouble(addr); - # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - const float oldData = EEPROMExternal->readDouble(addr); - # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + const ESPEASY_RULES_FLOAT_TYPE oldData = EEPROMExternal->readDouble(addr); if (!essentiallyEqual(oldData, data)) { EEPROMExternal->writeDouble(addr, data); // Always write double size! @@ -386,30 +375,19 @@ bool writeEEPROMSlot(uint32_t slot, /** * EEPROM read value from slot or 0 when invalid */ -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - -double readEEPROMSlot(uint32_t slot) { +ESPEASY_RULES_FLOAT_TYPE readEEPROMSlot(uint32_t slot) { const uint32_t addr = getEEPROMAddressForSlot(slot); if (addr != std::numeric_limits::max()) { return EEPROMExternal->readDouble(addr); } + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return 0.0; -} - -# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - -float readEEPROMSlot(uint32_t slot) { - const uint32_t addr = getEEPROMAddressForSlot(slot); - - if (addr != std::numeric_limits::max()) { - return EEPROMExternal->readDouble(addr); // Always read double size! - } + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return 0.0f; + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } // namespace eeprom } // namespace ESPEasy #endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h index 634f0bc34d..7c8dd371e2 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.h @@ -86,15 +86,10 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type); uint32_t getEEPROMAddressForSlot(uint32_t slot); uint32_t getEEPROMMaxSlots(); -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -bool writeEEPROMSlot(uint32_t slot, - double data); -double readEEPROMSlot(uint32_t slot); -# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -bool writeEEPROMSlot(uint32_t slot, - float data); -float readEEPROMSlot(uint32_t slot); -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + +bool writeEEPROMSlot(uint32_t slot, + ESPEASY_RULES_FLOAT_TYPE data); +ESPEASY_RULES_FLOAT_TYPE readEEPROMSlot(uint32_t slot); } // namespace eeprom } // namespace ESPEasy diff --git a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp new file mode 100644 index 0000000000..484171bbf4 --- /dev/null +++ b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp @@ -0,0 +1,234 @@ +#include "../Helpers/RTCSRAMStorage.h" +#include "../../../src/Globals/Settings.h" +#include "../../../src/Helpers/I2C_access.h" +#include "../../../ESPEasy_common.h" +#include "../../../src/Helpers/StringConverter.h" +#include "../../../src/DataTypes/TimeSource.h" +#include + +#if FEATURE_RTC_SRAM_STORAGE + +namespace ESPEasy { +namespace eeprom { + +# if FEATURE_SRAM_STORAGE_DOUBLE +constexpr uint32_t sizeof_rtcsram_slot = sizeof(double); +# else // if FEATURE_SRAM_STORAGE_DOUBLE +constexpr uint32_t sizeof_rtcsram_slot = sizeof(float); +# endif // if FEATURE_SRAM_STORAGE_DOUBLE + +/** + * Check if the RTC is properly initialized and enabled. + * Returns the external rtc type if all is OK + */ +uint8_t checkRTCSRAMEnabled() { + const ExtTimeSource_e extRtcType = static_cast(Settings.ExternalTimeSource); + + if ((ExtTimeSource_e::DS1307 == extRtcType) || + (ExtTimeSource_e::DS3232 == extRtcType)) { // RTC with SRAM Configured? + return Settings.ExternalTimeSource; + } + return 0; +} + +/** + * Get the RTC I2C Address + */ +uint8_t getRTCI2CAddress() { + const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + + switch (type) + { + case ExtTimeSource_e::DS1307: + return DS1307_ADDRESS; + case ExtTimeSource_e::DS3232: + return DS3231_ADDRESS; + case ExtTimeSource_e::DS3231: + case ExtTimeSource_e::PCF8523: + case ExtTimeSource_e::PCF8563: + case ExtTimeSource_e::None: + return 0; + } + return 0; + +} + +/** + * Switch to I2C Bus and multiplexer channel of External EEPROM + */ +uint8_t selectRTCSRAMI2CBus() { + const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + + if (checkRTCSRAMEnabled() > 0) { // RTC Module Configured? + # if FEATURE_I2C_MULTIPLE + const uint8_t i2cBus = Settings.getI2CInterfaceRTC(); + # else // if FEATURE_I2C_MULTIPLE + constexpr uint8_t i2cBus = 0; + # endif // if FEATURE_I2C_MULTIPLE + + switch (type) + { + case ExtTimeSource_e::DS1307: + I2CSelect_Max100kHz_ClockSpeed(i2cBus); + break; + case ExtTimeSource_e::DS3232: + I2CSelectHighClockSpeed(i2cBus); + break; + case ExtTimeSource_e::DS3231: + case ExtTimeSource_e::PCF8523: + case ExtTimeSource_e::PCF8563: + case ExtTimeSource_e::None: + break; + } + + if (0 == I2C_wakeup(getRTCI2CAddress())) { + return Settings.ExternalTimeSource; + } + } + return 0; +} + +/** + * RTC SRAM size in bytes + */ +uint32_t getRTCSRAMSize() { + const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + + switch (type) + { + case ExtTimeSource_e::DS1307: + return 56ul; + case ExtTimeSource_e::DS3232: + return 240ul; + case ExtTimeSource_e::DS3231: + case ExtTimeSource_e::PCF8523: + case ExtTimeSource_e::PCF8563: + case ExtTimeSource_e::None: + return 0; + } + return 0; +} + +/** + * RTC SRAM _relative_ address for slot or 0xFFFF when error + */ +uint32_t getRTCSRAMAddressForSlot(uint32_t slot) { + if (checkRTCSRAMEnabled() > 0) { + const uint32_t rtcSramSize = getRTCSRAMSize(); + + if ((rtcSramSize > 0) && (slot < getRTCSRAMMaxSlots())) { + const uint32_t slotAddr = slot * sizeof_rtcsram_slot; + + if (slotAddr < rtcSramSize) { + return slotAddr; + } + } + } + return std::numeric_limits::max(); +} + +/** + * EEPROM available number of slots, max use all available space minus some administrative bytes + */ +uint32_t getRTCSRAMMaxSlots() { + if (checkRTCSRAMEnabled() > 0) { + const uint32_t rtcSramSize = getRTCSRAMSize(); + + if (rtcSramSize > 0) { + const uint32_t slotMax = (uint32_t)(rtcSramSize / sizeof_rtcsram_slot); + + return slotMax; + } + } + return 0; +} + +/** + * EEPROM write value to slot if the slot is valid + */ +bool writeRTCSRAMSlot(uint32_t slot, + SRAM_STORAGE_FLOAT_TYPE data) +{ + const uint32_t addr = getRTCSRAMAddressForSlot(slot); + + if ((addr != std::numeric_limits::max()) && (selectRTCSRAMI2CBus() > 0)) { + const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + uint8_t _b[sizeof_rtcsram_slot]{}; + + switch (type) + { + case ExtTimeSource_e::DS1307: + { + RTC_DS1307 rtc; + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); + const SRAM_STORAGE_FLOAT_TYPE oldData = *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; + + if (!essentiallyEqual(oldData, data)) { + rtc.writenvram(addr, (uint8_t *)&data, sizeof_rtcsram_slot); + } + return true; + } + case ExtTimeSource_e::DS3232: + { + RTC_DS3231 rtc; + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); + const SRAM_STORAGE_FLOAT_TYPE oldData = *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; + + if (!essentiallyEqual(oldData, data)) { + rtc.writenvram(addr, (uint8_t *)&data, sizeof_rtcsram_slot); + } + return true; + } + case ExtTimeSource_e::DS3231: + case ExtTimeSource_e::PCF8523: + case ExtTimeSource_e::PCF8563: + case ExtTimeSource_e::None: + return false; + } + + } + return false; +} + +/** + * RTC SRAM read value from slot or 0 when invalid + */ +SRAM_STORAGE_FLOAT_TYPE readRTCSRAMSlot(uint32_t slot) { + const uint32_t addr = getRTCSRAMAddressForSlot(slot); + + if ((addr != std::numeric_limits::max()) && (selectRTCSRAMI2CBus() > 0)) { + const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + uint8_t _b[sizeof_rtcsram_slot]{}; + + switch (type) + { + case ExtTimeSource_e::DS1307: + { + RTC_DS1307 rtc; + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); + return *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; + } + case ExtTimeSource_e::DS3232: + { + RTC_DS3231 rtc; + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); + return *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; + } + case ExtTimeSource_e::DS3231: + case ExtTimeSource_e::PCF8523: + case ExtTimeSource_e::PCF8563: + case ExtTimeSource_e::None: + break; + } + + } + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return 0.0; + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + return 0.0f; + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +} + +} // namespace eeprom +} // namespace ESPEasy +#endif // if FEATURE_RTC_SRAM_STORAGE diff --git a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.h b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.h new file mode 100644 index 0000000000..58ced1e4e8 --- /dev/null +++ b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.h @@ -0,0 +1,30 @@ +#pragma once +#include "../../../ESPEasy_common.h" + +#if FEATURE_RTC_SRAM_STORAGE + +# include "../../../src/DataTypes/TaskIndex.h" + +namespace ESPEasy { +namespace eeprom { + +# if FEATURE_SRAM_STORAGE_DOUBLE + # define SRAM_STORAGE_FLOAT_TYPE double +# else + # define SRAM_STORAGE_FLOAT_TYPE float +# endif // if FEATURE_SRAM_STORAGE_DOUBLE + +uint8_t checkRTCSRAMEnabled(); +uint8_t selectRTCSRAMI2CBus(); + +uint32_t getRTCSRAMSize(); +uint32_t getRTCSRAMAddressForSlot(uint32_t slot); +uint32_t getRTCSRAMMaxSlots(); + +bool writeRTCSRAMSlot(uint32_t slot, + SRAM_STORAGE_FLOAT_TYPE data); +SRAM_STORAGE_FLOAT_TYPE readRTCSRAMSlot(uint32_t slot); + +} // namespace eeprom +} // namespace ESPEasy +#endif // if FEATURE_RTC_SRAM_STORAGE diff --git a/src/src/Commands/EEPROMExternal.cpp b/src/src/Commands/EEPROMExternal.cpp index da332d51df..8a7af26976 100644 --- a/src/src/Commands/EEPROMExternal.cpp +++ b/src/src/Commands/EEPROMExternal.cpp @@ -1,20 +1,20 @@ #include "../Commands/EEPROMExternal.h" -#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" - -#include "../../ESPEasy_common.h" +#if FEATURE_EEPROM_EXTERNAL +# include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#include "../Commands/Common.h" +# include "../../ESPEasy_common.h" -#include "../DataStructs/ESPEasy_EventStruct.h" +# include "../Commands/Common.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" +# include "../DataStructs/ESPEasy_EventStruct.h" -#if FEATURE_EEPROM_EXTERNAL +# include "../Helpers/Misc.h" +# include "../Helpers/Numerical.h" +# include "../Helpers/StringConverter.h" // Command: WriteEE,, : set a slot value. 0 is 'erased' // Command: WriteEE,erase,erase : reset all slots to 0 +// Command: WriteEE,check,wp : check if external EEPROM is write-protected const __FlashStringHelper* Command_writeEE(struct EventStruct *event, const char *Line) { uint32_t slot{}; diff --git a/src/src/Commands/InternalCommands.cpp b/src/src/Commands/InternalCommands.cpp index 31bdb6bbba..a23a711184 100644 --- a/src/src/Commands/InternalCommands.cpp +++ b/src/src/Commands/InternalCommands.cpp @@ -14,6 +14,9 @@ #if FEATURE_EEPROM_EXTERNAL #include "../Commands/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL +#if FEATURE_RTC_SRAM_STORAGE +#include "../Commands/RTCSRAMStorage.h" +#endif // if FEATURE_RTC_SRAM_STORAGE #include "../Commands/GPIO.h" #include "../Commands/HTTP.h" #include "../Commands/InternalCommands_decoder.h" @@ -518,6 +521,9 @@ bool InternalCommands::executeInternalCommand() #if FEATURE_EEPROM_EXTERNAL case ESPEasy_cmd_e::writeee: COMMAND_CASE_R(Command_writeEE, 2); // EEPROMExternal.h #endif // if FEATURE_EEPROM_EXTERNAL +#if FEATURE_RTC_SRAM_STORAGE + case ESPEasy_cmd_e::writertc: COMMAND_CASE_R(Command_writeRTC, 2); // RTCSRAMStorage.h +#endif // if FEATURE_RTC_SRAM_STORAGE case ESPEasy_cmd_e::NotMatched: diff --git a/src/src/Commands/InternalCommands_decoder.cpp b/src/src/Commands/InternalCommands_decoder.cpp index 52613a6b70..59a8d9ee9c 100644 --- a/src/src/Commands/InternalCommands_decoder.cpp +++ b/src/src/Commands/InternalCommands_decoder.cpp @@ -320,6 +320,9 @@ const char Internal_commands_w[] PROGMEM = #if FEATURE_EEPROM_EXTERNAL "writeee|" #endif // if FEATURE_EEPROM_EXTERNAL +#if FEATURE_RTC_SRAM_STORAGE + "writertc|" +#endif // if FEATURE_RTC_SRAM_STORAGE ; #endif diff --git a/src/src/Commands/InternalCommands_decoder.h b/src/src/Commands/InternalCommands_decoder.h index b908d1dc7e..0130dcb2f8 100644 --- a/src/src/Commands/InternalCommands_decoder.h +++ b/src/src/Commands/InternalCommands_decoder.h @@ -261,6 +261,9 @@ enum class ESPEasy_cmd_e : uint8_t { #if FEATURE_EEPROM_EXTERNAL writeee, #endif // if FEATURE_EEPROM_EXTERNAL +#if FEATURE_RTC_SRAM_STORAGE + writertc, +#endif // if FEATURE_RTC_SRAM_STORAGE NotMatched // Keep as last one diff --git a/src/src/Commands/RTCSRAMStorage.cpp b/src/src/Commands/RTCSRAMStorage.cpp new file mode 100644 index 0000000000..3924aa80a7 --- /dev/null +++ b/src/src/Commands/RTCSRAMStorage.cpp @@ -0,0 +1,47 @@ +#include "../Commands/RTCSRAMStorage.h" +#if FEATURE_RTC_SRAM_STORAGE +# include "../../ESPEasy/eeprom/Helpers/RTCSRAMStorage.h" + +# include "../../ESPEasy_common.h" + +# include "../Commands/Common.h" + +# include "../DataStructs/ESPEasy_EventStruct.h" + +# include "../Helpers/Misc.h" +# include "../Helpers/Numerical.h" +# include "../Helpers/StringConverter.h" + +// Command: WriteRTC,, : set a slot value. 0 is 'erased' +// Command: WriteRTC,erase,erase : reset all slots to 0 +const __FlashStringHelper* Command_writeRTC(struct EventStruct *event, const char *Line) +{ + uint32_t slot{}; + + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + double value{}; + bool validValue = validDoubleFromString(parseString(Line, 3), value); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + float value{}; + bool validValue = validFloatFromString(parseString(Line, 3), value); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + if (validUIntFromString(parseString(Line, 2), slot) && validValue) { + return return_command_boolean_result_flashstr(ESPEasy::eeprom::writeRTCSRAMSlot(slot, value)); + } else if (equals(parseString(Line, 2), F("erase")) && equals(parseString(Line, 3), F("erase"))) { + for (uint32_t slot = 0; slot < ESPEasy::eeprom::getRTCSRAMMaxSlots(); ++slot) { + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + ESPEasy::eeprom::writeRTCSRAMSlot(slot, 0.0); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + ESPEasy::eeprom::writeRTCSRAMSlot(slot, 0.0f); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + if (slot % 50 == 0) { delay(0); } + } + addLog(LOG_LEVEL_INFO, F("RTC SRAM: All slot-values erased.")); + return return_command_success_flashstr(); + } + return return_command_failed_flashstr(); +} + +#endif // if FEATURE_EEPROM_EXTERNAL diff --git a/src/src/Commands/RTCSRAMStorage.h b/src/src/Commands/RTCSRAMStorage.h new file mode 100644 index 0000000000..6c0593e952 --- /dev/null +++ b/src/src/Commands/RTCSRAMStorage.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../../ESPEasy_common.h" + +#if FEATURE_RTC_SRAM_STORAGE +const __FlashStringHelper* Command_writeRTC(struct EventStruct *event, + const char *Line); +#endif // if FEATURE_RTC_SRAM_STORAGE diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 2db24e2b85..78a7f10eec 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -4344,6 +4344,28 @@ To create/register a plugin, you have to : #endif #endif // ifndef FEATURE_EEPROM_EXTERNAL +#if FEATURE_EXT_RTC +#ifndef FEATURE_RTC_SRAM_STORAGE + #ifdef ESP32 + #define FEATURE_RTC_SRAM_STORAGE 1 + #endif + #ifdef ESP8266 + #ifdef LIMIT_BUILD_SIZE + #define FEATURE_RTC_SRAM_STORAGE 0 // Disabled for limited builds on ESP8266 + #else + #define FEATURE_RTC_SRAM_STORAGE 0 // Disabled by default on ESP8266 + #endif + #endif +#endif // ifndef FEATURE_RTC_SRAM_STORAGE +#else // if FEATURE_EXT_RTC + #define FEATURE_RTC_SRAM_STORAGE 0 // Not available +#endif // if FEATURE_EXT_RTC +#if FEATURE_RTC_SRAM_STORAGE + #define FEATURE_SRAM_STORAGE_DOUBLE 1 // 1 = double-size SRAM storage = 8 bytes per slot +#else // if FEATURE_RTC_SRAM_STORAGE + #define FEATURE_SRAM_STORAGE_DOUBLE 0 // 0 = float-size SRAM storage = 4 bytes per slot +#endif // if FEATURE_RTC_SRAM_STORAGE + #ifndef FEATURE_PLUGIN_LIST #ifdef ESP32 #define FEATURE_PLUGIN_LIST 1 diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.h b/src/src/DataStructs/ExtraTaskSettingsStruct.h index dc5ad44614..b4e3c4077e 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.h +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.h @@ -112,7 +112,6 @@ struct ExtraTaskSettingsStruct * - 0..7 : PluginStats config (8 bits) * - 8..15 : UnitOfMeasure index (8 bits) * - 16..23 : CustomValueType index (8 bits) - * - 24 : Store value in EEPROM (1 bit) */ }; diff --git a/src/src/DataTypes/TimeSource.cpp b/src/src/DataTypes/TimeSource.cpp index 3542a864a8..5f462c77b0 100644 --- a/src/src/DataTypes/TimeSource.cpp +++ b/src/src/DataTypes/TimeSource.cpp @@ -6,6 +6,7 @@ const __FlashStringHelper* toString(ExtTimeSource_e timeSource) case ExtTimeSource_e::None: break; case ExtTimeSource_e::DS1307: return F("DS1307"); case ExtTimeSource_e::DS3231: return F("DS3231"); + case ExtTimeSource_e::DS3232: return F("DS3232"); case ExtTimeSource_e::PCF8523: return F("PCF8523"); case ExtTimeSource_e::PCF8563: return F("PCF8563"); } diff --git a/src/src/DataTypes/TimeSource.h b/src/src/DataTypes/TimeSource.h index 37ea3b1ed6..c61afa366d 100644 --- a/src/src/DataTypes/TimeSource.h +++ b/src/src/DataTypes/TimeSource.h @@ -9,7 +9,8 @@ enum class ExtTimeSource_e { DS1307, DS3231, PCF8523, - PCF8563 + PCF8563, + DS3232, }; diff --git a/src/src/Helpers/ESPEasyRTC.cpp b/src/src/Helpers/ESPEasyRTC.cpp index b91cb45dc2..aa94852514 100644 --- a/src/src/Helpers/ESPEasyRTC.cpp +++ b/src/src/Helpers/ESPEasyRTC.cpp @@ -81,11 +81,6 @@ // - RTCStruct to keep information on reboot reason, last used WiFi, etc. // - UserVar to keep task values persistent just like on ESP8266 -/** - * With EEPROMExternal (AT24cxxx) enabled and configured: - * - UserVar will be stored in external EEPROM - * - - */ @@ -93,7 +88,6 @@ //#define RTC_STRUCT_DEBUG -constexpr uint32_t sizeof_uint32_t = sizeof(uint32_t); #ifdef ESP32 constexpr size_t UserVar_nrelements = VARS_PER_TASK * TASKS_MAX; @@ -140,7 +134,7 @@ void initRTC() saveToRTC(); UserVar.clear(); - saveUserVarToRTC(true); + saveUserVarToRTC(); } /********************************************************************************************\ @@ -164,11 +158,7 @@ bool readFromRTC() /********************************************************************************************\ Save values to RTC memory \*********************************************************************************************/ -bool saveUserVarToRTC() { - return saveUserVarToRTC(false); -} - -bool saveUserVarToRTC(bool initial) +bool saveUserVarToRTC() { // ESP8266 has the RTC struct stored in memory which we must actively fetch // ESP32 Uses a temp structure which is mapped to the RTC address range. diff --git a/src/src/Helpers/ESPEasyRTC.h b/src/src/Helpers/ESPEasyRTC.h index 58bdf5269c..d98182f83d 100644 --- a/src/src/Helpers/ESPEasyRTC.h +++ b/src/src/Helpers/ESPEasyRTC.h @@ -17,7 +17,6 @@ bool readFromRTC(); Save values to RTC memory \*********************************************************************************************/ bool saveUserVarToRTC(); -bool saveUserVarToRTC(bool initial); /********************************************************************************************\ Read RTC struct from RTC memory diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index 387ea8b7a5..fed2d1b70c 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -978,6 +978,7 @@ bool ESPEasy_time::ExtRTC_get(uint32_t& unixtime) break; } case ExtTimeSource_e::DS3231: + case ExtTimeSource_e::DS3232: { I2CSelectHighClockSpeed(i2cBus); RTC_DS3231 rtc; @@ -1073,6 +1074,7 @@ bool ESPEasy_time::ExtRTC_set(uint32_t unixtime) break; } case ExtTimeSource_e::DS3231: + case ExtTimeSource_e::DS3232: { I2CSelectHighClockSpeed(i2cBus); RTC_DS3231 rtc; diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index ffb3b2d00b..07b2971ca0 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -27,6 +27,9 @@ #if FEATURE_EEPROM_EXTERNAL #include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" #endif // if FEATURE_EEPROM_EXTERNAL +#if FEATURE_RTC_SRAM_STORAGE +#include "../../ESPEasy/eeprom/Helpers/RTCSRAMStorage.h" +#endif // if FEATURE_RTC_SRAM_STORAGE /********************************************************************************************\ Parse string template @@ -183,6 +186,9 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us #if FEATURE_EEPROM_EXTERNAL const bool devNameEqReadEE = equals(deviceName, F("readee")); #endif // if FEATURE_EEPROM_EXTERNAL + #if FEATURE_RTC_SRAM_STORAGE + const bool devNameEqReadRTC = equals(deviceName, F("readrtc")); + #endif // if FEATURE_RTC_SRAM_STORAGE if (devNameEqInt || equals(deviceName, F("var")) #if FEATURE_STRING_VARIABLES || devNameEqStr || devNameEqLength @@ -190,6 +196,9 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us #if FEATURE_EEPROM_EXTERNAL || devNameEqReadEE #endif // if FEATURE_EEPROM_EXTERNAL + #if FEATURE_RTC_SRAM_STORAGE + || devNameEqReadRTC + #endif // if FEATURE_RTC_SRAM_STORAGE ) { // Address an internal variable either as float or as int @@ -242,6 +251,29 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us } } else #endif // if FEATURE_EEPROM_EXTERNAL + #if FEATURE_RTC_SRAM_STORAGE + if (devNameEqReadRTC) { + uint32_t slot{}; + String value; + if (validUIntFromString(valueName, slot)) { + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = doubleToString(ESPEasy::eeprom::readRTCSRAMSlot(slot)); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = toString(ESPEasy::eeprom::readRTCSRAMSlot(slot)); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (valueName.equalsIgnoreCase(F("max"))) { + value = ESPEasy::eeprom::getRTCSRAMMaxSlots(); + } + if (!value.isEmpty()) { + transformValue( + newString, + minimal_lineSize, + std::move(value), + format, + tmpString); + } + } else + #endif // if FEATURE_RTC_SRAM_STORAGE { const ESPEASY_RULES_FLOAT_TYPE floatvalue = getCustomFloatVar(valueName); unsigned char nr_decimals = maxNrDecimals_fpType(floatvalue); diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index cf5ffa115a..30e51c2c58 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -456,11 +456,12 @@ void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __Flash { addRowLabel(label); const __FlashStringHelper * options[] = - { F("None"), F("DS1307"), F("DS3231"), F("PCF8523"), F("PCF8563")}; + { F("None"), F("DS1307"), F("DS3231"), F("DS3232"), F("PCF8523"), F("PCF8563")}; constexpr int optionValues[] = { static_cast(ExtTimeSource_e::None), static_cast(ExtTimeSource_e::DS1307), static_cast(ExtTimeSource_e::DS3231), + static_cast(ExtTimeSource_e::DS3232), static_cast(ExtTimeSource_e::PCF8523), static_cast(ExtTimeSource_e::PCF8563) }; diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index a157bacf17..f01a075bbf 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -364,9 +364,9 @@ void WebServerInit() #ifdef WEBSERVER_SYSVARS web_server.on(F("/sysvars"), handle_sysvars); #endif // WEBSERVER_SYSVARS -#if FEATURE_EEPROM_EXTERNAL +#if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE web_server.on(F("/eepromvars"), handle_eepromvars); -#endif // if FEATURE_EEPROM_EXTERNAL +#endif // if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE #if FEATURE_PLUGIN_LIST web_server.on(F("/pluginlist"), handle_pluginlist); #endif // if FEATURE_PLUGIN_LIST diff --git a/src/src/WebServer/EepromVarPage.h b/src/src/WebServer/EepromVarPage.h index ed4edb5620..e0bfac0213 100644 --- a/src/src/WebServer/EepromVarPage.h +++ b/src/src/WebServer/EepromVarPage.h @@ -1,8 +1,8 @@ #pragma once -#if FEATURE_EEPROM_EXTERNAL +#if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE # include "../WebServer/common.h" void handle_eepromvars(); -#endif // if FEATURE_EEPROM_EXTERNAL +#endif // if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EpromVarPage.cpp index cc6268092a..d0ee360b43 100644 --- a/src/src/WebServer/EpromVarPage.cpp +++ b/src/src/WebServer/EpromVarPage.cpp @@ -1,27 +1,33 @@ #include "../WebServer/EepromVarPage.h" +#if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE -#include "../WebServer/ESPEasy_WebServer.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Forms.h" -#include "../WebServer/HTML_wrappers.h" +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/AccessControl.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Forms.h" +# include "../WebServer/HTML_wrappers.h" -#include "../Globals/Settings.h" -#include "../Globals/ExtraTaskSettings.h" +# include "../Globals/Settings.h" +# include "../Globals/ExtraTaskSettings.h" -#include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" -#include "../Helpers/ESPEasy_Storage.h" +# if FEATURE_EEPROM_EXTERNAL +# include "../../ESPEasy/eeprom/Helpers/EEPROMExternal.h" +# endif // if FEATURE_EEPROM_EXTERNAL +# if FEATURE_RTC_SRAM_STORAGE +# include "../../ESPEasy/eeprom/Helpers/RTCSRAMStorage.h" +# endif // if FEATURE_RTC_SRAM_STORAGE +# include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/StringConverter.h" - -#if FEATURE_EEPROM_EXTERNAL +# include "../Helpers/StringConverter.h" void handle_eepromvars() { if (!isLoggedIn()) { return; } if (!startStream_send_stdTemplate(MENU_INDEX_TOOLS)) { return; } + # if FEATURE_EEPROM_EXTERNAL + if (ESPEasy::eeprom::checkEEPROMEnabled() > 0) { // the table header html_table_class_normal(); @@ -55,11 +61,80 @@ void handle_eepromvars() { html_TR_TD(); addHtmlInt(slot); html_TD(); - # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + addHtml(doubleToString(value)); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + addHtml(toString(value)); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + html_TD(2); + } + } + + addTableSeparator(F("Summary"), 3, 2); + + html_TR_TD(); + addHtml(F("Slots occupied: ")); + + if (0 == count) { + addHtml(F("none")); + } else { + addHtmlInt(count); + } + addHtml(F("")); + addHtml(F("Slots available: ")); + addHtmlInt(maxSlots - count); + + if (count > 0) { + addHtml(F(" of ")); + addHtmlInt(maxSlots); + } + html_TD(); + + html_end_table(); + } else { + addHtml(F("External EEPROM not enabled.
")); + } + # endif // if FEATURE_EEPROM_EXTERNAL + # if FEATURE_RTC_SRAM_STORAGE + + if (ESPEasy::eeprom::checkRTCSRAMEnabled() > 0) { + // the table header + html_table_class_normal(); + html_TR(); + html_table_header(F("External RTC SRAM"), + 300); + html_table_header(F(""), + 500); + html_table_header(F(""), + 400); + html_table_header(F("")); + + html_TR(); + + // sub-table header + html_table_header(F("Slot"), 300); + html_table_header(F("Value (only non-zero values)"), 500); + html_table_header(F("")); + html_table_header(F("")); + + const uint32_t maxSlots = ESPEasy::eeprom::getRTCSRAMMaxSlots(); + uint32_t count{}; + + for (uint32_t slot = 0; slot < maxSlots; ++slot) { + const float value = ESPEasy::eeprom::readRTCSRAMSlot(slot); + + if (slot % 50 == 0) { delay(0); } + + if (!isnan(value) && !essentiallyZero(value)) { + ++count; + html_TR_TD(); + addHtmlInt(slot); + html_TD(); + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE addHtml(doubleToString(value)); - # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE addHtml(toString(value)); - # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE html_TD(2); } } @@ -86,10 +161,11 @@ void handle_eepromvars() { html_end_table(); } else { - addHtml(F("External EEPROM not enabled.")); + addHtml(F("External RTC SRAM not available.")); } + # endif // if FEATURE_RTC_SRAM_STORAGE html_end_form(); sendTail_stdtemplate(); } -#endif // if FEATURE_EEPROM_EXTERNAL +#endif // if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE diff --git a/src/src/WebServer/ToolsPage.cpp b/src/src/WebServer/ToolsPage.cpp index fae39d300c..aec05350bb 100644 --- a/src/src/WebServer/ToolsPage.cpp +++ b/src/src/WebServer/ToolsPage.cpp @@ -86,9 +86,9 @@ void handle_tools() { addWideButtonPlusDescription(F("sysvars"), F("System Variables"), F("Show all system variables and conversions")); # endif // ifdef WEBSERVER_SYSVARS - #if FEATURE_EEPROM_EXTERNAL - addWideButtonPlusDescription(F("eepromvars"), F("External EEPROM values"), F("Show all values stored in the external EEPROM")); - #endif // if FEATURE_EEPROM_EXTERNAL + #if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE + addWideButtonPlusDescription(F("eepromvars"), F("External EEPROM/RTC values"), F("Show all values stored in the external EEPROM or RTC SRAM")); + #endif // if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE #if FEATURE_PLUGIN_LIST addWideButtonPlusDescription(F("pluginlist"), F("Included Plugins"), F("Show all plugins that are included in this build")); diff --git a/static/espeasy.js b/static/espeasy.js index 3bf7a53d44..4994711e84 100644 --- a/static/espeasy.js +++ b/static/espeasy.js @@ -11,10 +11,12 @@ var commonCommands = ["AccessInfo", "Background", "Build", "ClearAccessBlock", " "TaskDisable", "TaskEnable", "TaskRun", "TaskValueSet", "TaskValueSetAndRun", "TaskValueSetDerived", "TaskValueSetPresentation", "TimerPause", "TimerResume", "TimerSet", "TimerSet_ms", "TimeZone", "UdpPort", "UdpTest", "Unit", "UseNTP", "WdConfig", "WdRead", "WiFi", "WiFiAllowAP", "WiFiAPMode", "WiFiConnect", "WiFiDisconnect", "WiFiKey", "WiFiKey2", "WiFiMode", "WiFiScan", "WiFiSSID", "WiFiSSID2", "WiFiSTAMode", + "WriteEE", "WriteRTC", "Event", "AsyncEvent", "GPIO", "GPIOToggle", "LongPulse", "LongPulse_mS", "Monitor", "Pulse", "PWM", "Servo", "Status", "Tone", "RTTTL", "UnMonitor", "Provision", "Provision,Config", "Provision,Security", "Provision,Notification", "Provision,Provision", "Provision,Rules", "Provision,CustomCdnUrl", "Provision,Firmware"]; var commonEvents = ["Clock#Time", "JsonReply", "JsonReply#", "Login#Failed", "MQTT#Connected", "MQTT#Disconnected", "MQTTimport#Connected", "MQTTimport#Disconnected","OpenMeteo#current","OpenMeteo#daily", "OpenMeteo#hourly", "Rules#Timer", "System#Boot", + "ReadEE#", "ReadRTC#", "System#BootMode", "System#Sleep", "System#Wake", "TaskExit#", "TaskInit#", "ThingspeakReply", "Time#Initialized", "Time#Set", "WiFi#APmodeDisabled", "WiFi#APmodeEnabled", "WiFi#ChangedAccesspoint", "WiFi#ChangedWiFichannel", "WiFi#Connected", "WiFi#Disconnected"]; var commonPlugins = [ diff --git a/static/espeasy.min.js b/static/espeasy.min.js index 63a16731ae..dc0e22f27c 100644 --- a/static/espeasy.min.js +++ b/static/espeasy.min.js @@ -1 +1 @@ -var commonAtoms=["And","Or"],commonKeywords=["If","Else","Elseif","Endif"],commonCommands=["AccessInfo","Background","Build","ClearAccessBlock","ClearRTCam","Config","ControllerDisable","ControllerEnable","DateTime","Debug","Dec","DeepSleep","DisablePriorityTask","DNS","DST","EraseSDKWiFi","ExecuteRules","FactoryReset","Gateway","I2Cscanner","Inc","IP","Latitude","Let","LetStr","Load","LogEntry","LogPortStatus","Longitude","LoopTimerSet","LoopTimerSet_ms","LoopTimerSetAndRun","LoopTimerSetAndRun_ms","MemInfo","MemInfoDetail","Name","NetworkDisable","NetworkEnable","Password","PostToHTTP","PostToHTTPS","Publish","PublishR","PutToHTTP","PutToHTTPS","Reboot","Save","SendTo","SendToHTTP","SendToHTTPS","SendToUDP","SendToUDPMix","Settings","Subnet","Subscribe","TaskClear","TaskClearAll","TaskDisable","TaskEnable","TaskRun","TaskValueSet","TaskValueSetAndRun","TaskValueSetDerived","TaskValueSetPresentation","TimerPause","TimerResume","TimerSet","TimerSet_ms","TimeZone","UdpPort","UdpTest","Unit","UseNTP","WdConfig","WdRead","WiFi","WiFiAllowAP","WiFiAPMode","WiFiConnect","WiFiDisconnect","WiFiKey","WiFiKey2","WiFiMode","WiFiScan","WiFiSSID","WiFiSSID2","WiFiSTAMode","Event","AsyncEvent","GPIO","GPIOToggle","LongPulse","LongPulse_mS","Monitor","Pulse","PWM","Servo","Status","Tone","RTTTL","UnMonitor","Provision","Provision,Config","Provision,Security","Provision,Notification","Provision,Provision","Provision,Rules","Provision,CustomCdnUrl","Provision,Firmware"],commonEvents=["Clock#Time","JsonReply","JsonReply#","Login#Failed","MQTT#Connected","MQTT#Disconnected","MQTTimport#Connected","MQTTimport#Disconnected","OpenMeteo#current","OpenMeteo#daily","OpenMeteo#hourly","Rules#Timer","System#Boot","System#BootMode","System#Sleep","System#Wake","TaskExit#","TaskInit#","ThingspeakReply","Time#Initialized","Time#Set","WiFi#APmodeDisabled","WiFi#APmodeEnabled","WiFi#ChangedAccesspoint","WiFi#ChangedWiFichannel","WiFi#Connected","WiFi#Disconnected"],commonPlugins=["ResetPulseCounter","SetPulseCounterTotal","LogPulseStatistic","analogout","MCPGPIO","MCPGPIOToggle","MCPLongPulse","MCPLongPulse_ms","MCPPulse","Status,MCP","Monitor,MCP","MonitorRange,MCP","UnMonitorRange,MCP","UnMonitor,MCP","MCPGPIORange","MCPGPIOPattern","MCPMode","MCPModeRange","ExtGpio","ExtPwm","ExtPulse","ExtLongPulse","Status,EXT,","LCDCmd","LCD","PCFGPIO","PCFGPIOToggle","PCFLongPulse","PCFLongPulse_ms","PCFPulse","Status,PCF","Monitor,PCF","MonitorRange,PCF","UnMonitorRange,PCF","UnMonitor,PCF","PCFGPIORange","PCFGPIOpattern","PCFMode","PCFmodeRange","SerialSend","SerialSendMix","Ser2NetClientSend","SerialSend_test","pcapwm","pcafrq","mode2","OLED","OLEDCMD","OLEDCMD,on","OLEDCMD,off","OLEDCMD,clear","IRSEND","IRSENDAC","OledFramedCmd","OledFramedCmd,Display","OledFramedCmd,low","OledFramedCmd,med","OledFramedCmd,high","OledFramedCmd,Frame","OledFramedCmd,linecount","OledFramedCmd,leftalign","OledFramedCmd,align","OledFramedCmd,userDef1","OledFramedCmd,userDef2","NeoPixel","NeoPixelAll","NeoPixelLine","NeoPixelHSV","NeoPixelAllHSV","NeoPixelLineHSV","NeoPixelBright","MotorShieldCmd,DCMotor","MotorShieldCmd,Stepper","MHZCalibrateZero","MHZReset","MHZABCEnable","MHZABCDisable","Sensair_SetRelay","PMSX003","PMSX003,Wake","PMSX003,Sleep","PMSX003,Reset","encwrite","Play","Vol","Eq","Mode","Repeat","tareChanA","tareChanB","7dn","7dst","7dsd","7dtext","7ddt","7dt","7dtfont","7dtbin","7don","7doff","7output","HLWCalibrate","HLWReset","csecalibrate","cseclearpulses","csereset","WemosMotorShieldCMD","LolinMotorShieldCMD","GPS","GPS,Sleep","GPS,Wake","GPS#GotFix","GPS#LostFix","GPS#Travelled","homieValueSet","SerialProxy_Write","SerialProxy_WriteMix","SerialProxy_Test","HeatPumpir","MitsubishiHP","MitsubishiHP,temperature","MitsubishiHP,power","MitsubishiHP,mode","MitsubishiHP,fan","MitsubishiHP,vane","MitsubishiHP,widevane","Culreader_Write","Touch","Touch,Rot","Touch,Flip","Touch,Enable","Touch,Disable","Touch,On","Touch,Off","Touch,Toggle","Touch,Setgrp","Touch,Incgrp","Touch,Decgrp","Touch,Incpage","Touch,Decpage","Touch,Updatebutton","WakeOnLan","DotMatrix","DotMatrix,clear","DotMatrix,update","DotMatrix,size","DotMatrix,txt","DotMatrix,settxt","DotMatrix,content","DotMatrix,alignment","DotMatrix,anim.in","DotMatrix,anim.out","DotMatrix,speed","DotMatrix,pause","DotMatrix,font","DotMatrix,layout","DotMatrix,inverted","DotMatrix,specialeffect","DotMatrix,offset","DotMatrix,brightness","DotMatrix,repeat","DotMatrix,setbar","DotMatrix,bar","Thermo","Thermo,Up","Thermo,Down","Thermo,Mode","Thermo,ModeBtn","Thermo,Setpoint","Max1704xclearalert","scdgetabc","scdgetalt","scdgettmp","scdsetcalibration","scdsetfrc","scdgetinterval","multirelay","multirelay,on","multirelay,off","multirelay,set","multirelay,get","multirelay,loop","ShiftOut","ShiftOut,Set","ShiftOut,SetNoUpdate","ShiftOut,Update","ShiftOut,SetAll","ShiftOut,SetAllNoUpdate","ShiftOut,SetAllLow","ShiftOut,SetAllHigh","ShiftOut,SetChipCount","ShiftOut,SetHexBin","cdmrst","nfx","nfx,off","nfx,on","nfx,dim","nfx,line,","nfx,hsvline,","nfx,one,","nfx,hsvone,","nfx,all,","nfx,rgb,","nfx,fade,","nfx,hsv,","nfx,colorfade,","nfx,rainbow","nfx,kitt,","nfx,comet,","nfx,theatre,","nfx,scan,","nfx,dualscan,","nfx,twinkle,","nfx,twinklefade,","nfx,sparkle,","nfx,wipe,","nfx,dualwipe","nfx,fire","nfx,fireflicker","nfx,faketv","nfx,simpleclock","nfx,stop","nfx,statusrequest","nfx,fadetime,","nfx,fadedelay,","nfx,speed,","nfx,count,","nfx,bgcolor","ShiftIn","ShiftIn,PinEvent","ShiftIn,ChipEvent","ShiftIn,SetChipCount","ShiftIn,SampleFrequency","ShiftIn,EventPerPin","scd4x","scd4x,storesettings","scd4x,facoryreset","scd4x,selftest","scd4x,setfrc,","axp","axp,ldo2","axp,ldo3","axp,ldoio","axp,gpio0","axp,gpio1","axp,gpio2","axp,gpio3","axp,gpio4","axp,dcdc2","axp,dcdc3","axp,ldo2map","axp,ldo3map","axp,ldoiomap","axp,dcdc2map","axp,dcdc3map","axp,ldo2perc","axp,ldo3perc","axp,ldoioperc","axp,dcdc2perc","axp,dcdc3perc","I2CEncoder","I2CEncoder,bright","I2CEncoder,led1","I2CEncoder,led2","I2CEncoder,gain","I2CEncoder,set","cachereader","cachereader,readpos","cachereader,sendtaskinfo","cachereader,flush","tm1621","tm1621,write,","tm1621,writerow,","tm1621,voltamp,","tm1621,energy,","tm1621,celcius,","tm1621,fahrenheit,","tm1621,humidity,","tm1621,raw,","dac","dac,1","dac,2","sht4x","sht4x,startup","ld2410","ld2410,factoryreset","ld2410,logall","digipot","digipot,reset","digipot,shutdown","digipot,","7dextra","7dbefore","7dgroup","7digit","7color","7digitcolor","7groupcolor","gp8403","gp8403,volt,","gp8403,mvolt,","gp8403,range,","gp8403,preset,","gp8403,init,","sen5x","sen5x,startclean","sen5x,techlog,","as3935","as3935,clearstats","as3935,calibrate","as3935,setgain,","as3935,setnf,","as3935,setwd,","as3925,setsrej,","lu9685","lu9685,servo,","lu9685,enable,","lu9685,disable,","lu9685,setrange,","geni2c","geni2c,cmd,","geni2c,exec,","geni2c,log,"],pluginDispKind=["tft","ili9341","ili9342","ili9481","ili9486","ili9488","epd","eink","epaper","il3897","uc8151d","ssd1680","ws2in7","ws1in54","st77xx","st7735","st7789","st7796","neomatrix","neo","pcd8544"],pluginDispCmd=["cmd,on","cmd,off","cmd,clear","cmd,backlight","cmd,bright","cmd,deepsleep","cmd,seq_start","cmd,seq_end","cmd,inv","cmd,rot",",clear",",rot",",tpm",",txt",",txp",",txz",",txc",",txs",",txtfull",",asciitable",",font",",l",",lh",",lv",",lm",",lmr",",r",",rf",",c",",cf",",rf",",t",",tf",",rr",",rrf",",px",",pxh",",pxv",",bmp",",btn",",win",",defwin",",delwin"],commonTag=["On","Do","Endon"],commonNumber=["toBin","toHex","Constrain","XOR","AND:","OR:","Ord","bitRead","bitSet","bitClear","bitWrite","urlencode"],commonMath=["Log","Ln","Abs","Exp","Sqrt","Sq","Round","Sin","Cos","Tan","aSin","aCos","aTan","aTan2","Sin_d","Cos_d","Tan_d","aSin_d","aCos_d","aTan_d","aTan2_d","map","mapc","fmod"],commonWarning=["delay","Delay","ResetFlashWriteCounter"],taskSpecifics=["settings.Enabled","settings.Interval","settings.ValueCount","settings.Controller1.Enabled","settings.Controller2.Enabled","settings.Controller3.Enabled","settings.Controller1.Idx","settings.Controller2.Idx","settings.Controller3.Idx"],AnythingElse=["%eventvalue%","%eventpar%","%eventname%","%sysname%","%bootcause%","%systime%","%systm_hm%","%systm_hm_0%","%systm_hm_sp%","%systime_am%","%systime_am_0%","%systime_am_sp%","%systm_hm_am%","%systm_hm_am_0%","%systm_hm_am_sp%","%lcltime%","%sunrise%","%s_sunrise%","%m_sunrise%","%sunset%","%s_sunset%","%m_sunset%","%lcltime_am%","%latitude%","%longitude%","%syshour%","%syshour_0%","%sysmin%","%sysmin_0%","%syssec%","%syssec_0%","%sysday%","%sysday_0%","%sysmonth%","%sysmonth_0%","%systzoffset%","%systzoffset_s%","%sysyear%","%sysyear_0%","%sysyears%","%sysweekday%","%sysweekday_s%","%unixtime%","%unixtime_lcl%","%uptime%","%uptime_ms%","%rssi%","%ip%","%unit%","%unit_0%","%ssid%","%bssid%","%wi_ch%","%iswifi%","%vcc%","%mac%","%mac_int%","%isntp%","%ismqtt%","%dns%","%dns1%","%dns2%","%flash_freq%","%flash_size%","%flash_chip_vendor%","%flash_chip_model%","%fs_free%","%fs_size%","%cpu_id%","%cpu_freq%","%cpu_model%","%cpu_rev%","%cpu_cores%","%board_name%","%inttemp%","%islimited_build%","%isvar_double%","substring","lookup","indexOf","indexOf_ci","equals","equals_ci","strtol","timeToMin","timeToSec","%ethwifimode%","%ethconnected%","%ethduplex%","%ethspeed%","%ethstate%","%ethspeedstate%","%c_w_dir%","%c_c2f%","%c_ms2Bft%","%c_dew_th%","%c_alt_pres_sea%","%c_sea_pres_alt%","%c_cm2imp%","%c_isnum%","%c_mm2imp%","%c_m2day%","%c_m2dh%","%c_m2dhm%","%c_s2dhms%","%c_ts2date%","%c_ts2isodate%","%c_ts2wday%","%c_random%","%c_2hex%","%c_u2ip%","%c_uname%","%c_uage%","%c_ubuild%","%c_ubuildstr%","%c_uload%","%c_utype%","%c_utypestr%","%c_strf%","%c_d2r%","%c_r2d%","%SP%","%CR%","%LF%","%N%","%R%","%e%","%pi%","var","int","str","length"];for(const e of pluginDispKind)commonPlugins=commonPlugins.concat(e);for(const e of pluginDispKind)for(const t of pluginDispCmd){let n=e+t;commonPlugins=commonPlugins.concat(n)}var rEdit,EXTRAWORDS=commonAtoms.concat(commonPlugins,commonKeywords,commonCommands,commonEvents,commonTag,commonNumber,commonMath,commonWarning,taskSpecifics,AnythingElse),confirmR=!0,android=/Android/.test(navigator.userAgent);function initCM(){function e(e){}android&&(confirmR=!!confirm("Do you want to enable colored rules on your Android device?\nThis feature hasn't been fully tested yet and may still have some issues.\nIt is currently expected to work with Chrome, Firefox, and Vivaldi.\nPlease report any problems you encounter.")),confirmR&&(CodeMirror.commands.autocomplete=function(e){e.showHint({hint:CodeMirror.hint.anyword})},(rEdit=CodeMirror.fromTextArea(document.getElementById("rules"),{tabSize:2,indentWithTabs:!1,lineNumbers:!0,autoCloseBrackets:!0,extraKeys:{"Ctrl-Space":"autocomplete",Tab:e=>{"null"===e.getMode().name?e.execCommand("insertTab"):e.somethingSelected()?e.execCommand("indentMore"):e.execCommand("insertSoftTab")},"Shift-Tab":e=>e.execCommand("indentLess")}})).on("change",(function(){rEdit.save()})),android||rEdit.on("inputRead",(function(e,t){var n=e.getCursor(),o=e.getTokenAt(n);/[\w%,.]/.test(t.text)&&"comment"!=o.type&&e.showHint({completeSingle:!1})})),CodeMirror.keyMap.default["Ctrl-F"]=function(e){openFind()},CodeMirror.keyMap.default["Cmd-F"]=function(e){openFind()},CodeMirror.keyMap.default["Ctrl-G"]=e,CodeMirror.keyMap.default["Cmd-G"]=e,CodeMirror.keyMap.default["Shift-Ctrl-G"]=e,CodeMirror.keyMap.default["Shift-Cmd-G"]=e,CodeMirror.keyMap.default["Ctrl-H"]=e,CodeMirror.keyMap.default["Cmd-H"]=e,CodeMirror.keyMap.default["Shift-Ctrl-F"]=e,CodeMirror.keyMap.default["Shift-Cmd-F"]=e,CodeMirror.keyMap.default["Ctrl-Shift-R"]=e,CodeMirror.keyMap.default["Cmd-Shift-R"]=e)}function closeSearchDialog(){const e=document.querySelectorAll(".CodeMirror-dialog");e.length>0&&(e.forEach((e=>e.remove())),document.body.classList.remove("dialog-opened")),rEdit.execCommand("clearSearch")}function removeHighlight(){requestAnimationFrame((()=>{document.querySelectorAll(".search-next-highlight").forEach((e=>e.classList.remove("search-next-highlight")))}))}let findDialogObserver=null;function openFind(){findDialogObserver&&(document.querySelectorAll(".CodeMirror-dialog").forEach((e=>e.remove())),findDialogObserver.disconnect(),findDialogObserver=null),findDialogObserver=new MutationObserver((()=>{document.querySelector(".CodeMirror-dialog")||(removeHighlight(),findDialogObserver.disconnect(),findDialogObserver=null)})),findDialogObserver.observe(document.body,{childList:!0,subtree:!0}),clearSearchNextHighlight(rEdit),rEdit.execCommand("findPersistent"),addFindButtons()}function clearSearchNextHighlight(e){removeHighlight(),e.__searchNextHighlight&&(e.__searchNextHighlight.clear(),e.__searchNextHighlight=null)}function addFindButtons(){document.querySelector(".CodeMirror-selected");const e=document.querySelector(".CodeMirror-dialog");if(!e||e.querySelector(".search-button-group"))return;[{title:"Find Previous",symbol:"▲",action:()=>rEdit.execCommand("findPersistentPrev")},{title:"Find Next",symbol:"▼",action:()=>rEdit.execCommand("findPersistentNext")},{title:"Replace",symbol:"Replace",action:()=>{closeSearchDialog(),rEdit.execCommand("replace"),addFindButtons()}},{title:"Close",symbol:"❌",action:closeSearchDialog},{title:"Help",symbol:"?",action:()=>{alert("Available shortcuts:\n• Ctrl+F / Cmd+F: Open search\n• Enter: Find next\n• Shift+Enter: Find previous\n• Use /re/ syntax for regex search")}}].forEach((({title:t,symbol:n,action:o})=>{const i=document.createElement("span");i.title=t,i.className="help"===t.toLowerCase()?"button help":"button",i.innerHTML=n,i.style.cssText="\n cursor: pointer;\n user-select: none;\n ",i.addEventListener("click",(e=>{e.preventDefault(),o()})),e.appendChild(i)}))}function triggerFormatting(){let e,t,n,o,i;if(confirmR){const r=rEdit.getDoc();e=rEdit.getScrollInfo(),t=r.getCursor(),n=0===t.ch?t.line-1:t.line,o=rEdit.getLine(n)||"",i=rEdit.getValue()}else i=document.getElementById("rules").value;if(i=initalAutocorrection(i),i=formatLogic(i),confirmR){rEdit.setValue(i);const r=n,s=0===t.ch&&o.length>0?o.length:t.ch;rEdit.setCursor({line:r,ch:s}),setTimeout((()=>{rEdit.scrollTo(e.left,e.top),rEdit.focus()}),0),rEdit.save()}else document.getElementById("rules").value=i}function initalAutocorrection(e){for(const t of EXTRAWORDS)if("Do"===t){const t=/(^|\s)(do)(\s*)(\/\/.*)?$/gim;e=e.replace(t,((e,t,n,o,i)=>`${t}Do${o}${i??""}`))}else{const n=new RegExp(`^\\s*\\b${t}\\b`,"gmi");e=e.replace(n,(e=>e.replace(new RegExp(t,"i"),t)))}return e}function formatLogic(e){const t=" ",n=e.split("\n").map((e=>{const t=e.trimStart();return t.startsWith("//")?e:t})),o=[],i=[];let r=!1,s=null,a=[],l=[];function c(e){return e.trim().startsWith("//")}function d(e){return""===e.trim()}function m(e){return e.trim().toLowerCase().startsWith("on")}function u(e){return e.trim().toLowerCase().endsWith("do")}function f(e){return"endon"===e.trim().toLowerCase()}function h(e){return e.trim().toLowerCase().startsWith("if")}function p(e){return"else"===e.trim().toLowerCase()}function g(e){return e.trim().toLowerCase().startsWith("elseif")}function C(e){return"endif"===e.trim().toLowerCase()}let x=0;function S(){a.length>0&&(i.push(`• Missing ${a.length} Endif(s):`),i.push(` - Unclosed If block(s) starting at line(s): ${l.join(", ")}`)),a=[],l=[]}for(let e=0;e0){const e=extractFirstErrorLine(i);if(alert("Errors found:\n"+i.join("\n")),!isNaN(e))if(confirmR)setTimeout((()=>{jumpToLine(e)}),50);else{const t=document.getElementById("rules");setTimeout((()=>{jumpToLineInTextarea(t,e)}),50)}}return o.join("\n")}function jumpToLine(e){const t=Math.max(0,e-1);rEdit.setCursor({line:t,ch:0}),rEdit.focus(),rEdit.scrollIntoView({line:t,ch:0},100)}function extractFirstErrorLine(e){for(const t of e){let e=t.match(/• Line (\d+)/);if(e)return parseInt(e[1]);if(e=t.match(/starting at line (\d+)/),e)return parseInt(e[1]);if(e=t.match(/starting at line\(s\):\s*(\d+)/),e)return parseInt(e[1])}return null}function jumpToLineInTextarea(e,t){const n=e.value.split("\n"),o=Math.max(1,Math.min(t,n.length));let i=0;for(let e=0;e{const e=document.getElementById("rulesselect");if(e){if(confirmR){const t=document.createElement("button");t.type="button",t.id="searchBtn",t.innerHTML="🔎︎",t.style.padding="2px 5px",t.className="button help",e.appendChild(t),t.addEventListener("click",(()=>{void 0!==rEdit&&openFind()}))}const t=document.createElement("button");t.type="button",t.id="formatBtn",t.textContent="Format",t.className="button",e.appendChild(t),t.addEventListener("click",(()=>{triggerFormatting()}))}let t="";if(document.addEventListener("keydown",(function(e){const n=e.key;(["Backspace","Delete","ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Enter","Tab","Escape","Shift","Control","Alt","Meta"].includes(n)||1!==n.length)&&(t="")})),android){var n=!1;rEdit.on("keydown",((e,o)=>{["Enter","Backspace"," "].includes(o.key)&&(t=""),n=!0}));let e="";const i=rEdit.getInputField();function o(o,i=!1){if(!(rEdit.hasFocus()&&rEdit&&o.data&&n))return;n=!1;const r=o.data,s=rEdit.getDoc(),a=s.getCursor(),l=rEdit.getTokenAt(a);if(" "===r)return t="",void(e="");if(!(r===e&&t.length>0))if(/[\w%,.]/.test(r)&&"comment"!==l.type){const n=a.ch<=1?r.slice(-1):r;t+=n,e=t,t.startsWith(String(a.line+1))&&0===a.ch&&(t=t.slice(String(a.line).length));const o={line:a.line,ch:a.ch-t.length+1},l=()=>{s.replaceRange(t,o,a),rEdit.setCursor({line:o.line,ch:o.ch+t.length}),rEdit.showHint({completeSingle:!1})};i?l():setTimeout(l,0)}else t=""}const r=navigator.userAgent.toLowerCase(),s=/firefox/.test(r),a=/chrome/.test(r)&&!s;s?i.addEventListener("beforeinput",(e=>{e.preventDefault(),o(e,!0)})):a&&document.addEventListener("input",(e=>{o(e,!1)})),rEdit.on("endCompletion",(function(){setTimeout((()=>{!function(){const e=document.createElement("input");e.type="text",e.style.position="absolute",e.style.opacity="0",e.style.height="0",e.style.width="0",e.style.border="none",e.style.top="0",e.style.left="-9999",e.style.padding="0",e.style.zIndex="-1",e.style.fontSize="16px",document.body.appendChild(e),e.focus(),setTimeout((()=>{e.remove(),rEdit.focus()}),10)}()}),100)}))}})),function(e){"object"==typeof exports&&"object"==typeof module?e(require("codemirror")):"function"==typeof define&&define.amd?define(["codemirror"],e):e(CodeMirror)}((function(e){"use strict";e.defineMode("espeasy",(function(){var e={};function t(t,n){for(var o=0;oe.toLowerCase()));commonCommands=commonCommands.concat(n);var o=commonEvents.map((e=>e.toLowerCase()));commonEvents=commonEvents.concat(o);var i=commonPlugins.map((e=>e.toLowerCase()));commonPlugins=commonPlugins.concat(i);var r=commonAtoms.map((e=>e.toLowerCase()));commonAtoms=commonAtoms.concat(r);var s=commonKeywords.map((e=>e.toLowerCase()));commonKeywords=commonKeywords.concat(s);var a=commonTag.map((e=>e.toLowerCase()));commonTag=commonTag.concat(a);var l=commonNumber.map((e=>e.toLowerCase()));commonNumber=commonNumber.concat(l);var c=commonMath.map((e=>e.toLowerCase()));commonMath=commonMath.concat(c);var d=AnythingElse.map((e=>e.toLowerCase()));AnythingElse=AnythingElse.concat(d);var m=taskSpecifics.map((e=>e.toLowerCase()));function u(t,n){if(t.eatSpace())return null;t.sol();var o=t.next();if(/\d/.test(o)){if("0"==o)return"x"===t.next()?(t.eatWhile(/\w/),"number"):(t.eatWhile(/\d|\./),"number");if(t.eatWhile(/\d|\./),!t.match("d")&&!t.match("output")&&(t.eol()||/\D/.test(t.peek())))return"number"}if(/\w/.test(o))for(const e of EXTRAWORDS){let n=e.substring(1);(e.includes(":")||e.includes(",")||e.includes("."))&&t.match(n)}if(/\w/.test(o)&&(t.eatWhile(/[\w]/),t.match(".gpio")||t.match(".pulse")||t.match(".frq")||t.match(".pwm")))return"def";if("\\"===o)return t.next(),null;if("("===o||")"===o)return"bracket";if("{"===o||"}"===o||":"===o)return"number";if("/"==o)return/\//.test(t.peek())?(t.skipToEnd(),"comment"):"operator";if("'"==o&&(t.eatWhile(/[^']/),t.match("'")))return"attribute";if("+"===o||"="===o||"<"===o||">"===o||"-"===o||","===o||"*"===o||"!"===o)return"operator";if("%"==o){if(/\d/.test(t.next()))return"number";if(t.eatWhile(/[^\s\%]/),t.match("%"))return"hr"}if("["==o&&(t.eatWhile(/[^\s\]]/),t.eat("]")))return"hr";t.eatWhile(/\w/);var i=t.current();return/\w/.test(o)&&t.match("#")?(t.eatWhile(/[\w.#]/),"events"):"#"===o?(t.eatWhile(/\w/),"number"):e.hasOwnProperty(i)?e[i]:null}function f(e,t){return(t.tokens[0]||u)(e,t)}return taskSpecifics=taskSpecifics.concat(m),t("atom",commonAtoms),t("keyword",commonKeywords),t("builtin",commonCommands),t("events",commonEvents),t("def",commonPlugins),t("tag",commonTag),t("number",commonNumber),t("bracket",commonMath),t("warning",commonWarning),t("hr",AnythingElse),t("comment",taskSpecifics),{startState:function(){return{tokens:[]}},token:function(e,t){return f(e,t)},closeBrackets:"[]{}''\"\"``()",lineComment:"//",fold:"brace"}}))})),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],mod):e(CodeMirror)}((function(e){var t={pairs:"()[]{}''\"\"",closeBefore:")]}'\":;>",triples:"",explode:"[]{}"},n=e.Pos;function o(e,n){return"pairs"==n&&"string"==typeof e?e:"object"==typeof e&&null!=e[n]?e[n]:t[n]}e.defineOption("autoCloseBrackets",!1,(function(t,n,s){s&&s!=e.Init&&(t.removeKeyMap(i),t.state.closeBrackets=null),n&&(r(o(n,"pairs")),t.state.closeBrackets=n,t.addKeyMap(i))}));var i={Backspace:function(t){var i=a(t);if(!i||t.getOption("disableInput"))return e.Pass;for(var r=o(i,"pairs"),s=t.listSelections(),l=0;l=0;l--){var m=s[l].head;t.replaceRange("",n(m.line,m.ch-1),n(m.line,m.ch+1),"+delete")}},Enter:function(t){var n=a(t),i=n&&o(n,"explode");if(!i||t.getOption("disableInput"))return e.Pass;for(var r=t.listSelections(),s=0;s1&&h.indexOf(i)>=0&&t.getRange(n(M.line,M.ch-2),M)==i+i){if(M.ch>2&&/\bstring/.test(t.getTokenTypeAt(n(M.line,M.ch-2))))return e.Pass;S="addFour"}else if(p){var b=0==M.ch?" ":t.getRange(n(M.line,M.ch-1),M);if(e.isWordChar(P)||b==i||e.isWordChar(b))return e.Pass;S="both"}else{if(!C||!(0===P.length||/\s/.test(P)||f.indexOf(P)>-1))return e.Pass;S="both"}else S=p&&m(t,M)?"both":h.indexOf(i)>=0&&t.getRange(M,n(M.line,M.ch+3))==i+i+i?"skipThree":"skip";if(u){if(u!=S)return e.Pass}else u=S}var T=d%2?s.charAt(d-1):i,v=d%2?i:s.charAt(d+1);t.operation((function(){if("skip"==u)l(t,1);else if("skipThree"==u)l(t,3);else if("surround"==u){for(var e=t.getSelections(),n=0;n0?{line:s.head.line,ch:s.head.ch+t}:{line:s.head.line-1};n.push({anchor:a,head:a})}e.setSelections(n,i)}function c(t){var o=e.cmpPos(t.anchor,t.head)>0;return{anchor:new n(t.anchor.line,t.anchor.ch+(o?-1:1)),head:new n(t.head.line,t.head.ch+(o?1:-1))}}function d(e,t){var o=e.getRange(n(t.line,t.ch-1),n(t.line,t.ch+1));return 2==o.length?o:null}function m(e,t){var o=e.getTokenAt(n(t.line,t.ch+1));return/\bstring/.test(o.type)&&o.start==t.ch&&(0==t.ch||!/\bstring/.test(e.getTokenTypeAt(t)))}r(t.pairs+"`")})); \ No newline at end of file +var commonAtoms=["And","Or"],commonKeywords=["If","Else","Elseif","Endif"],commonCommands=["AccessInfo","Background","Build","ClearAccessBlock","ClearRTCam","Config","ControllerDisable","ControllerEnable","DateTime","Debug","Dec","DeepSleep","DisablePriorityTask","DNS","DST","EraseSDKWiFi","ExecuteRules","FactoryReset","Gateway","I2Cscanner","Inc","IP","Latitude","Let","LetStr","Load","LogEntry","LogPortStatus","Longitude","LoopTimerSet","LoopTimerSet_ms","LoopTimerSetAndRun","LoopTimerSetAndRun_ms","MemInfo","MemInfoDetail","Name","NetworkDisable","NetworkEnable","Password","PostToHTTP","PostToHTTPS","Publish","PublishR","PutToHTTP","PutToHTTPS","Reboot","Save","SendTo","SendToHTTP","SendToHTTPS","SendToUDP","SendToUDPMix","Settings","Subnet","Subscribe","TaskClear","TaskClearAll","TaskDisable","TaskEnable","TaskRun","TaskValueSet","TaskValueSetAndRun","TaskValueSetDerived","TaskValueSetPresentation","TimerPause","TimerResume","TimerSet","TimerSet_ms","TimeZone","UdpPort","UdpTest","Unit","UseNTP","WdConfig","WdRead","WiFi","WiFiAllowAP","WiFiAPMode","WiFiConnect","WiFiDisconnect","WiFiKey","WiFiKey2","WiFiMode","WiFiScan","WiFiSSID","WiFiSSID2","WiFiSTAMode","WriteEE","WriteRTC","Event","AsyncEvent","GPIO","GPIOToggle","LongPulse","LongPulse_mS","Monitor","Pulse","PWM","Servo","Status","Tone","RTTTL","UnMonitor","Provision","Provision,Config","Provision,Security","Provision,Notification","Provision,Provision","Provision,Rules","Provision,CustomCdnUrl","Provision,Firmware"],commonEvents=["Clock#Time","JsonReply","JsonReply#","Login#Failed","MQTT#Connected","MQTT#Disconnected","MQTTimport#Connected","MQTTimport#Disconnected","OpenMeteo#current","OpenMeteo#daily","OpenMeteo#hourly","Rules#Timer","System#Boot","ReadEE#","ReadRTC#","System#BootMode","System#Sleep","System#Wake","TaskExit#","TaskInit#","ThingspeakReply","Time#Initialized","Time#Set","WiFi#APmodeDisabled","WiFi#APmodeEnabled","WiFi#ChangedAccesspoint","WiFi#ChangedWiFichannel","WiFi#Connected","WiFi#Disconnected"],commonPlugins=["ResetPulseCounter","SetPulseCounterTotal","LogPulseStatistic","analogout","MCPGPIO","MCPGPIOToggle","MCPLongPulse","MCPLongPulse_ms","MCPPulse","Status,MCP","Monitor,MCP","MonitorRange,MCP","UnMonitorRange,MCP","UnMonitor,MCP","MCPGPIORange","MCPGPIOPattern","MCPMode","MCPModeRange","ExtGpio","ExtPwm","ExtPulse","ExtLongPulse","Status,EXT,","LCDCmd","LCD","PCFGPIO","PCFGPIOToggle","PCFLongPulse","PCFLongPulse_ms","PCFPulse","Status,PCF","Monitor,PCF","MonitorRange,PCF","UnMonitorRange,PCF","UnMonitor,PCF","PCFGPIORange","PCFGPIOpattern","PCFMode","PCFmodeRange","SerialSend","SerialSendMix","Ser2NetClientSend","SerialSend_test","pcapwm","pcafrq","mode2","OLED","OLEDCMD","OLEDCMD,on","OLEDCMD,off","OLEDCMD,clear","IRSEND","IRSENDAC","OledFramedCmd","OledFramedCmd,Display","OledFramedCmd,low","OledFramedCmd,med","OledFramedCmd,high","OledFramedCmd,Frame","OledFramedCmd,linecount","OledFramedCmd,leftalign","OledFramedCmd,align","OledFramedCmd,userDef1","OledFramedCmd,userDef2","NeoPixel","NeoPixelAll","NeoPixelLine","NeoPixelHSV","NeoPixelAllHSV","NeoPixelLineHSV","NeoPixelBright","MotorShieldCmd,DCMotor","MotorShieldCmd,Stepper","MHZCalibrateZero","MHZReset","MHZABCEnable","MHZABCDisable","Sensair_SetRelay","PMSX003","PMSX003,Wake","PMSX003,Sleep","PMSX003,Reset","encwrite","Play","Vol","Eq","Mode","Repeat","tareChanA","tareChanB","7dn","7dst","7dsd","7dtext","7ddt","7dt","7dtfont","7dtbin","7don","7doff","7output","HLWCalibrate","HLWReset","csecalibrate","cseclearpulses","csereset","WemosMotorShieldCMD","LolinMotorShieldCMD","GPS","GPS,Sleep","GPS,Wake","GPS#GotFix","GPS#LostFix","GPS#Travelled","homieValueSet","SerialProxy_Write","SerialProxy_WriteMix","SerialProxy_Test","HeatPumpir","MitsubishiHP","MitsubishiHP,temperature","MitsubishiHP,power","MitsubishiHP,mode","MitsubishiHP,fan","MitsubishiHP,vane","MitsubishiHP,widevane","Culreader_Write","Touch","Touch,Rot","Touch,Flip","Touch,Enable","Touch,Disable","Touch,On","Touch,Off","Touch,Toggle","Touch,Setgrp","Touch,Incgrp","Touch,Decgrp","Touch,Incpage","Touch,Decpage","Touch,Updatebutton","WakeOnLan","DotMatrix","DotMatrix,clear","DotMatrix,update","DotMatrix,size","DotMatrix,txt","DotMatrix,settxt","DotMatrix,content","DotMatrix,alignment","DotMatrix,anim.in","DotMatrix,anim.out","DotMatrix,speed","DotMatrix,pause","DotMatrix,font","DotMatrix,layout","DotMatrix,inverted","DotMatrix,specialeffect","DotMatrix,offset","DotMatrix,brightness","DotMatrix,repeat","DotMatrix,setbar","DotMatrix,bar","Thermo","Thermo,Up","Thermo,Down","Thermo,Mode","Thermo,ModeBtn","Thermo,Setpoint","Max1704xclearalert","scdgetabc","scdgetalt","scdgettmp","scdsetcalibration","scdsetfrc","scdgetinterval","multirelay","multirelay,on","multirelay,off","multirelay,set","multirelay,get","multirelay,loop","ShiftOut","ShiftOut,Set","ShiftOut,SetNoUpdate","ShiftOut,Update","ShiftOut,SetAll","ShiftOut,SetAllNoUpdate","ShiftOut,SetAllLow","ShiftOut,SetAllHigh","ShiftOut,SetChipCount","ShiftOut,SetHexBin","cdmrst","nfx","nfx,off","nfx,on","nfx,dim","nfx,line,","nfx,hsvline,","nfx,one,","nfx,hsvone,","nfx,all,","nfx,rgb,","nfx,fade,","nfx,hsv,","nfx,colorfade,","nfx,rainbow","nfx,kitt,","nfx,comet,","nfx,theatre,","nfx,scan,","nfx,dualscan,","nfx,twinkle,","nfx,twinklefade,","nfx,sparkle,","nfx,wipe,","nfx,dualwipe","nfx,fire","nfx,fireflicker","nfx,faketv","nfx,simpleclock","nfx,stop","nfx,statusrequest","nfx,fadetime,","nfx,fadedelay,","nfx,speed,","nfx,count,","nfx,bgcolor","ShiftIn","ShiftIn,PinEvent","ShiftIn,ChipEvent","ShiftIn,SetChipCount","ShiftIn,SampleFrequency","ShiftIn,EventPerPin","scd4x","scd4x,storesettings","scd4x,facoryreset","scd4x,selftest","scd4x,setfrc,","axp","axp,ldo2","axp,ldo3","axp,ldoio","axp,gpio0","axp,gpio1","axp,gpio2","axp,gpio3","axp,gpio4","axp,dcdc2","axp,dcdc3","axp,ldo2map","axp,ldo3map","axp,ldoiomap","axp,dcdc2map","axp,dcdc3map","axp,ldo2perc","axp,ldo3perc","axp,ldoioperc","axp,dcdc2perc","axp,dcdc3perc","I2CEncoder","I2CEncoder,bright","I2CEncoder,led1","I2CEncoder,led2","I2CEncoder,gain","I2CEncoder,set","cachereader","cachereader,readpos","cachereader,sendtaskinfo","cachereader,flush","tm1621","tm1621,write,","tm1621,writerow,","tm1621,voltamp,","tm1621,energy,","tm1621,celcius,","tm1621,fahrenheit,","tm1621,humidity,","tm1621,raw,","dac","dac,1","dac,2","sht4x","sht4x,startup","ld2410","ld2410,factoryreset","ld2410,logall","digipot","digipot,reset","digipot,shutdown","digipot,","7dextra","7dbefore","7dgroup","7digit","7color","7digitcolor","7groupcolor","gp8403","gp8403,volt,","gp8403,mvolt,","gp8403,range,","gp8403,preset,","gp8403,init,","sen5x","sen5x,startclean","sen5x,techlog,","as3935","as3935,clearstats","as3935,calibrate","as3935,setgain,","as3935,setnf,","as3935,setwd,","as3925,setsrej,","lu9685","lu9685,servo,","lu9685,enable,","lu9685,disable,","lu9685,setrange,","geni2c","geni2c,cmd,","geni2c,exec,","geni2c,log,"],pluginDispKind=["tft","ili9341","ili9342","ili9481","ili9486","ili9488","epd","eink","epaper","il3897","uc8151d","ssd1680","ws2in7","ws1in54","st77xx","st7735","st7789","st7796","neomatrix","neo","pcd8544"],pluginDispCmd=["cmd,on","cmd,off","cmd,clear","cmd,backlight","cmd,bright","cmd,deepsleep","cmd,seq_start","cmd,seq_end","cmd,inv","cmd,rot",",clear",",rot",",tpm",",txt",",txp",",txz",",txc",",txs",",txtfull",",asciitable",",font",",l",",lh",",lv",",lm",",lmr",",r",",rf",",c",",cf",",rf",",t",",tf",",rr",",rrf",",px",",pxh",",pxv",",bmp",",btn",",win",",defwin",",delwin"],commonTag=["On","Do","Endon"],commonNumber=["toBin","toHex","Constrain","XOR","AND:","OR:","Ord","bitRead","bitSet","bitClear","bitWrite","urlencode"],commonMath=["Log","Ln","Abs","Exp","Sqrt","Sq","Round","Sin","Cos","Tan","aSin","aCos","aTan","aTan2","Sin_d","Cos_d","Tan_d","aSin_d","aCos_d","aTan_d","aTan2_d","map","mapc","fmod"],commonWarning=["delay","Delay","ResetFlashWriteCounter"],taskSpecifics=["settings.Enabled","settings.Interval","settings.ValueCount","settings.Controller1.Enabled","settings.Controller2.Enabled","settings.Controller3.Enabled","settings.Controller1.Idx","settings.Controller2.Idx","settings.Controller3.Idx"],AnythingElse=["%eventvalue%","%eventpar%","%eventname%","%sysname%","%bootcause%","%systime%","%systm_hm%","%systm_hm_0%","%systm_hm_sp%","%systime_am%","%systime_am_0%","%systime_am_sp%","%systm_hm_am%","%systm_hm_am_0%","%systm_hm_am_sp%","%lcltime%","%sunrise%","%s_sunrise%","%m_sunrise%","%sunset%","%s_sunset%","%m_sunset%","%lcltime_am%","%latitude%","%longitude%","%syshour%","%syshour_0%","%sysmin%","%sysmin_0%","%syssec%","%syssec_0%","%sysday%","%sysday_0%","%sysmonth%","%sysmonth_0%","%systzoffset%","%systzoffset_s%","%sysyear%","%sysyear_0%","%sysyears%","%sysweekday%","%sysweekday_s%","%unixtime%","%unixtime_lcl%","%uptime%","%uptime_ms%","%rssi%","%ip%","%unit%","%unit_0%","%ssid%","%bssid%","%wi_ch%","%iswifi%","%vcc%","%mac%","%mac_int%","%isntp%","%ismqtt%","%dns%","%dns1%","%dns2%","%flash_freq%","%flash_size%","%flash_chip_vendor%","%flash_chip_model%","%fs_free%","%fs_size%","%cpu_id%","%cpu_freq%","%cpu_model%","%cpu_rev%","%cpu_cores%","%board_name%","%inttemp%","%islimited_build%","%isvar_double%","substring","lookup","indexOf","indexOf_ci","equals","equals_ci","strtol","timeToMin","timeToSec","%ethwifimode%","%ethconnected%","%ethduplex%","%ethspeed%","%ethstate%","%ethspeedstate%","%c_w_dir%","%c_c2f%","%c_ms2Bft%","%c_dew_th%","%c_alt_pres_sea%","%c_sea_pres_alt%","%c_cm2imp%","%c_isnum%","%c_mm2imp%","%c_m2day%","%c_m2dh%","%c_m2dhm%","%c_s2dhms%","%c_ts2date%","%c_ts2isodate%","%c_ts2wday%","%c_random%","%c_2hex%","%c_u2ip%","%c_uname%","%c_uage%","%c_ubuild%","%c_ubuildstr%","%c_uload%","%c_utype%","%c_utypestr%","%c_strf%","%c_d2r%","%c_r2d%","%SP%","%CR%","%LF%","%N%","%R%","%e%","%pi%","var","int","str","length"];for(const e of pluginDispKind)commonPlugins=commonPlugins.concat(e);for(const e of pluginDispKind)for(const t of pluginDispCmd){let n=e+t;commonPlugins=commonPlugins.concat(n)}var rEdit,EXTRAWORDS=commonAtoms.concat(commonPlugins,commonKeywords,commonCommands,commonEvents,commonTag,commonNumber,commonMath,commonWarning,taskSpecifics,AnythingElse),confirmR=!0,android=/Android/.test(navigator.userAgent);function initCM(){function e(e){}android&&(confirmR=!!confirm("Do you want to enable colored rules on your Android device?\nThis feature hasn't been fully tested yet and may still have some issues.\nIt is currently expected to work with Chrome, Firefox, and Vivaldi.\nPlease report any problems you encounter.")),confirmR&&(CodeMirror.commands.autocomplete=function(e){e.showHint({hint:CodeMirror.hint.anyword})},(rEdit=CodeMirror.fromTextArea(document.getElementById("rules"),{tabSize:2,indentWithTabs:!1,lineNumbers:!0,autoCloseBrackets:!0,extraKeys:{"Ctrl-Space":"autocomplete",Tab:e=>{"null"===e.getMode().name?e.execCommand("insertTab"):e.somethingSelected()?e.execCommand("indentMore"):e.execCommand("insertSoftTab")},"Shift-Tab":e=>e.execCommand("indentLess")}})).on("change",(function(){rEdit.save()})),android||rEdit.on("inputRead",(function(e,t){var n=e.getCursor(),o=e.getTokenAt(n);/[\w%,.]/.test(t.text)&&"comment"!=o.type&&e.showHint({completeSingle:!1})})),CodeMirror.keyMap.default["Ctrl-F"]=function(e){openFind()},CodeMirror.keyMap.default["Cmd-F"]=function(e){openFind()},CodeMirror.keyMap.default["Ctrl-G"]=e,CodeMirror.keyMap.default["Cmd-G"]=e,CodeMirror.keyMap.default["Shift-Ctrl-G"]=e,CodeMirror.keyMap.default["Shift-Cmd-G"]=e,CodeMirror.keyMap.default["Ctrl-H"]=e,CodeMirror.keyMap.default["Cmd-H"]=e,CodeMirror.keyMap.default["Shift-Ctrl-F"]=e,CodeMirror.keyMap.default["Shift-Cmd-F"]=e,CodeMirror.keyMap.default["Ctrl-Shift-R"]=e,CodeMirror.keyMap.default["Cmd-Shift-R"]=e)}function closeSearchDialog(){const e=document.querySelectorAll(".CodeMirror-dialog");e.length>0&&(e.forEach((e=>e.remove())),document.body.classList.remove("dialog-opened")),rEdit.execCommand("clearSearch")}function removeHighlight(){requestAnimationFrame((()=>{document.querySelectorAll(".search-next-highlight").forEach((e=>e.classList.remove("search-next-highlight")))}))}let findDialogObserver=null;function openFind(){findDialogObserver&&(document.querySelectorAll(".CodeMirror-dialog").forEach((e=>e.remove())),findDialogObserver.disconnect(),findDialogObserver=null),findDialogObserver=new MutationObserver((()=>{document.querySelector(".CodeMirror-dialog")||(removeHighlight(),findDialogObserver.disconnect(),findDialogObserver=null)})),findDialogObserver.observe(document.body,{childList:!0,subtree:!0}),clearSearchNextHighlight(rEdit),rEdit.execCommand("findPersistent"),addFindButtons()}function clearSearchNextHighlight(e){removeHighlight(),e.__searchNextHighlight&&(e.__searchNextHighlight.clear(),e.__searchNextHighlight=null)}function addFindButtons(){document.querySelector(".CodeMirror-selected");const e=document.querySelector(".CodeMirror-dialog");if(!e||e.querySelector(".search-button-group"))return;[{title:"Find Previous",symbol:"▲",action:()=>rEdit.execCommand("findPersistentPrev")},{title:"Find Next",symbol:"▼",action:()=>rEdit.execCommand("findPersistentNext")},{title:"Replace",symbol:"Replace",action:()=>{closeSearchDialog(),rEdit.execCommand("replace"),addFindButtons()}},{title:"Close",symbol:"❌",action:closeSearchDialog},{title:"Help",symbol:"?",action:()=>{alert("Available shortcuts:\n• Ctrl+F / Cmd+F: Open search\n• Enter: Find next\n• Shift+Enter: Find previous\n• Use /re/ syntax for regex search")}}].forEach((({title:t,symbol:n,action:o})=>{const i=document.createElement("span");i.title=t,i.className="help"===t.toLowerCase()?"button help":"button",i.innerHTML=n,i.style.cssText="\n cursor: pointer;\n user-select: none;\n ",i.addEventListener("click",(e=>{e.preventDefault(),o()})),e.appendChild(i)}))}function triggerFormatting(){let e,t,n,o,i;if(confirmR){const r=rEdit.getDoc();e=rEdit.getScrollInfo(),t=r.getCursor(),n=0===t.ch?t.line-1:t.line,o=rEdit.getLine(n)||"",i=rEdit.getValue()}else i=document.getElementById("rules").value;if(i=initalAutocorrection(i),i=formatLogic(i),confirmR){rEdit.setValue(i);const r=n,s=0===t.ch&&o.length>0?o.length:t.ch;rEdit.setCursor({line:r,ch:s}),setTimeout((()=>{rEdit.scrollTo(e.left,e.top),rEdit.focus()}),0),rEdit.save()}else document.getElementById("rules").value=i}function initalAutocorrection(e){for(const t of EXTRAWORDS)if("Do"===t){const t=/(^|\s)(do)(\s*)(\/\/.*)?$/gim;e=e.replace(t,((e,t,n,o,i)=>`${t}Do${o}${i??""}`))}else{const n=new RegExp(`^\\s*\\b${t}\\b`,"gmi");e=e.replace(n,(e=>e.replace(new RegExp(t,"i"),t)))}return e}function formatLogic(e){const t=" ",n=e.split("\n").map((e=>{const t=e.trimStart();return t.startsWith("//")?e:t})),o=[],i=[];let r=!1,s=null,a=[],l=[];function c(e){return e.trim().startsWith("//")}function d(e){return""===e.trim()}function m(e){return e.trim().toLowerCase().startsWith("on")}function u(e){return e.trim().toLowerCase().endsWith("do")}function f(e){return"endon"===e.trim().toLowerCase()}function h(e){return e.trim().toLowerCase().startsWith("if")}function p(e){return"else"===e.trim().toLowerCase()}function g(e){return e.trim().toLowerCase().startsWith("elseif")}function C(e){return"endif"===e.trim().toLowerCase()}let x=0;function S(){a.length>0&&(i.push(`• Missing ${a.length} Endif(s):`),i.push(` - Unclosed If block(s) starting at line(s): ${l.join(", ")}`)),a=[],l=[]}for(let e=0;e0){const e=extractFirstErrorLine(i);if(alert("Errors found:\n"+i.join("\n")),!isNaN(e))if(confirmR)setTimeout((()=>{jumpToLine(e)}),50);else{const t=document.getElementById("rules");setTimeout((()=>{jumpToLineInTextarea(t,e)}),50)}}return o.join("\n")}function jumpToLine(e){const t=Math.max(0,e-1);rEdit.setCursor({line:t,ch:0}),rEdit.focus(),rEdit.scrollIntoView({line:t,ch:0},100)}function extractFirstErrorLine(e){for(const t of e){let e=t.match(/• Line (\d+)/);if(e)return parseInt(e[1]);if(e=t.match(/starting at line (\d+)/),e)return parseInt(e[1]);if(e=t.match(/starting at line\(s\):\s*(\d+)/),e)return parseInt(e[1])}return null}function jumpToLineInTextarea(e,t){const n=e.value.split("\n"),o=Math.max(1,Math.min(t,n.length));let i=0;for(let e=0;e{const e=document.getElementById("rulesselect");if(e){if(confirmR){const t=document.createElement("button");t.type="button",t.id="searchBtn",t.innerHTML="🔎︎",t.style.padding="2px 5px",t.className="button help",e.appendChild(t),t.addEventListener("click",(()=>{void 0!==rEdit&&openFind()}))}const t=document.createElement("button");t.type="button",t.id="formatBtn",t.textContent="Format",t.className="button",e.appendChild(t),t.addEventListener("click",(()=>{triggerFormatting()}))}let t="";if(document.addEventListener("keydown",(function(e){const n=e.key;(["Backspace","Delete","ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Enter","Tab","Escape","Shift","Control","Alt","Meta"].includes(n)||1!==n.length)&&(t="")})),android){var n=!1;rEdit.on("keydown",((e,o)=>{["Enter","Backspace"," "].includes(o.key)&&(t=""),n=!0}));let e="";const i=rEdit.getInputField();function o(o,i=!1){if(!(rEdit.hasFocus()&&rEdit&&o.data&&n))return;n=!1;const r=o.data,s=rEdit.getDoc(),a=s.getCursor(),l=rEdit.getTokenAt(a);if(" "===r)return t="",void(e="");if(!(r===e&&t.length>0))if(/[\w%,.]/.test(r)&&"comment"!==l.type){const n=a.ch<=1?r.slice(-1):r;t+=n,e=t,t.startsWith(String(a.line+1))&&0===a.ch&&(t=t.slice(String(a.line).length));const o={line:a.line,ch:a.ch-t.length+1},l=()=>{s.replaceRange(t,o,a),rEdit.setCursor({line:o.line,ch:o.ch+t.length}),rEdit.showHint({completeSingle:!1})};i?l():setTimeout(l,0)}else t=""}const r=navigator.userAgent.toLowerCase(),s=/firefox/.test(r),a=/chrome/.test(r)&&!s;s?i.addEventListener("beforeinput",(e=>{e.preventDefault(),o(e,!0)})):a&&document.addEventListener("input",(e=>{o(e,!1)})),rEdit.on("endCompletion",(function(){setTimeout((()=>{!function(){const e=document.createElement("input");e.type="text",e.style.position="absolute",e.style.opacity="0",e.style.height="0",e.style.width="0",e.style.border="none",e.style.top="0",e.style.left="-9999",e.style.padding="0",e.style.zIndex="-1",e.style.fontSize="16px",document.body.appendChild(e),e.focus(),setTimeout((()=>{e.remove(),rEdit.focus()}),10)}()}),100)}))}})),function(e){"object"==typeof exports&&"object"==typeof module?e(require("codemirror")):"function"==typeof define&&define.amd?define(["codemirror"],e):e(CodeMirror)}((function(e){"use strict";e.defineMode("espeasy",(function(){var e={};function t(t,n){for(var o=0;oe.toLowerCase()));commonCommands=commonCommands.concat(n);var o=commonEvents.map((e=>e.toLowerCase()));commonEvents=commonEvents.concat(o);var i=commonPlugins.map((e=>e.toLowerCase()));commonPlugins=commonPlugins.concat(i);var r=commonAtoms.map((e=>e.toLowerCase()));commonAtoms=commonAtoms.concat(r);var s=commonKeywords.map((e=>e.toLowerCase()));commonKeywords=commonKeywords.concat(s);var a=commonTag.map((e=>e.toLowerCase()));commonTag=commonTag.concat(a);var l=commonNumber.map((e=>e.toLowerCase()));commonNumber=commonNumber.concat(l);var c=commonMath.map((e=>e.toLowerCase()));commonMath=commonMath.concat(c);var d=AnythingElse.map((e=>e.toLowerCase()));AnythingElse=AnythingElse.concat(d);var m=taskSpecifics.map((e=>e.toLowerCase()));function u(t,n){if(t.eatSpace())return null;t.sol();var o=t.next();if(/\d/.test(o)){if("0"==o)return"x"===t.next()?(t.eatWhile(/\w/),"number"):(t.eatWhile(/\d|\./),"number");if(t.eatWhile(/\d|\./),!t.match("d")&&!t.match("output")&&(t.eol()||/\D/.test(t.peek())))return"number"}if(/\w/.test(o))for(const e of EXTRAWORDS){let n=e.substring(1);(e.includes(":")||e.includes(",")||e.includes("."))&&t.match(n)}if(/\w/.test(o)&&(t.eatWhile(/[\w]/),t.match(".gpio")||t.match(".pulse")||t.match(".frq")||t.match(".pwm")))return"def";if("\\"===o)return t.next(),null;if("("===o||")"===o)return"bracket";if("{"===o||"}"===o||":"===o)return"number";if("/"==o)return/\//.test(t.peek())?(t.skipToEnd(),"comment"):"operator";if("'"==o&&(t.eatWhile(/[^']/),t.match("'")))return"attribute";if("+"===o||"="===o||"<"===o||">"===o||"-"===o||","===o||"*"===o||"!"===o)return"operator";if("%"==o){if(/\d/.test(t.next()))return"number";if(t.eatWhile(/[^\s\%]/),t.match("%"))return"hr"}if("["==o&&(t.eatWhile(/[^\s\]]/),t.eat("]")))return"hr";t.eatWhile(/\w/);var i=t.current();return/\w/.test(o)&&t.match("#")?(t.eatWhile(/[\w.#]/),"events"):"#"===o?(t.eatWhile(/\w/),"number"):e.hasOwnProperty(i)?e[i]:null}function f(e,t){return(t.tokens[0]||u)(e,t)}return taskSpecifics=taskSpecifics.concat(m),t("atom",commonAtoms),t("keyword",commonKeywords),t("builtin",commonCommands),t("events",commonEvents),t("def",commonPlugins),t("tag",commonTag),t("number",commonNumber),t("bracket",commonMath),t("warning",commonWarning),t("hr",AnythingElse),t("comment",taskSpecifics),{startState:function(){return{tokens:[]}},token:function(e,t){return f(e,t)},closeBrackets:"[]{}''\"\"``()",lineComment:"//",fold:"brace"}}))})),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],mod):e(CodeMirror)}((function(e){var t={pairs:"()[]{}''\"\"",closeBefore:")]}'\":;>",triples:"",explode:"[]{}"},n=e.Pos;function o(e,n){return"pairs"==n&&"string"==typeof e?e:"object"==typeof e&&null!=e[n]?e[n]:t[n]}e.defineOption("autoCloseBrackets",!1,(function(t,n,s){s&&s!=e.Init&&(t.removeKeyMap(i),t.state.closeBrackets=null),n&&(r(o(n,"pairs")),t.state.closeBrackets=n,t.addKeyMap(i))}));var i={Backspace:function(t){var i=a(t);if(!i||t.getOption("disableInput"))return e.Pass;for(var r=o(i,"pairs"),s=t.listSelections(),l=0;l=0;l--){var m=s[l].head;t.replaceRange("",n(m.line,m.ch-1),n(m.line,m.ch+1),"+delete")}},Enter:function(t){var n=a(t),i=n&&o(n,"explode");if(!i||t.getOption("disableInput"))return e.Pass;for(var r=t.listSelections(),s=0;s1&&h.indexOf(i)>=0&&t.getRange(n(M.line,M.ch-2),M)==i+i){if(M.ch>2&&/\bstring/.test(t.getTokenTypeAt(n(M.line,M.ch-2))))return e.Pass;S="addFour"}else if(p){var b=0==M.ch?" ":t.getRange(n(M.line,M.ch-1),M);if(e.isWordChar(P)||b==i||e.isWordChar(b))return e.Pass;S="both"}else{if(!C||!(0===P.length||/\s/.test(P)||f.indexOf(P)>-1))return e.Pass;S="both"}else S=p&&m(t,M)?"both":h.indexOf(i)>=0&&t.getRange(M,n(M.line,M.ch+3))==i+i+i?"skipThree":"skip";if(u){if(u!=S)return e.Pass}else u=S}var T=d%2?s.charAt(d-1):i,v=d%2?i:s.charAt(d+1);t.operation((function(){if("skip"==u)l(t,1);else if("skipThree"==u)l(t,3);else if("surround"==u){for(var e=t.getSelections(),n=0;n0?{line:s.head.line,ch:s.head.ch+t}:{line:s.head.line-1};n.push({anchor:a,head:a})}e.setSelections(n,i)}function c(t){var o=e.cmpPos(t.anchor,t.head)>0;return{anchor:new n(t.anchor.line,t.anchor.ch+(o?-1:1)),head:new n(t.head.line,t.head.ch+(o?1:-1))}}function d(e,t){var o=e.getRange(n(t.line,t.ch-1),n(t.line,t.ch+1));return 2==o.length?o:null}function m(e,t){var o=e.getTokenAt(n(t.line,t.ch+1));return/\bstring/.test(o.type)&&o.start==t.ch&&(0==t.ch||!/\bstring/.test(e.getTokenTypeAt(t)))}r(t.pairs+"`")})); \ No newline at end of file From 2368a3cc418e760c17a7fbf636611136aefd7cf0 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 20:24:51 +0200 Subject: [PATCH 31/44] [Storage] Fix merge error --- src/src/Helpers/ESPEasy_checks.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index 575a5d966d..940df67801 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -190,10 +190,8 @@ void run_compiletime_checks() { // to determine nr of bits in a struct. static_assert(GPIO_DIRECTION_NR_BITS== NR_BITS(static_cast(gpio_direction::gpio_direction_MAX)), "Correct GPIO_DIRECTION_NR_BITS"); - #endif // ifndef LIMIT_BUILD_SIZE } -#ifndef LIMIT_BUILD_SIZE String ReportOffsetErrorInStruct(const String& structname, size_t offset) { String error; if (error.reserve(48 + structname.length())) { @@ -271,4 +269,4 @@ String checkTaskSettings(taskIndex_t taskIndex) { #endif return err; } -#endif \ No newline at end of file +#endif // ifndef LIMIT_BUILD_SIZE From c72df2778d792c638849cd932ae32ec16cccf783 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 20:25:20 +0200 Subject: [PATCH 32/44] [P020] Fix compiler warning --- src/src/PluginStructs/P020_data_struct.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index becf5a6d88..309d890fa4 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -378,9 +378,11 @@ void P020_Task::discardSerialIn() { // We can also use the rules engine for local control! void P020_Task::rulesEngine(const String& message, struct EventStruct *event) { if (!Settings.UseRules || message.isEmpty() || (P020_Events::None == serial_processing)) { return; } - int NewLinePos = 0; - uint16_t StartPos = 0; - bool eventSent = false; + int NewLinePos = 0; + uint16_t StartPos = 0; + # if FEATURE_STRING_VARIABLES + bool eventSent = false; + # endif // if FEATURE_STRING_VARIABLES NewLinePos = handleMultiLine ? message.indexOf('\n', StartPos) : message.length(); @@ -449,7 +451,9 @@ void P020_Task::rulesEngine(const String& message, struct EventStruct *event) { eventString += message.substring(StartPos, NewLinePos); } eventQueue.addMove(std::move(eventString)); + # if FEATURE_STRING_VARIABLES eventSent = true; + # endif // if FEATURE_STRING_VARIABLES break; } case P020_Events::P1WiFiGateway: // P1 WiFi Gateway @@ -474,7 +478,9 @@ void P020_Task::rulesEngine(const String& message, struct EventStruct *event) { if (!eventString.isEmpty()) { eventQueue.add(eventString); + # if FEATURE_STRING_VARIABLES eventSent = true; + # endif // if FEATURE_STRING_VARIABLES } NewLinePos = message.indexOf('\n', StartPos); From 76237a9b15119e2bbc29295c7f4d1830317e1b2f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 20:26:15 +0200 Subject: [PATCH 33/44] [Storage] Add FEATURE_ flags to Custom-sample.h --- src/Custom-sample.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 97e6daad1f..d6d740cdce 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -30,6 +30,8 @@ #define FEATURE_JSON_EVENT 0 // Generates an event with the values of a JSON repsonse of an HTTP call. Keys are stored in json.keys one key per line (e.g.: Body.Data.DAY_ENERGY.Values.1) // #define FEATURE_SD 1 // Enable SD card support // #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url +// #define FEATURE_EEPROM_EXTERNAL 1 // Enable support for AT24Cxxx EEPROM chips AT24C32(4kB)..AT24C1024(128kB), optional: AT24C2048(256kB) and FRAM MB85RC32(4kB)..MB85RC1M(128kB), optional: MB85RC2M(256kB) +// #define FEATURE_RTC_SRAM_STORAGE 1 // Enable storing values in supported RTC Clock chip SRAM (DS1307, DS3232) #ifdef BUILD_GIT # undef BUILD_GIT From 9a67cb254aabd4fd2ee031eb0e253d6b8b4cd2d2 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 21:54:42 +0200 Subject: [PATCH 34/44] [Storage] Code reduction and other improvements --- src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp | 56 +++++++++---------- src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp | 48 ++++++++-------- .../{EpromVarPage.cpp => EepromVarPage.cpp} | 0 src/src/WebServer/EepromVarPage.h | 2 +- 4 files changed, 51 insertions(+), 55 deletions(-) rename src/src/WebServer/{EpromVarPage.cpp => EepromVarPage.cpp} (100%) diff --git a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp index 0a43f5918c..3339a43cfb 100644 --- a/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp +++ b/src/ESPEasy/eeprom/Helpers/EEPROMExternal.cpp @@ -1,11 +1,12 @@ #include "../Helpers/EEPROMExternal.h" -#include "../../../src/Globals/Settings.h" -#include "../../../src/Helpers/I2C_access.h" -#include "../../../ESPEasy_common.h" -#include "../../../src/Helpers/StringConverter.h" - #if FEATURE_EEPROM_EXTERNAL +# include "../../../src/Globals/Settings.h" +# include "../../../src/Helpers/I2C_access.h" +# include "../../../ESPEasy_common.h" +# include "../../../src/Helpers/StringConverter.h" + + namespace ESPEasy { namespace eeprom { AT24CX *EEPROMExternal = nullptr; @@ -31,7 +32,7 @@ void initializeEEPROMExternal() { const ESPEasy::eeprom::EEPROMExternal_Type_e eepromType = static_cast(Settings.EEPROMExternalType()); - if (0 != ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM + if (ESPEasy::eeprom::selectEEPROMI2CBusAndMultiplexer()) { // Switch to I2C Bus and multiplexer channel of External EEPROM // We have an I2C device at this address, let's assume it's an EEPROM... uint8_t pageSize = 0; const uint32_t eepromSize = ESPEasy::eeprom::getEEPROMSize(eepromType, pageSize); @@ -120,10 +121,10 @@ void updateEEPROMExternalParameters() { uint8_t checkEEPROMEnabled() { const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); - if ((nullptr != EEPROMExternal) && (eepromAddress > 0)) { // EEPROM Configured? + if ((nullptr != EEPROMExternal) && eepromAddress) { // EEPROM Configured? return eepromAddress; } - return 0; + return (uint8_t)0; } /** @@ -177,7 +178,7 @@ bool isEEPROMExternalWriteProtected() { return EEPROMExternal_WriteProtect_e: uint8_t selectEEPROMI2CBusAndMultiplexer() { const uint8_t eepromAddress = Settings.EEPROMExternalI2CAddress(); - if (eepromAddress > 0) { // EEPROM Configured? + if (eepromAddress) { // EEPROM Configured? # if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = Settings.getI2CInterfaceEEPROM(); # else // if FEATURE_I2C_MULTIPLE @@ -197,7 +198,7 @@ uint8_t selectEEPROMI2CBusAndMultiplexer() { return eepromAddress; } } - return 0; + return (uint8_t)0; } /** @@ -232,7 +233,7 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { case EEPROMExternal_Type_e::MB85RC128: return 16384ul; } - return 0; + return 0ul; } /** @@ -240,35 +241,34 @@ uint32_t getEEPROMSize(EEPROMExternal_Type_e type) { */ uint32_t getEEPROMSize(EEPROMExternal_Type_e type, uint8_t & pageSize) { - pageSize = 0; + pageSize = (uint8_t)0; switch (type) { case EEPROMExternal_Type_e::AT24C256: case EEPROMExternal_Type_e::MB85RC256: - pageSize = 64u; - case EEPROMExternal_Type_e::AT24C512: - case EEPROMExternal_Type_e::MB85RC512: - pageSize = 128u; + case EEPROMExternal_Type_e::AT24C128: + case EEPROMExternal_Type_e::MB85RC128: + pageSize = (uint8_t)64; + break; # if EEPROM_SUPPORT_AT24C1024 case EEPROMExternal_Type_e::AT24C1024: case EEPROMExternal_Type_e::MB85RC1M: - pageSize = 128u; # endif // if EEPROM_SUPPORT_AT24C1024 # if EEPROM_SUPPORT_AT24C2048 case EEPROMExternal_Type_e::AT24C2048: case EEPROMExternal_Type_e::MB85RC2M: - pageSize = 128u; # endif // if EEPROM_SUPPORT_AT24C2048 + case EEPROMExternal_Type_e::AT24C512: + case EEPROMExternal_Type_e::MB85RC512: + pageSize = (uint8_t)128; + break; case EEPROMExternal_Type_e::AT24C32: case EEPROMExternal_Type_e::MB85RC32: - pageSize = 32u; case EEPROMExternal_Type_e::AT24C64: case EEPROMExternal_Type_e::MB85RC64: - pageSize = 32u; - case EEPROMExternal_Type_e::AT24C128: - case EEPROMExternal_Type_e::MB85RC128: - pageSize = 64u; + pageSize = (uint8_t)32; + break; } return getEEPROMSize(type); } @@ -323,10 +323,10 @@ const __FlashStringHelper* getEEPROMName(EEPROMExternal_Type_e type) { * EEPROM address for slot or 0xFFFF when error */ uint32_t getEEPROMAddressForSlot(uint32_t slot) { - if (checkEEPROMEnabled() > 0) { + if (checkEEPROMEnabled()) { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); - if ((eepromSize > 0) && (slot < getEEPROMMaxSlots())) { + if (eepromSize && (slot < getEEPROMMaxSlots())) { const uint32_t slotAddr = EEPROM_CUSTOM_START_OFFSET + (slot * sizeof_eeprom_slot); if (slotAddr < eepromSize) { @@ -341,16 +341,16 @@ uint32_t getEEPROMAddressForSlot(uint32_t slot) { * EEPROM available number of slots, max use all available space minus some administrative bytes */ uint32_t getEEPROMMaxSlots() { - if (checkEEPROMEnabled() > 0) { + if (checkEEPROMEnabled()) { const uint32_t eepromSize = getEEPROMSize(static_cast(Settings.EEPROMExternalType())); - if (eepromSize > 0) { + if (eepromSize) { const uint32_t slotMax = (unsigned long)(((eepromSize - EEPROM_CUSTOM_START_OFFSET) / EEPROM_CUSTOM_DIVISOR) / sizeof_eeprom_slot); return slotMax; } } - return 0; + return 0ul; } /** diff --git a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp index 484171bbf4..25bf0cc312 100644 --- a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp +++ b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp @@ -1,25 +1,21 @@ #include "../Helpers/RTCSRAMStorage.h" -#include "../../../src/Globals/Settings.h" -#include "../../../src/Helpers/I2C_access.h" -#include "../../../ESPEasy_common.h" -#include "../../../src/Helpers/StringConverter.h" -#include "../../../src/DataTypes/TimeSource.h" -#include - #if FEATURE_RTC_SRAM_STORAGE +# include "../../../src/Globals/Settings.h" +# include "../../../src/Helpers/I2C_access.h" +# include "../../../ESPEasy_common.h" +# include "../../../src/Helpers/StringConverter.h" +# include "../../../src/DataTypes/TimeSource.h" +# include + namespace ESPEasy { namespace eeprom { -# if FEATURE_SRAM_STORAGE_DOUBLE -constexpr uint32_t sizeof_rtcsram_slot = sizeof(double); -# else // if FEATURE_SRAM_STORAGE_DOUBLE -constexpr uint32_t sizeof_rtcsram_slot = sizeof(float); -# endif // if FEATURE_SRAM_STORAGE_DOUBLE +constexpr uint32_t sizeof_rtcsram_slot = sizeof(SRAM_STORAGE_FLOAT_TYPE); /** * Check if the RTC is properly initialized and enabled. - * Returns the external rtc type if all is OK + * Returns the external rtc type if SRAM is available */ uint8_t checkRTCSRAMEnabled() { const ExtTimeSource_e extRtcType = static_cast(Settings.ExternalTimeSource); @@ -28,7 +24,7 @@ uint8_t checkRTCSRAMEnabled() { (ExtTimeSource_e::DS3232 == extRtcType)) { // RTC with SRAM Configured? return Settings.ExternalTimeSource; } - return 0; + return (uint8_t)0; } /** @@ -47,9 +43,9 @@ uint8_t getRTCI2CAddress() { case ExtTimeSource_e::PCF8523: case ExtTimeSource_e::PCF8563: case ExtTimeSource_e::None: - return 0; + break; } - return 0; + return (uint8_t)0; } @@ -59,7 +55,7 @@ uint8_t getRTCI2CAddress() { uint8_t selectRTCSRAMI2CBus() { const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); - if (checkRTCSRAMEnabled() > 0) { // RTC Module Configured? + if (checkRTCSRAMEnabled()) { // RTC Module Configured? # if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = Settings.getI2CInterfaceRTC(); # else // if FEATURE_I2C_MULTIPLE @@ -85,7 +81,7 @@ uint8_t selectRTCSRAMI2CBus() { return Settings.ExternalTimeSource; } } - return 0; + return (uint8_t)0; } /** @@ -104,19 +100,19 @@ uint32_t getRTCSRAMSize() { case ExtTimeSource_e::PCF8523: case ExtTimeSource_e::PCF8563: case ExtTimeSource_e::None: - return 0; + break; } - return 0; + return 0ul; } /** * RTC SRAM _relative_ address for slot or 0xFFFF when error */ uint32_t getRTCSRAMAddressForSlot(uint32_t slot) { - if (checkRTCSRAMEnabled() > 0) { + if (checkRTCSRAMEnabled()) { const uint32_t rtcSramSize = getRTCSRAMSize(); - if ((rtcSramSize > 0) && (slot < getRTCSRAMMaxSlots())) { + if (rtcSramSize && (slot < getRTCSRAMMaxSlots())) { const uint32_t slotAddr = slot * sizeof_rtcsram_slot; if (slotAddr < rtcSramSize) { @@ -131,16 +127,16 @@ uint32_t getRTCSRAMAddressForSlot(uint32_t slot) { * EEPROM available number of slots, max use all available space minus some administrative bytes */ uint32_t getRTCSRAMMaxSlots() { - if (checkRTCSRAMEnabled() > 0) { + if (checkRTCSRAMEnabled()) { const uint32_t rtcSramSize = getRTCSRAMSize(); - if (rtcSramSize > 0) { + if (rtcSramSize) { const uint32_t slotMax = (uint32_t)(rtcSramSize / sizeof_rtcsram_slot); return slotMax; } } - return 0; + return 0ul; } /** @@ -183,7 +179,7 @@ bool writeRTCSRAMSlot(uint32_t slot, case ExtTimeSource_e::PCF8523: case ExtTimeSource_e::PCF8563: case ExtTimeSource_e::None: - return false; + break; } } diff --git a/src/src/WebServer/EpromVarPage.cpp b/src/src/WebServer/EepromVarPage.cpp similarity index 100% rename from src/src/WebServer/EpromVarPage.cpp rename to src/src/WebServer/EepromVarPage.cpp diff --git a/src/src/WebServer/EepromVarPage.h b/src/src/WebServer/EepromVarPage.h index e0bfac0213..366b3a5bac 100644 --- a/src/src/WebServer/EepromVarPage.h +++ b/src/src/WebServer/EepromVarPage.h @@ -1,7 +1,7 @@ #pragma once -#if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE # include "../WebServer/common.h" +#if FEATURE_EEPROM_EXTERNAL || FEATURE_RTC_SRAM_STORAGE void handle_eepromvars(); From 011fa27a6816066ec587e3c0ef218ae0b95efc48 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 21:55:32 +0200 Subject: [PATCH 35/44] [Storage] RTC SRAM Show available slots on Advanced page --- src/src/WebServer/AdvancedConfigPage.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 30e51c2c58..62392f311d 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -10,6 +10,9 @@ #include "../../ESPEasy/net/wifi/ESPEasyWifi.h" +#if FEATURE_RTC_SRAM_STORAGE +#include "../../ESPEasy/eeprom/Helpers/RTCSRAMStorage.h" +#endif // if FEATURE_RTC_SRAM_STORAGE #include "../Globals/ESPEasy_time.h" #include "../Globals/Settings.h" @@ -172,7 +175,8 @@ void handle_advanced() { } } - addHtml(F("
")); + html_add_form(); + html_table_class_normal(); addFormHeader(F("Advanced Settings"), F("RTDTools/Tools.html#advanced")); @@ -200,6 +204,12 @@ void handle_advanced() { if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { addFormNote(concat(getLabel(LabelType::EXT_RTC_UTC_TIME), F(": ")) + getValue(LabelType::EXT_RTC_UTC_TIME)); } + #if FEATURE_RTC_SRAM_STORAGE + if (ESPEasy::eeprom::checkRTCSRAMEnabled()) { + addRowLabel(F("'WriteRTC' slots available")); + addHtmlInt(ESPEasy::eeprom::getRTCSRAMMaxSlots()); + } + #endif // if FEATURE_RTC_SRAM_STORAGE #if FEATURE_I2C_MULTIPLE { const uint8_t i2cBus = Settings.getI2CInterfaceRTC(); From 995a71dea817531fe49aab84c60aef3478e21b3e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 21:56:49 +0200 Subject: [PATCH 36/44] [Storage] Small form fixes --- src/src/WebServer/DevicesPage.cpp | 27 ++++++++++++++------------- src/src/WebServer/HardwarePage.cpp | 7 +++---- src/src/WebServer/InterfacesPage.cpp | 3 ++- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index a54632cfd8..c23acb1f02 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -587,11 +587,12 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) for (taskIndex_t x = (page - 1) * TASKS_PER_PAGE; x < ((page) * TASKS_PER_PAGE) && validTaskIndex(x); x++) { const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); - const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); + const pluginID_t pid = Settings.getPluginID_for_task(x); + const bool pluginID_set = INVALID_PLUGIN_ID != pid; html_TR_TD(); - if (pluginID_set && !supportedPluginID(Settings.getPluginID_for_task(x))) { + if (pluginID_set && !supportedPluginID(pid)) { html_add_button_prefix(F("red"), true); } else { html_add_button_prefix(); @@ -621,7 +622,7 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) addEnabled(Settings.TaskDeviceEnabled[x] && validDeviceIndex(DeviceIndex)); html_TD(); - addHtml(getPluginNameFromPluginID(Settings.getPluginID_for_task(x))); + addHtml(getPluginNameFromPluginID(pid)); html_TD(); addHtml(getTaskDeviceName(x)); html_TD(); @@ -857,7 +858,7 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) for (uint8_t varNr = 0; varNr < valueCount; varNr++) { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) + if (validPluginID_fullcheck(pid)) { # if FEATURE_TASKVALUE_UNIT_OF_MEASURE const uint8_t uomIndex = Cache.getTaskVarUnitOfMeasure(x, varNr); @@ -1102,11 +1103,13 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) addHtml(F("Device:")); + const pluginID_t pid = Settings.getPluginID_for_task(taskIndex); + // no (supported) device selected, this effectively checks for validDeviceIndex - if (!supportedPluginID(Settings.getPluginID_for_task(taskIndex))) + if (!supportedPluginID(pid)) { // takes lots of memory/time so call this only when needed. - addDeviceSelect(F("TDNUM"), Settings.getPluginID_for_task(taskIndex)); // ="taskdevicenumber" + addDeviceSelect(F("TDNUM"), pid); // ="taskdevicenumber" addFormSeparator(4); } @@ -1119,18 +1122,16 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) addHtml(F("'); // show selected device name and delete button addHtml(getPluginNameFromDeviceIndex(DeviceIndex)); - const uint8_t pid = Settings.getPluginID_for_task(taskIndex).value; - - if (pid <= 79) { // Up to P079 seem to be listed in the old Wiki (and a few incomplete pages), so lets keep pointing there too - addHelpButton(concat(F("Plugin"), Settings.getPluginID_for_task(taskIndex).value)); + if (pid.value <= 79) { // Up to P079 seem to be listed in the old Wiki (and a few incomplete pages), so lets keep pointing there too + addHelpButton(concat(F("Plugin"), pid.value)); } - addRTDPluginButton(Settings.getPluginID_for_task(taskIndex)); + addRTDPluginButton(pid); addFormTextBox(F("Name"), F("TDN"), getTaskDeviceName(taskIndex), NAME_FORMULA_LENGTH_MAX); // ="taskdevicename" @@ -1555,7 +1556,6 @@ void ShowI2CMultiplexerUI(uint8_t i2cBus, bool muxPortsOption, int taskDeviceI2C } } } -#endif void GetI2CMultiplexerFromPage(uint8_t i2cBus, bool &muxPortsOption, int &selectedPorts) { if (isI2CMultiplexerEnabled(i2cBus)) { @@ -1573,6 +1573,7 @@ void GetI2CMultiplexerFromPage(uint8_t i2cBus, bool &muxPortsOption, int &select } } # endif // if FEATURE_I2CMULTIPLEXER +#endif // if FEATURE_I2C void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index c80282d6f2..dc6e93ac83 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -37,8 +37,6 @@ void handle_hardware() { if (!startStream_send_stdTemplate(MENU_INDEX_HARDWARE)) { return; } - html_add_form(); - if (isFormItem(F("pled"))) { String error; Settings.Pin_status_led = getFormItemInt(F("pled")); @@ -99,9 +97,10 @@ void handle_hardware() { addHtmlError(error); } - addHtml(F("")); + html_add_form(); + html_table_class_normal(); - addFormHeader(F("Hardware Settings"), F(""), F("Hardware/Hardware.html")); + addFormHeader(F("Hardware Settings"), F("RTDHardware/Hardware.html")); addFormSubHeader(F("Wifi Status LED")); addFormPinSelect(PinSelectPurpose::Status_led, formatGpioName_output(F("LED")), F("pled"), Settings.Pin_status_led); diff --git a/src/src/WebServer/InterfacesPage.cpp b/src/src/WebServer/InterfacesPage.cpp index abdc7c9277..25c0afc287 100644 --- a/src/src/WebServer/InterfacesPage.cpp +++ b/src/src/WebServer/InterfacesPage.cpp @@ -99,7 +99,8 @@ void handle_interfaces() { save_interfaces(); - addHtml(F("")); + html_add_form(); + html_table_class_normal(); addFormFixedFirstColumn(); // This must be added as the first element in a table definition addFormHeader(strformat(F("%s Interfaces Settings"), FsP(getGpMenuIcon(navMenuIndex))), F(""), F("Interfaces/Interfaces.html")); From e4c7db2d8547ce0006c38324ef4c31f9ed16a23e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 22:00:30 +0200 Subject: [PATCH 37/44] [Plugins] Disable some less-necessary logs to make ESP8266 builds fit again --- src/_C005.cpp | 14 ++++++++++++++ src/_C014.cpp | 23 ++++++++++++++--------- src/_P004_Dallas.ino | 2 ++ src/_P011_PME.ino | 4 ++++ src/_P017_PN532.ino | 4 ++++ src/_P018_Dust.ino | 2 ++ src/_P034_DHT12.ino | 4 ++++ src/_P040_ID12.ino | 2 ++ src/_P051_AM2320.ino | 2 ++ src/_P060_MCP3221.ino | 6 ++++++ src/_P065_DRF0299_MP3.ino | 2 ++ src/_P068_SHT3x.ino | 2 ++ src/_P071_Kamstrup401.ino | 2 ++ src/_P072_HDC1080.ino | 2 ++ 14 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/_C005.cpp b/src/_C005.cpp index 4b8485a790..9dd17d1839 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -93,9 +93,14 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& mqttDiscoveryTimeout = random(10, MQTT_DISCOVERY_MAX_DELAY_0_1_SECONDS); if (loglevelActiveFor(LOG_LEVEL_INFO)) { + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("C005 : INIT: AutoDiscovery for Controller %d in %.1f sec."), event->ControllerIndex + 1, mqttDiscoveryTimeout / 10.0f)); + # else // ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("C005 : Discovery in %.1f sec."), + mqttDiscoveryTimeout / 10.0f)); + # endif // ifndef BUILD_NO_DEBUG } } } @@ -112,7 +117,11 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& if (0 == mqttDiscoveryTimeout) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("C005 : AutoDiscovery delay expired, starting now...")); + # else // ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("C005 : Discovery starting...")); + # endif // ifndef BUILD_NO_DEBUG } success = MQTT_SendAutoDiscovery(event->ControllerIndex, CPLUGIN_ID_005); } @@ -175,8 +184,13 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& mqttDiscoveryTimeout = random(10, MQTT_DISCOVERY_MAX_DELAY_0_1_SECONDS); if (loglevelActiveFor(LOG_LEVEL_INFO)) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("C005 : Request for AutoDiscovery received. %.1f sec."), mqttDiscoveryTimeout / 10.0f)); + #else // ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("C005 : Request Discovery. %.1f sec."), + mqttDiscoveryTimeout / 10.0f)); + #endif // ifndef BUILD_NO_DEBUG } // FIXME Generate event when request received? diff --git a/src/_C014.cpp b/src/_C014.cpp index 852af381fd..d9bc10e85e 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -287,10 +287,10 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& // $localip Device → Controller IP of the device on the local network Yes Yes # ifdef CPLUGIN_014_V3 - CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$localip"), formatIP(NetworkLocalIP()), errorCounter); + CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$localip"), formatIP(NetworkLocalIP()), errorCounter); // $mac Device → Controller Mac address of the device network interface. The format MUST be of the type A1:B2:C3:D4:E5:F6 Yes Yes - CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$mac"), ESPEasy::net::NetworkMacAddress(), errorCounter); + CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$mac"), ESPEasy::net::NetworkMacAddress(), errorCounter); // $implementation Device → Controller An identifier for the Homie implementation (example esp8266) Yes Yes CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$implementation"), @@ -436,7 +436,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& // $datatype The data type. See Payloads. Enum: [integer, float, boolean,string, enum, color] unitName.clear(); - switch (Settings.TaskDevicePluginConfig[x][varNr]) { + switch (Settings.TaskDevicePluginConfig[x][varNr]) + { case 0: valueName = F("integer"); @@ -457,8 +458,10 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& ExtraTaskSettings.TaskDevicePluginConfig[varNr + valueCount]); } break; - case 2: valueName = F("boolean"); break; - case 3: valueName = F("string"); break; + case 2: valueName = F("boolean"); + break; + case 3: valueName = F("string"); + break; case 4: valueName = F("enum"); unitName = ExtraTaskSettings.TaskDeviceFormula[varNr]; @@ -687,7 +690,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& CPlugin_014_sendMQTTdevice(pubname, event->TaskIndex, F("$state"), F("ready"), errorCounter); success = true; } -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("C014 : autodiscover information of %d Devices and %d Nodes sent with %s errors! (%d messages)"), @@ -697,7 +701,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& msgCounter) ); } -#endif +# endif // ifndef BUILD_NO_DEBUG msgCounter = 0; errorCounter = 0; break; @@ -721,7 +725,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = strformat(F("C014 : Device: %s got invalid (disconnect%s"), - Settings.getHostname().c_str(), String(success ? F("ed).") : F(") failed!")).c_str()); + Settings.getHostname().c_str(), FsP(success ? F("ed).") : F(") failed!"))); addLogMove(LOG_LEVEL_INFO, log); } break; @@ -919,7 +923,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& addLogMove(LOG_LEVEL_ERROR, log); } } else if (equals(commandName, F(CPLUGIN_014_HOMIEVALUESET_COMMAND))) { // acknowledges value form P086 Homie Receiver - switch (Settings.TaskDevicePluginConfig[deviceIndex - 1][taskVarIndex]) { + switch (Settings.TaskDevicePluginConfig[deviceIndex - 1][taskVarIndex]) + { case 0: // PLUGIN_085_VALUE_INTEGER valueInt = static_cast(UserVar[userVarIndex]); valueStr = toString(UserVar[userVarIndex], 0); diff --git a/src/_P004_Dallas.ino b/src/_P004_Dallas.ino index a2c144e256..d5113b34cb 100644 --- a/src/_P004_Dallas.ino +++ b/src/_P004_Dallas.ino @@ -360,6 +360,7 @@ boolean Plugin_004(uint8_t function, struct EventStruct *event, String& string) } } + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("DS : Temperature: "); @@ -371,6 +372,7 @@ boolean Plugin_004(uint8_t function, struct EventStruct *event, String& string) log += strformat(F(" (%s)"), P004_data->get_formatted_address(i).c_str()); addLogMove(LOG_LEVEL_INFO, log); } + #endif // ifndef BUILD_NO_DEBUG } P004_data->set_measurement_inactive(); } diff --git a/src/_P011_PME.ino b/src/_P011_PME.ino index b13ffd5c6f..fb8c2580df 100644 --- a/src/_P011_PME.ino +++ b/src/_P011_PME.ino @@ -120,9 +120,11 @@ boolean Plugin_011(uint8_t function, struct EventStruct *event, String& string) if (P011_TYPE_SWITCH != P011_PORT_TYPE) { // Not for Switch type UserVar.setFloat(event->TaskIndex, 0, Plugin_011_Read(P011_PORT_TYPE, CONFIG_PORT)); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("PME : PortValue: "), formatUserVarNoCheck(event, 0))); } + #endif // ifndef BUILD_NO_DEBUG success = true; } break; @@ -138,9 +140,11 @@ boolean Plugin_011(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, newValue); sendData(event); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat(F("PME : Switch state: %d"), newValue)); } + #endif // ifndef BUILD_NO_DEBUG success = true; } } diff --git a/src/_P017_PN532.ino b/src/_P017_PN532.ino index 9da5e31b0e..f8782799dc 100644 --- a/src/_P017_PN532.ino +++ b/src/_P017_PN532.ino @@ -245,7 +245,9 @@ bool P017_handle_timer_in(struct EventStruct *event) // Reset card id on timeout if (P017_AUTO_TAG_REMOVAL == 0) { UserVar.setSensorTypeLong(event->TaskIndex, P017_NO_TAG_DETECTED_VALUE); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("RFID : Removed Tag")); + #endif // ifndef BUILD_NO_DEBUG if (P017_EVENT_ON_TAG_REMOVAL == 1) { sendData(event); @@ -396,10 +398,12 @@ boolean Plugin_017_Init(int8_t resetPin) if (validGpio(resetPin)) { + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("PN532: Reset on pin: "), resetPin)); } + #endif // ifndef BUILD_NO_DEBUG pinMode(resetPin, OUTPUT); digitalWrite(resetPin, LOW); delay(100); diff --git a/src/_P018_Dust.ino b/src/_P018_Dust.ino index d1e3afb01b..4d8a262a22 100644 --- a/src/_P018_Dust.ino +++ b/src/_P018_Dust.ino @@ -100,9 +100,11 @@ boolean Plugin_018(uint8_t function, struct EventStruct *event, String& string) ISR_interrupts(); UserVar.setFloat(event->TaskIndex, 0, value); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("GPY : Dust value: "), value)); } + #endif // ifndef BUILD_NO_DEBUG success = true; break; diff --git a/src/_P034_DHT12.ino b/src/_P034_DHT12.ino index 3c12f5138a..0a52be13b5 100644 --- a/src/_P034_DHT12.ino +++ b/src/_P034_DHT12.ino @@ -100,6 +100,7 @@ boolean Plugin_034(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, temperature); UserVar.setFloat(event->TaskIndex, 1, humidity); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("DHT12: Temperature: "), @@ -118,13 +119,16 @@ boolean Plugin_034(uint8_t function, struct EventStruct *event, String& string) addLog(LOG_LEVEL_INFO, log); */ } + #endif // ifndef BUILD_NO_DEBUG success = true; } // checksum } // error if (!success) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("DHT12: No reading!")); + #endif // ifndef BUILD_NO_DEBUG UserVar.setFloat(event->TaskIndex, 0, NAN); UserVar.setFloat(event->TaskIndex, 1, NAN); } diff --git a/src/_P040_ID12.ino b/src/_P040_ID12.ino index 6d1af2bfd3..a9faaae08a 100644 --- a/src/_P040_ID12.ino +++ b/src/_P040_ID12.ino @@ -77,7 +77,9 @@ boolean Plugin_040(uint8_t function, struct EventStruct *event, String& string) if (Plugin_040_init) { // Reset card id on timeout UserVar.setSensorTypeLong(event->TaskIndex, 0); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("RFID : Removed Tag")); + #endif // ifndef BUILD_NO_DEBUG sendData(event); success = true; } diff --git a/src/_P051_AM2320.ino b/src/_P051_AM2320.ino index 7718a8943f..67d9cc49bd 100644 --- a/src/_P051_AM2320.ino +++ b/src/_P051_AM2320.ino @@ -100,10 +100,12 @@ boolean Plugin_051(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, th.t); UserVar.setFloat(event->TaskIndex, 1, th.h); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("AM2320: Temperature: "), formatUserVarNoCheck(event, 0))); addLogMove(LOG_LEVEL_INFO, concat(F("AM2320: Humidity: "), formatUserVarNoCheck(event, 1))); } + #endif // ifndef BUILD_NO_DEBUG success = true; break; } diff --git a/src/_P060_MCP3221.ino b/src/_P060_MCP3221.ino index d670edc89e..7b9e8d0492 100644 --- a/src/_P060_MCP3221.ino +++ b/src/_P060_MCP3221.ino @@ -157,7 +157,9 @@ boolean Plugin_060(uint8_t function, struct EventStruct *event, String& string) if (nullptr != P060_data) { UserVar.setFloat(event->TaskIndex, 0, P060_data->getValue()); + #ifndef BUILD_NO_DEBUG String log = concat(F("ADMCP: Analog value: "), formatUserVarNoCheck(event, 0)); + #endif // ifndef BUILD_NO_DEBUG if (PCONFIG(3)) // Calibration? { @@ -171,11 +173,15 @@ boolean Plugin_060(uint8_t function, struct EventStruct *event, String& string) const float normalized = (UserVar[event->BaseVarIndex] - adc1) / static_cast(adc2 - adc1); UserVar.setFloat(event->TaskIndex, 0, normalized * (out2 - out1) + out1); + #ifndef BUILD_NO_DEBUG log += concat(F(" = "), formatUserVarNoCheck(event, 0)); + #endif // ifndef BUILD_NO_DEBUG } } + #ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_INFO, log); + #endif // ifndef BUILD_NO_DEBUG success = true; } break; diff --git a/src/_P065_DRF0299_MP3.ino b/src/_P065_DRF0299_MP3.ino index ec0a70f255..ff38ba0557 100644 --- a/src/_P065_DRF0299_MP3.ino +++ b/src/_P065_DRF0299_MP3.ino @@ -221,6 +221,7 @@ boolean Plugin_065(uint8_t function, struct EventStruct *event, String& string) success = true; } + #ifndef BUILD_NO_DEBUG if (success && loglevelActiveFor(LOG_LEVEL_INFO)) { String log; log.reserve(20); @@ -232,6 +233,7 @@ boolean Plugin_065(uint8_t function, struct EventStruct *event, String& string) } addLogMove(LOG_LEVEL_INFO, log); } + #endif // ifndef BUILD_NO_DEBUG break; } } diff --git a/src/_P068_SHT3x.ino b/src/_P068_SHT3x.ino index e0912aec76..562b83e850 100644 --- a/src/_P068_SHT3x.ino +++ b/src/_P068_SHT3x.ino @@ -132,10 +132,12 @@ boolean Plugin_068(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, sht3x->tmp); UserVar.setFloat(event->TaskIndex, 1, sht3x->hum); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("SHT3x: Temperature: "), formatUserVarNoCheck(event, 0))); addLogMove(LOG_LEVEL_INFO, concat(F("SHT3x: Humidity: "), formatUserVarNoCheck(event, 1))); } + #endif // ifndef BUILD_NO_DEBUG success = true; break; } diff --git a/src/_P071_Kamstrup401.ino b/src/_P071_Kamstrup401.ino index c9c493649d..17f66ee065 100644 --- a/src/_P071_Kamstrup401.ino +++ b/src/_P071_Kamstrup401.ino @@ -263,10 +263,12 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, m_energy); // gives energy in Wh UserVar.setFloat(event->TaskIndex, 1, m_volume); // gives volume in liters + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat(F("Kamstrup : Heat value: %.3f kWh"), m_energy / 1000)); addLogMove(LOG_LEVEL_INFO, strformat(F("Kamstrup : Volume value: %d Liter"), m_volume)); } + #endif // ifndef BUILD_NO_DEBUG } else { diff --git a/src/_P072_HDC1080.ino b/src/_P072_HDC1080.ino index cbc9eab859..978ebebefc 100644 --- a/src/_P072_HDC1080.ino +++ b/src/_P072_HDC1080.ino @@ -108,10 +108,12 @@ boolean Plugin_072(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, hdc1080_temp); UserVar.setFloat(event->TaskIndex, 1, hdc1080_hum); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("HDC10xx: Temperature: "), formatUserVarNoCheck(event, 0))); addLogMove(LOG_LEVEL_INFO, concat(F("HDC10xx: Humidity: "), formatUserVarNoCheck(event, 1))); } + #endif // ifndef BUILD_NO_DEBUG success = true; break; } From 6aae97b9be5a8d61b25a475f8cefbebd2dfd6de5 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 11 Aug 2026 22:02:39 +0200 Subject: [PATCH 38/44] [Storage] Add missing library.properties file for AT24CX (made up) --- lib/AT24Cx/library.properties | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 lib/AT24Cx/library.properties diff --git a/lib/AT24Cx/library.properties b/lib/AT24Cx/library.properties new file mode 100644 index 0000000000..24d9622016 --- /dev/null +++ b/lib/AT24Cx/library.properties @@ -0,0 +1,11 @@ +name=AT24CX +version=0.0.1 +author=Christian Paul +maintainer=Christian Paul +sentence=Arduino library for AT24Cx EEPROM storage devices. +paragraph= +category=Sensors +url=https://github.com/cyberp/AT24Cx +architectures=* +includes=AT24CX.h +depends= From ec1edddb6ed702c2e7fbbecc08c9df88f24d30e2 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 12 Aug 2026 13:40:36 +0200 Subject: [PATCH 39/44] [Plugins] Disable some less-necessary logs to make ESP8266 builds fit better --- src/_P084_VEML6070.ino | 2 + src/_P088_HeatpumpIR.ino | 4 + src/_P090_CCS811.ino | 2 + src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino | 2 + src/_P105_AHT.ino | 2 + src/_P107_SI1145.ino | 2 + src/_P112_AS7265x.ino | 4 +- src/_P114_VEML6075.ino | 2 + src/_P115_MAX1704x_v2.ino | 2 + src/_P117_SCD30.ino | 2 +- src/_P122_SHT2x.ino | 2 + src/_P127_CDM7160.ino | 2 + src/src/Helpers/AdafruitGFX_helper.cpp | 4 + src/src/Helpers/Audio.cpp | 2 + src/src/Helpers/ESPEasy_TouchHandler.cpp | 4 + src/src/Helpers/OTA.cpp | 4 +- src/src/PluginStructs/P005_data_struct.cpp | 6 ++ src/src/PluginStructs/P014_data_struct.cpp | 5 +- src/src/PluginStructs/P020_data_struct.cpp | 6 +- src/src/PluginStructs/P035_data_struct.cpp | 3 +- src/src/PluginStructs/P037_data_struct.cpp | 112 +++++++++------------ src/src/PluginStructs/P038_data_struct.cpp | 7 +- src/src/PluginStructs/P047_data_struct.cpp | 4 +- src/src/PluginStructs/P049_data_struct.cpp | 16 +++ src/src/PluginStructs/P053_data_struct.cpp | 6 ++ src/src/PluginStructs/P067_data_struct.cpp | 4 + src/src/PluginStructs/P095_data_struct.cpp | 2 +- src/src/PluginStructs/P096_data_struct.cpp | 4 +- src/src/PluginStructs/P105_data_struct.cpp | 2 + src/src/PluginStructs/P110_data_struct.cpp | 6 +- src/src/PluginStructs/P111_data_struct.cpp | 2 + src/src/PluginStructs/P114_data_struct.cpp | 5 +- src/src/PluginStructs/P116_data_struct.cpp | 4 +- src/src/PluginStructs/P119_data_struct.cpp | 8 ++ src/src/PluginStructs/P120_data_struct.cpp | 32 +++--- src/src/PluginStructs/P123_data_struct.cpp | 2 +- src/src/PluginStructs/P128_data_struct.cpp | 4 +- src/src/PluginStructs/P131_data_struct.cpp | 8 +- src/src/PluginStructs/P134_data_struct.cpp | 2 + src/src/PluginStructs/P135_data_struct.cpp | 48 +++++---- src/src/PluginStructs/P138_data_struct.cpp | 2 + src/src/PluginStructs/P141_data_struct.cpp | 4 +- src/src/PluginStructs/P142_data_struct.cpp | 2 + src/src/PluginStructs/P150_data_struct.cpp | 2 + src/src/PluginStructs/P153_data_struct.cpp | 2 + src/src/PluginStructs/P159_data_struct.cpp | 6 +- src/src/PluginStructs/P162_data_struct.cpp | 4 + src/src/PluginStructs/P167_data_struct.cpp | 2 + src/src/PluginStructs/P168_data_struct.cpp | 2 + src/src/PluginStructs/P173_data_struct.cpp | 2 + src/src/WebServer/ControllerPage.cpp | 4 + src/src/WebServer/Rules.cpp | 2 +- src/src/WebServer/SetupPage.cpp | 4 +- 53 files changed, 241 insertions(+), 137 deletions(-) diff --git a/src/_P084_VEML6070.ino b/src/_P084_VEML6070.ino index 4c9d90c2a6..7f266256df 100644 --- a/src/_P084_VEML6070.ino +++ b/src/_P084_VEML6070.ino @@ -148,9 +148,11 @@ boolean Plugin_084(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 1, uv_risk); UserVar.setFloat(event->TaskIndex, 2, uv_power); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("VEML6070: UV: "), formatUserVarNoCheck(event, 0))); } + #endif // ifndef BUILD_NO_DEBUG success = true; } diff --git a/src/_P088_HeatpumpIR.ino b/src/_P088_HeatpumpIR.ino index cb669b3d00..6ba4324b54 100644 --- a/src/_P088_HeatpumpIR.ino +++ b/src/_P088_HeatpumpIR.ino @@ -216,7 +216,9 @@ boolean Plugin_088(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_EXIT: { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("P088: Heatpump IR transmitter deactivated")); + #endif // ifndef BUILD_NO_DEBUG if (Plugin_088_irSender != nullptr) { @@ -243,7 +245,9 @@ boolean Plugin_088(uint8_t function, struct EventStruct *event, String& string) enableIR_RX(true); delete panasonicHeatpumpIR; + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("P088: The TIMER led on Panasonic CKP should now be OFF")); + #endif // ifndef BUILD_NO_DEBUG } } } diff --git a/src/_P090_CCS811.ino b/src/_P090_CCS811.ino index ba8d8d39e0..b298fa33e2 100644 --- a/src/_P090_CCS811.ino +++ b/src/_P090_CCS811.ino @@ -281,10 +281,12 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 1, P090_data->myCCS811.getCO2()); P090_data->newReadingAvailable = true; + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat(F("CCS811 : tVOC: %d, eCO2: %d"), P090_data->myCCS811.getTVOC(), P090_data->myCCS811.getCO2())); } + #endif // ifndef BUILD_NO_DEBUG } } } diff --git a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino index 8ab8238b8b..809cf56d2b 100644 --- a/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino +++ b/src/_P103_Atlas_EZO_pH_ORP_EC_DO.ino @@ -605,7 +605,9 @@ boolean Plugin_103(uint8_t function, struct EventStruct *event, String& string) if (P103_send_I2C_command(P103_I2C_ADDRESS, readCommand, boarddata)) { const String sensorString(boarddata); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, concat(F("P103: READ result: "), sensorString)); + #endif // ifndef BUILD_NO_DEBUG float sensor_f{}; diff --git a/src/_P105_AHT.ino b/src/_P105_AHT.ino index 2e38d1860e..a349b8afec 100644 --- a/src/_P105_AHT.ino +++ b/src/_P105_AHT.ino @@ -224,6 +224,7 @@ boolean Plugin_105(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, P105_data->getTemperature() + (P105_TEMPERATURE_OFFSET / 10.0f)); UserVar.setFloat(event->TaskIndex, 1, min(P105_data->getHumidity() * (1 - 0.005f * P105_TEMPERATURE_OFFSET), 100.0f)); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat(F("%s : Addr: 0x%02x"), P105_data->getDeviceName().c_str(), P105_I2C_ADRESS)); addLogMove(LOG_LEVEL_INFO, @@ -232,6 +233,7 @@ boolean Plugin_105(uint8_t function, struct EventStruct *event, String& string) formatUserVarNoCheck(event, 0).c_str(), formatUserVarNoCheck(event, 1).c_str())); } + #endif // ifndef BUILD_NO_DEBUG success = true; } break; diff --git a/src/_P107_SI1145.ino b/src/_P107_SI1145.ino index 6cb8cb4e76..e7dd834fc5 100644 --- a/src/_P107_SI1145.ino +++ b/src/_P107_SI1145.ino @@ -108,11 +108,13 @@ boolean Plugin_107(uint8_t function, struct EventStruct *event, String& string) P107_data->uv.reset(); // Stop the sensor reading + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("SI1145: Visible: "), formatUserVarNoCheck(event, 0))); addLogMove(LOG_LEVEL_INFO, concat(F("SI1145: Infrared: "), formatUserVarNoCheck(event, 1))); addLogMove(LOG_LEVEL_INFO, concat(F("SI1145: UV index: "), formatUserVarNoCheck(event, 2))); } + #endif // ifndef BUILD_NO_DEBUG success = true; break; } diff --git a/src/_P112_AS7265x.ino b/src/_P112_AS7265x.ino index 9d3ccea0ee..064535a8a3 100644 --- a/src/_P112_AS7265x.ino +++ b/src/_P112_AS7265x.ino @@ -304,7 +304,9 @@ boolean Plugin_112(uint8_t function, struct EventStruct *event, String& string) P112_data->initialized = false; // Force re-init just in case the address changed. if (P112_data->begin()) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("AS7265X: Found sensor")); + #endif // ifndef BUILD_NO_DEBUG success = P112_data->initialized; P112_data->sensor.setGain(PCONFIG_LONG(0)); @@ -337,7 +339,7 @@ boolean Plugin_112(uint8_t function, struct EventStruct *event, String& string) success = true; } else { - addLog(LOG_LEVEL_INFO, F("AS7265X: No sensor found")); + addLog(LOG_LEVEL_ERROR, F("AS7265X: No sensor found")); success = false; } } diff --git a/src/_P114_VEML6075.ino b/src/_P114_VEML6075.ino index 8a6445a5b8..f8a8b83b80 100644 --- a/src/_P114_VEML6075.ino +++ b/src/_P114_VEML6075.ino @@ -161,6 +161,7 @@ boolean Plugin_114(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 1, UVB); UserVar.setFloat(event->TaskIndex, 2, UVIndex); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat(F("VEML6075: Address: 0x%02x / Integration Time: %d / " "Dynamic Mode: %d / divisor: %d / UVA: %.2f / UVB: %.2f / UVIndex: %.2f"), @@ -169,6 +170,7 @@ boolean Plugin_114(uint8_t function, struct EventStruct *event, String& string) UserVar[event->BaseVarIndex + 1], UserVar[event->BaseVarIndex + 2])); } + #endif // ifndef BUILD_NO_DEBUG success = true; } diff --git a/src/_P115_MAX1704x_v2.ino b/src/_P115_MAX1704x_v2.ino index ddbedabdfc..699fea1665 100644 --- a/src/_P115_MAX1704x_v2.ino +++ b/src/_P115_MAX1704x_v2.ino @@ -161,10 +161,12 @@ boolean Plugin_115(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 2, P115_data->alert); UserVar.setFloat(event->TaskIndex, 3, P115_data->changeRate); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat(F("MAX1704x : Voltage: %.2f SoC: %.2f Alert: %d Rate: %.2f"), P115_data->voltage, P115_data->soc, P115_data->alert, P115_data->changeRate)); } + #endif // ifndef BUILD_NO_DEBUG success = true; } break; diff --git a/src/_P117_SCD30.ino b/src/_P117_SCD30.ino index 80294e1972..94f5885cf6 100644 --- a/src/_P117_SCD30.ino +++ b/src/_P117_SCD30.ino @@ -181,7 +181,7 @@ boolean Plugin_117(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 3, scd30_CO2); if (scd30_CO2EAvg > 5000) { - addLog(LOG_LEVEL_INFO, F("SCD30: Sensor saturated! > 5000 ppm")); + addLog(LOG_LEVEL_ERROR, F("SCD30: Sensor saturated! > 5000 ppm")); } break; case ERROR_SCD30_NO_DATA: diff --git a/src/_P122_SHT2x.ino b/src/_P122_SHT2x.ino index e842e20fe4..f29c27a2b1 100644 --- a/src/_P122_SHT2x.ino +++ b/src/_P122_SHT2x.ino @@ -221,6 +221,7 @@ boolean Plugin_122(uint8_t function, struct EventStruct *event, String& string) } } + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, @@ -229,6 +230,7 @@ boolean Plugin_122(uint8_t function, struct EventStruct *event, String& string) formatUserVarNoCheck(event, 1).c_str() )); } + #endif // ifndef BUILD_NO_DEBUG success = true; break; } diff --git a/src/_P127_CDM7160.ino b/src/_P127_CDM7160.ino index 323a1466cf..6c19279e95 100644 --- a/src/_P127_CDM7160.ino +++ b/src/_P127_CDM7160.ino @@ -160,6 +160,7 @@ boolean Plugin_127(uint8_t function, struct EventStruct *event, String& string) addLog(LOG_LEVEL_ERROR, F("CDM7160: Sensor saturated! > 10000 ppm")); } + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat(F("CDM7160: Address: 0x%02x: CO2 ppm: %d, alt: %d, comp: %d"), P127_CONFIG_I2C_ADDRESS, @@ -167,6 +168,7 @@ boolean Plugin_127(uint8_t function, struct EventStruct *event, String& string) P127_data->getAltitude(), P127_data->getCompensation())); } + #endif // ifndef BUILD_NO_DEBUG break; } case PLUGIN_WRITE: diff --git a/src/src/Helpers/AdafruitGFX_helper.cpp b/src/src/Helpers/AdafruitGFX_helper.cpp index ed616452ef..f44bcc8851 100644 --- a/src/src/Helpers/AdafruitGFX_helper.cpp +++ b/src/src/Helpers/AdafruitGFX_helper.cpp @@ -764,7 +764,9 @@ AdafruitGFX_helper::AdafruitGFX_helper(Adafruit_GFX *display, _textPrintMode(textPrintMode), _fontscaling(fontscaling), _fgcolor(fgcolor), _bgcolor(bgcolor), _useValidation(useValidation), _textBackFill(textBackFill), _defaultFontId(defaultFontId) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("AdaGFX_helper: GFX Init.")); + #endif // ifndef BUILD_NO_DEBUG } # if ADAGFX_ENABLE_BMP_DISPLAY @@ -785,7 +787,9 @@ AdafruitGFX_helper::AdafruitGFX_helper(Adafruit_SPITFT *display, _useValidation(useValidation), _textBackFill(textBackFill), _defaultFontId(defaultFontId) { _display = _tft; + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("AdaGFX_helper: TFT Init.")); + #endif // ifndef BUILD_NO_DEBUG } # endif // if ADAGFX_ENABLE_BMP_DISPLAY diff --git a/src/src/Helpers/Audio.cpp b/src/src/Helpers/Audio.cpp index 1ee71cda51..b334d9802a 100644 --- a/src/src/Helpers/Audio.cpp +++ b/src/src/Helpers/Audio.cpp @@ -43,7 +43,9 @@ void clear_rtttl_melody() { // The non-blocking play will read from a char pointer. // So we must stop the playing before changing the string as it could otherwise lead to a crash. if (anyrtttl::nonblocking::isPlaying()) { // If currently playing, cancel that + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("RTTTL: Cancelling running song...")); + #endif // ifndef BUILD_NO_DEBUG anyrtttl::nonblocking::stop(); # if FEATURE_RTTTL_EVENTS diff --git a/src/src/Helpers/ESPEasy_TouchHandler.cpp b/src/src/Helpers/ESPEasy_TouchHandler.cpp index eea5fd5262..5fcb0af03d 100644 --- a/src/src/Helpers/ESPEasy_TouchHandler.cpp +++ b/src/src/Helpers/ESPEasy_TouchHandler.cpp @@ -2222,7 +2222,9 @@ void ESPEasy_TouchHandler::generateObjectEvent(struct EventStruct *event, eventCommand += ','; eventCommand += wrapWithQuotesIfContainsParameterSeparatorChar(TouchObjects[objectIndex].objectName); ExecuteCommand_all({ EventValueSource::Enum::VALUE_SOURCE_RULES, eventCommand }, true); // Simulate like from rules + #ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_INFO, eventCommand); + #endif // ifndef BUILD_NO_DEBUG delay(0); // Handle group actions @@ -2251,7 +2253,9 @@ void ESPEasy_TouchHandler::generateObjectEvent(struct EventStruct *event, case Touch_action_e::Default: break; } + #ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_INFO, concat(F("TOUCH event: "), toString(action))); + #endif // ifndef BUILD_NO_DEBUG } } } diff --git a/src/src/Helpers/OTA.cpp b/src/src/Helpers/OTA.cpp index 77f1d4d907..3aa0c3e8bf 100644 --- a/src/src/Helpers/OTA.cpp +++ b/src/src/Helpers/OTA.cpp @@ -124,9 +124,7 @@ void ArduinoOTAInit() #endif if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("OTA : Arduino OTA enabled on port "); - log += ARDUINO_OTA_PORT; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("OTA : Arduino OTA enabled on port "), ARDUINO_OTA_PORT)); } } } diff --git a/src/src/PluginStructs/P005_data_struct.cpp b/src/src/PluginStructs/P005_data_struct.cpp index f5caadc6d3..96b68f3f6b 100644 --- a/src/src/PluginStructs/P005_data_struct.cpp +++ b/src/src/PluginStructs/P005_data_struct.cpp @@ -38,12 +38,16 @@ const __FlashStringHelper* P005_logString(P005_logNr logNr) { \*********************************************************************************************/ void P005_log(struct EventStruct *event, P005_logNr logNr) { + #ifndef BUILD_NO_DEBUG bool isError = true; + #endif // ifndef BUILD_NO_DEBUG switch (logNr) { case P005_logNr::P005_info_temperature: case P005_logNr::P005_info_humidity: + #ifndef BUILD_NO_DEBUG isError = false; + #endif // ifndef BUILD_NO_DEBUG break; default: @@ -52,6 +56,7 @@ void P005_log(struct EventStruct *event, P005_logNr logNr) break; } + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(isError ? LOG_LEVEL_ERROR : LOG_LEVEL_INFO)) { String text = concat(F("DHT : "), P005_logString(logNr)); @@ -64,6 +69,7 @@ void P005_log(struct EventStruct *event, P005_logNr logNr) } addLogMove(isError ? LOG_LEVEL_ERROR : LOG_LEVEL_INFO, text); } + #endif // ifndef BUILD_NO_DEBUG } P005_data_struct::P005_data_struct(struct EventStruct *event) { diff --git a/src/src/PluginStructs/P014_data_struct.cpp b/src/src/PluginStructs/P014_data_struct.cpp index 9091caeb57..bd45b013d8 100644 --- a/src/src/PluginStructs/P014_data_struct.cpp +++ b/src/src/PluginStructs/P014_data_struct.cpp @@ -50,10 +50,7 @@ bool P014_data_struct::finalizeInit(uint8_t i2caddr, uint8_t resolution) addLogMove(LOG_LEVEL_INFO, strformat(F("P014: chip_id=%d"), chip_id)); } if (chip_id == CHIP_ID_SI7013){ - if (!I2C_write8_reg(i2caddr,SI7013_WRITE_REG2, SI7013_REG2_DEFAULT )){ - return false; - } - if (!enablePowerForADC(i2caddr)){ + if (!I2C_write8_reg(i2caddr,SI7013_WRITE_REG2, SI7013_REG2_DEFAULT ) || !enablePowerForADC(i2caddr)){ return false; } } diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index 309d890fa4..890616a573 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -75,7 +75,7 @@ void P020_Task::startServer(uint16_t portnumber) { ser2netServer->begin(); if (serverActive(ser2netServer)) { - addLog(LOG_LEVEL_INFO, strformat(F("Ser2Net: WiFi server started at port %d"), portnumber)); + addLog(LOG_LEVEL_INFO, concat(F("Ser2Net: WiFi server started at port "), portnumber)); } else { addLog(LOG_LEVEL_ERROR, strformat(F("Ser2Net: WiFi server start FAILED at port %d, retrying..."), portnumber)); } @@ -105,11 +105,11 @@ void P020_Task::checkServer() { if (ser2netUdp->begin(_udpport) == 0) { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, strformat(F("Ser2Net: Cannot bind UDP at port %d"), _udpport)); + addLog(LOG_LEVEL_ERROR, concat(F("Ser2Net: Cannot bind UDP at port "), _udpport)); } } else { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("Ser2Net: UDP receiver started at port %d"), _udpport)); + addLog(LOG_LEVEL_INFO, concat(F("Ser2Net: UDP receiver started at port "), _udpport)); } } } diff --git a/src/src/PluginStructs/P035_data_struct.cpp b/src/src/PluginStructs/P035_data_struct.cpp index eb3bdd8ded..7a67ddd0cc 100644 --- a/src/src/PluginStructs/P035_data_struct.cpp +++ b/src/src/PluginStructs/P035_data_struct.cpp @@ -30,8 +30,7 @@ bool P035_data_struct::plugin_init(struct EventStruct *event) { if ((Plugin_035_irSender == nullptr) && validGpio(_gpioPin)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, F("INIT: IR TX")); - addLog(LOG_LEVEL_INFO, F("IR lib Version: " _IRREMOTEESP8266_VERSION_STR)); + addLog(LOG_LEVEL_INFO, F("INIT: IR TX, lib Version: " _IRREMOTEESP8266_VERSION_STR)); # ifdef P035_DEBUG_LOG addLog(LOG_LEVEL_INFO, concat(F("Supported Protocols by IRSEND: "), listProtocols())); # endif // ifdef P035_DEBUG_LOG diff --git a/src/src/PluginStructs/P037_data_struct.cpp b/src/src/PluginStructs/P037_data_struct.cpp index 269216848f..f222ff5e7b 100644 --- a/src/src/PluginStructs/P037_data_struct.cpp +++ b/src/src/PluginStructs/P037_data_struct.cpp @@ -33,8 +33,9 @@ bool P037_data_struct::loadSettings() { if (_taskIndex < TASKS_MAX) { # ifdef USE_SECOND_HEAP -// HeapSelectIram ephemeral; - #endif + + // HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP size_t offset = 0; LoadCustomTaskSettings(_taskIndex, mqttTopics, VARS_PER_TASK, 41, offset); @@ -50,7 +51,7 @@ bool P037_data_struct::loadSettings() { LoadCustomTaskSettings(_taskIndex, tmp, 1, 41, offset); move_special(globalTopicPrefix, std::move(tmp[0])); - offset += 41; + offset += 41; } @@ -292,7 +293,7 @@ bool P037_data_struct::webform_load( html_TD(); addTextBox(getPluginCustomArgName(idx + 100 + 0), parseStringKeepCase(valueArray[filterOffset], 1, P037_VALUE_SEPARATOR), - 32, + 32, F("xwide")); } { @@ -302,7 +303,7 @@ bool P037_data_struct::webform_load( html_TD(); addTextBox(getPluginCustomArgName(idx + 100 + 2), parseStringKeepCase(valueArray[filterOffset], 3, P037_VALUE_SEPARATOR), - 32, + 32, F("")); addUnit(F("Range/List: separate values with ; ")); html_TD(); @@ -314,17 +315,13 @@ bool P037_data_struct::webform_load( # ifdef PLUGIN_037_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String info; - info.reserve(25); - info += concat(F("P037 maxFilter: "), (int)_maxFilter); - info += concat(F(" idx: "), (int)idx); - addLogMove(LOG_LEVEL_INFO, info); + addLogMove(LOG_LEVEL_INFO, strformat(F("P037 maxFilter: %d idx: %d"), _maxFilter, idx)); } # endif // ifdef PLUGIN_037_DEBUG # ifndef P037_FILTER_PER_TOPIC filterIndex = 0; uint8_t extraFilters = 0; - + while (extraFilters < P037_EXTRA_VALUES && idx < P037_MAX_FILTERS * 3) { { html_TR_TD(); @@ -332,7 +329,7 @@ bool P037_data_struct::webform_load( addHtmlInt(filterNr); html_TD(); addTextBox(getPluginCustomArgName(idx + 100 + 0), EMPTY_STRING, - 32, + 32, F("xwide")); } { @@ -340,7 +337,7 @@ bool P037_data_struct::webform_load( selector.addSelector(getPluginCustomArgName(idx + 100 + 1), filterIndex); html_TD(); addTextBox(getPluginCustomArgName(idx + 100 + 2), EMPTY_STRING, - 32, + 32, F("")); addUnit(F("Range/List: separate values with ; ")); html_TD(); @@ -355,9 +352,7 @@ bool P037_data_struct::webform_load( # ifdef PLUGIN_037_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - info = concat(F("P037 extraFilters: "), (int)extraFilters); - info += concat(F(" idx: "), (int)idx); - addLogMove(LOG_LEVEL_INFO, info); + addLogMove(LOG_LEVEL_INFO, strformat(F("P037 extraFilters: %d idx: %d"), extraFilters, idx)); } # endif // ifdef PLUGIN_037_DEBUG # endif // ifndef P037_FILTER_PER_TOPIC @@ -388,8 +383,8 @@ bool P037_data_struct::webform_load( html_table_header(F("Value"), 300); const __FlashStringHelper *operandOptions[] = { - F("map"), // map name to int - F("percentage") }; // map attribute value to percentage of provided value + F("map"), // map name to int + F("percentage") }; // map attribute value to percentage of provided value const int operandIndices[] = { 0, 1 }; const FormSelectorOptions selector(P037_OPERAND_COUNT, operandOptions, operandIndices); @@ -408,7 +403,7 @@ bool P037_data_struct::webform_load( html_TD(); addTextBox(getPluginCustomArgName(idx + 0), parseStringKeepCase(valueArray[mappingOffset], 1, P037_VALUE_SEPARATOR), - 32, + 32, F("")); } { @@ -419,7 +414,7 @@ bool P037_data_struct::webform_load( html_TD(); addTextBox(getPluginCustomArgName(idx + 2), parseStringKeepCase(valueArray[mappingOffset], 3, P037_VALUE_SEPARATOR), - 32, + 32, F("")); html_TD(); } @@ -429,11 +424,7 @@ bool P037_data_struct::webform_load( # ifdef PLUGIN_037_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String info; - info.reserve(25); - info += concat(F("P037 maxIdx: "), (int)_maxIdx); - info += concat(F(" idx: "), (int)idx); - addLogMove(LOG_LEVEL_INFO, info); + addLogMove(LOG_LEVEL_INFO, strformat(F("P037 maxIdx: %d idx: %d"), _maxIdx, idx)); } # endif // ifdef PLUGIN_037_DEBUG operandIndex = 0; @@ -445,18 +436,18 @@ bool P037_data_struct::webform_load( addHtml(F(" ")); addHtmlInt(mapNr); html_TD(); - addTextBox(getPluginCustomArgName(idx + 0), + addTextBox(getPluginCustomArgName(idx + 0), EMPTY_STRING, - 32, + 32, F("")); } { html_TD(); selector.addSelector(getPluginCustomArgName(idx + 1), operandIndex); html_TD(); - addTextBox(getPluginCustomArgName(idx + 2), + addTextBox(getPluginCustomArgName(idx + 2), EMPTY_STRING, - 32, + 32, F("")); html_TD(); } @@ -468,19 +459,15 @@ bool P037_data_struct::webform_load( # ifdef PLUGIN_037_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String info; - info.reserve(35); - info += concat(F("P037 extraMappings: "), (int)extraMappings); - info += concat(F(" idx: "), (int)idx); - addLogMove(LOG_LEVEL_INFO, info); + addLogMove(LOG_LEVEL_INFO, strformat(F("P037 extraMappings: %d idx: %d"), extraMappings, idx)); } # endif // ifdef PLUGIN_037_DEBUG addFormNote(F("Both Name and Value must be filled for a valid mapping. Mappings are case-sensitive.")); if (extraMappings == P037_EXTRA_VALUES) { addFormNote(strformat( - F("After filling all mappings, submitting this page will make extra mappings available (up to %d)."), - P037_MAX_MAPPINGS)); + F("After filling all mappings, submitting this page will make extra mappings available (up to %d)."), + P037_MAX_MAPPINGS)); } } # endif // if P037_MAPPING_SUPPORT @@ -638,14 +625,10 @@ bool P037_data_struct::webform_save( # if P037_MAPPING_SUPPORT # ifdef PLUGIN_037_DEBUG + void P037_data_struct::logMapValue(const String& input, const String& result) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String info; - info.reserve(45); - info += concat(F("IMPT : MQTT mapped value '"), input); - info += concat(F("' to '"), result); - info += '\''; - addLogMove(LOG_LEVEL_INFO, info); + addLogMove(LOG_LEVEL_INFO, strformat(F("IMPT : MQTT mapped value '%s' to '%s'"), input.c_str(), result.c_str())); } } // logMapValue @@ -671,7 +654,8 @@ String P037_data_struct::mapValue(const String& input, const String& attribute) if ((name == input) || ((!attribute.isEmpty()) && (name == attribute))) { int8_t operandIndex = operands.indexOf(oper); - switch (operandIndex) { + switch (operandIndex) + { case 0: // = => 1:1 mapping { if (!valu.isEmpty()) { @@ -690,9 +674,12 @@ String P037_data_struct::mapValue(const String& input, const String& attribute) if (validDoubleFromString(input, inputDouble) && validDoubleFromString(valu, mappingDouble)) { if (compareDoubleValues('>', mappingDouble, 0.0)) { - ESPEASY_RULES_FLOAT_TYPE resultDouble = (static_cast(100) / mappingDouble) * inputDouble; // Simple calculation to percentage - int8_t decimals = 0; - int8_t dotPos = input.indexOf('.'); + ESPEASY_RULES_FLOAT_TYPE resultDouble = (static_cast(100) / mappingDouble) * inputDouble; // Simple + // calculation + // to + // percentage + int8_t decimals = 0; + int8_t dotPos = input.indexOf('.'); if (dotPos > -1) { String decPart = input.substring(dotPos + 1); @@ -733,6 +720,7 @@ bool P037_data_struct::hasFilters() { } // hasFilters # ifdef P037_FILTER_PER_TOPIC + String P037_data_struct::getFilterAsTopic(uint8_t topicId) { String result; @@ -763,15 +751,10 @@ String P037_data_struct::getFilterAsTopic(uint8_t topicId) { # endif // P037_FILTER_PER_TOPIC # ifdef PLUGIN_037_DEBUG + void P037_data_struct::logFilterValue(const String& text, const String& key, const String& value, const String& match) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(text.length() + key.length() + value.length() + match.length() + 16); - log += text; - log += key; - log += concat(F(" value: "), value); - log += concat(F(" match: "), match); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("%s %s value: %s match: %s"), text.c_str(), key.c_str(), value.c_str(), match.c_str())); } } // logFilterValue @@ -790,10 +773,10 @@ bool P037_data_struct::checkFilters(const String& key, const String& value, int8 if ((!key.isEmpty()) && (!value.isEmpty())) { // Ignore empty input(s) - String filters = P037_FILTER_LIST; - String valueData = value; - String fltKey, fltIndex, filterData, fltOper; - ESPEASY_RULES_FLOAT_TYPE from, to, doubleValue; + String filters = P037_FILTER_LIST; + String valueData = value; + String fltKey, fltIndex, filterData, fltOper; + ESPEASY_RULES_FLOAT_TYPE from, to, doubleValue; int8_t rangeSeparator; bool accept = true; bool matchTopicId = true; @@ -825,8 +808,9 @@ bool P037_data_struct::checkFilters(const String& key, const String& value, int8 filterData = parseStringKeepCase(valueArray[flt], 3, P037_VALUE_SEPARATOR); parseSystemVariables(filterData, false); // Replace system variables - switch (filterIndex) { - case 0: // = => equals + switch (filterIndex) + { + case 0: // = => equals { _filterListItem = EMPTY_STRING; @@ -903,7 +887,8 @@ bool P037_data_struct::checkFilters(const String& key, const String& value, int8 validDoubleFromString(valueData, doubleValue)) { accept = false; - do { + do + { item = filterData.substring(0, rangeSeparator); item.trim(); filterData = filterData.substring(rangeSeparator + 1); @@ -980,7 +965,8 @@ bool P037_data_struct::parseJSONMessage(const String& message) { // Try to allocate in PSRAM or 2nd heap if possible constexpr unsigned size = sizeof(DynamicJsonDocument); void *ptr = special_calloc(1, size); - if (ptr) { + + if (ptr) { root = new (ptr) DynamicJsonDocument(lastJsonMessageLength); // Dynamic allocation } } @@ -989,9 +975,9 @@ bool P037_data_struct::parseJSONMessage(const String& message) { deserializeJson(*root, message); if (!root->isNull()) { - # ifdef USE_SECOND_HEAP + # ifdef USE_SECOND_HEAP HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP + # endif // ifdef USE_SECOND_HEAP result = true; doc = root->as(); diff --git a/src/src/PluginStructs/P038_data_struct.cpp b/src/src/PluginStructs/P038_data_struct.cpp index fec73913f9..d549bbe617 100644 --- a/src/src/PluginStructs/P038_data_struct.cpp +++ b/src/src/PluginStructs/P038_data_struct.cpp @@ -70,6 +70,7 @@ enum class p038_commands_e : int8_t { neopixelfor, neopixelforhsv, # endif // if P038_FEATURE_NEOPIXELFOR + }; bool P038_data_struct::plugin_write(struct EventStruct *event, const String& string) { @@ -82,14 +83,18 @@ bool P038_data_struct::plugin_write(struct EventStruct *event, const String& str if (cmd_i < 0) { return false; } // Fail fast + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("P038 : write - "), string)); } + # endif // ifndef BUILD_NO_DEBUG const p038_commands_e cmde = static_cast(cmd_i); success = true; - switch (cmde) { + switch (cmde) + { case p038_commands_e::invalid: break; case p038_commands_e::neopixel: diff --git a/src/src/PluginStructs/P047_data_struct.cpp b/src/src/PluginStructs/P047_data_struct.cpp index 6ffe0d16dc..7ede2682ec 100644 --- a/src/src/PluginStructs/P047_data_struct.cpp +++ b/src/src/PluginStructs/P047_data_struct.cpp @@ -11,7 +11,7 @@ P047_data_struct::P047_data_struct(uint8_t address, _address(address), _model(static_cast(model)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, - strformat(F("SoilMoisture: Initializing sensor: %s, version: 0x%x"), String(toString(_model)).c_str(), getVersion())); + strformat(F("SoilMoisture: Initializing sensor: %s, version: 0x%x"), FsP(toString(_model)), getVersion())); } } @@ -120,6 +120,7 @@ bool P047_data_struct::plugin_read(struct EventStruct *event) { UserVar.setFloat(event->TaskIndex, 1, moisture); UserVar.setFloat(event->TaskIndex, 2, light); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = strformat(F("SoilMoisture: Address: 0x%02x"), P047_I2C_ADDR); @@ -134,6 +135,7 @@ bool P047_data_struct::plugin_read(struct EventStruct *event) { addLogMove(LOG_LEVEL_INFO, concat(F("SoilMoisture: Light: "), formatUserVarNoCheck(event, 2))); } } + #endif // ifndef BUILD_NO_DEBUG if (P047_SENSOR_SLEEP) { // send sensor to sleep diff --git a/src/src/PluginStructs/P049_data_struct.cpp b/src/src/PluginStructs/P049_data_struct.cpp index 287f12a3ee..139a8fec7d 100644 --- a/src/src/PluginStructs/P049_data_struct.cpp +++ b/src/src/PluginStructs/P049_data_struct.cpp @@ -103,25 +103,33 @@ bool P049_data_struct::plugin_write(struct EventStruct *event, const String& str if (equals(command, F("mhzcalibratezero"))) { send_mhzCmd(mhzCmdCalibrateZero); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Calibrated zero point!")); + #endif // ifndef BUILD_NO_DEBUG return true; } else if (equals(command, F("mhzreset"))) { send_mhzCmd(mhzCmdReset); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Sent sensor reset!")); + #endif // ifndef BUILD_NO_DEBUG return true; } else if (equals(command, F("mhzabcenable"))) { send_mhzCmd(mhzCmdABCEnable); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Sent sensor ABC Enable!")); + #endif // ifndef BUILD_NO_DEBUG return true; } else if (equals(command, F("mhzabcdisable"))) { send_mhzCmd(mhzCmdABCDisable); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Sent sensor ABC Disable!")); + #endif // ifndef BUILD_NO_DEBUG return true; } @@ -130,25 +138,33 @@ bool P049_data_struct::plugin_write(struct EventStruct *event, const String& str if (equals(command, F("mhzmeasurementrange1000"))) { send_mhzCmd(mhzCmdMeasurementRange1000); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Sent measurement range 0-1000PPM!")); + #endif // ifndef BUILD_NO_DEBUG return true; } else if (equals(command, F("mhzmeasurementrange2000"))) { send_mhzCmd(mhzCmdMeasurementRange2000); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Sent measurement range 0-2000PPM!")); + #endif // ifndef BUILD_NO_DEBUG return true; } else if (equals(command, F("mhzmeasurementrange3000"))) { send_mhzCmd(mhzCmdMeasurementRange3000); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Sent measurement range 0-3000PPM!")); + #endif // ifndef BUILD_NO_DEBUG return true; } else if (equals(command, F("mhzmeasurementrange5000"))) { send_mhzCmd(mhzCmdMeasurementRange5000); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("MHZ19: Sent measurement range 0-5000PPM!")); + #endif // ifndef BUILD_NO_DEBUG return true; } } diff --git a/src/src/PluginStructs/P053_data_struct.cpp b/src/src/PluginStructs/P053_data_struct.cpp index 4b63bf011f..cde6ccc43f 100644 --- a/src/src/PluginStructs/P053_data_struct.cpp +++ b/src/src/PluginStructs/P053_data_struct.cpp @@ -545,7 +545,9 @@ bool P053_data_struct::checkAndClearValuesReceived(struct EventStruct *event) { bool P053_data_struct::resetSensor() { if (validGpio(_resetPin)) { // Reset if pin is configured // Toggle 'reset' to assure we start reading header + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("PMSx003: resetting module")); + #endif // ifndef BUILD_NO_DEBUG pinMode(_resetPin, OUTPUT); digitalWrite(_resetPin, LOW); delay(250); @@ -560,7 +562,9 @@ bool P053_data_struct::wakeSensor() { if (!initialized()) { return false; } + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("PMSx003: Wake sensor")); + #endif // ifndef BUILD_NO_DEBUG if (validGpio(_pwrPin)) { // Make sure the sensor is "on" @@ -594,7 +598,9 @@ bool P053_data_struct::sleepSensor() { } // Put the sensor to sleep + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("PMSx003: Sleep sensor")); + #endif // ifndef BUILD_NO_DEBUG if (_pwrPin >= 0) { pinMode(_pwrPin, OUTPUT); diff --git a/src/src/PluginStructs/P067_data_struct.cpp b/src/src/PluginStructs/P067_data_struct.cpp index a2de8e1b66..ffc7c064f2 100644 --- a/src/src/PluginStructs/P067_data_struct.cpp +++ b/src/src/PluginStructs/P067_data_struct.cpp @@ -203,14 +203,18 @@ bool P067_data_struct::plugin_write(struct EventStruct *event, P067_int2float(P067_OFFSET_CHANNEL_A_1, P067_OFFSET_CHANNEL_A_2, &_offsetChanA); OversamplingChanA.reset(); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("HX711: tare channel A")); + #endif // ifndef BUILD_NO_DEBUG success = true; } else if (equals(command, F("tarechanb"))) { P067_float2int(-UserVar[event->BaseVarIndex + 3], &P067_OFFSET_CHANNEL_B_1, &P067_OFFSET_CHANNEL_B_2); P067_int2float(P067_OFFSET_CHANNEL_B_1, P067_OFFSET_CHANNEL_B_2, &_offsetChanB); OversamplingChanB.reset(); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("HX711: tare channel B")); + #endif // ifndef BUILD_NO_DEBUG success = true; } diff --git a/src/src/PluginStructs/P095_data_struct.cpp b/src/src/PluginStructs/P095_data_struct.cpp index c22586713d..20557c6831 100644 --- a/src/src/PluginStructs/P095_data_struct.cpp +++ b/src/src/PluginStructs/P095_data_struct.cpp @@ -210,7 +210,7 @@ bool P095_data_struct::plugin_init(struct EventStruct *event) { } # endif // ifndef BUILD_NO_DEBUG } else { - addLog(LOG_LEVEL_INFO, F("ILI9341: No init?")); + addLog(LOG_LEVEL_ERROR, F("ILI9341: No init?")); } if (isInitialized()) { diff --git a/src/src/PluginStructs/P096_data_struct.cpp b/src/src/PluginStructs/P096_data_struct.cpp index 972a6b2063..97c2ac2e12 100644 --- a/src/src/PluginStructs/P096_data_struct.cpp +++ b/src/src/PluginStructs/P096_data_struct.cpp @@ -245,7 +245,7 @@ bool P096_data_struct::plugin_init(struct EventStruct *event) { success = true; } else { - addLog(LOG_LEVEL_INFO, F("EPD : No init?")); + addLog(LOG_LEVEL_ERROR, F("EPD : No init?")); } return success; @@ -269,7 +269,9 @@ void P096_data_struct::updateFontMetrics() { * plugin_exit: De-initialize before destruction ***************************************************************************/ bool P096_data_struct::plugin_exit(struct EventStruct *event) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("EPD : Exit.")); + #endif // ifndef BUILD_NO_DEBUG # if P096_USE_EXTENDED_SETTINGS diff --git a/src/src/PluginStructs/P105_data_struct.cpp b/src/src/PluginStructs/P105_data_struct.cpp index b653ea4d11..6104a8c4fb 100644 --- a/src/src/PluginStructs/P105_data_struct.cpp +++ b/src/src/PluginStructs/P105_data_struct.cpp @@ -119,7 +119,9 @@ bool P105_data_struct::updateMeasurements(taskIndex_t task_index) { addLogMove(LOG_LEVEL_ERROR, strformat(F("%s : unable to initialize"), getDeviceName().c_str())); return false; } + #ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_INFO, strformat(F("%s : initialized"), getDeviceName().c_str())); + #endif // ifndef BUILD_NO_DEBUG trigger_time = current_time; state = AHTx_state::AHTx_Trigger_measurement; diff --git a/src/src/PluginStructs/P110_data_struct.cpp b/src/src/PluginStructs/P110_data_struct.cpp index 0a9b3ffaa6..1ceb903f68 100644 --- a/src/src/PluginStructs/P110_data_struct.cpp +++ b/src/src/PluginStructs/P110_data_struct.cpp @@ -16,8 +16,8 @@ bool P110_data_struct::begin(uint32_t interval_ms) { sensor.setAddress(_i2cAddress); // Initialize for configured address if (!sensor.init()) { + addLogMove(LOG_LEVEL_ERROR, strformat(F("VL53L0X: Sensor not found, init failed for 0x%02x"), _i2cAddress)); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L0X: Sensor not found, init failed for 0x%02x"), _i2cAddress)); addLog(LOG_LEVEL_INFO, sensor.getInitResult()); } return false; @@ -97,12 +97,12 @@ bool P110_data_struct::plugin_read(struct EventStruct *event) { // direction_changed || (std::abs(displacement) > P110_DELTA); - # ifdef P110_INFO_LOG + # ifdef P110_INFO_LOG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("VL53L0x: Perform read: trig: %d, prev: %d, dist: %d"), triggered, p_dist, dist)); } - # endif // ifdef P110_INFO_LOG + # endif // ifdef P110_INFO_LOG // Value is classified as invalid when > 8190, so no conversion or 'split' needed UserVar.setFloat(event->TaskIndex, 0, _filtered); UserVar.setFloat(event->TaskIndex, 1, disp_dir); // Trend of value diff --git a/src/src/PluginStructs/P111_data_struct.cpp b/src/src/PluginStructs/P111_data_struct.cpp index 479638024d..c00763c5e0 100644 --- a/src/src/PluginStructs/P111_data_struct.cpp +++ b/src/src/PluginStructs/P111_data_struct.cpp @@ -145,11 +145,13 @@ bool P111_data_struct::reset(int8_t csPin, int8_t resetPin) { if ((resetPin != -1) && (initPhase == P111_initPhases::Ready)) { + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove( LOG_LEVEL_INFO, concat(F("MFRC522: Reset on pin: "), resetPin)); } + #endif // ifndef BUILD_NO_DEBUG init(); return true; diff --git a/src/src/PluginStructs/P114_data_struct.cpp b/src/src/PluginStructs/P114_data_struct.cpp index 6aa6b5a749..d468f64636 100644 --- a/src/src/PluginStructs/P114_data_struct.cpp +++ b/src/src/PluginStructs/P114_data_struct.cpp @@ -102,10 +102,7 @@ bool P114_data_struct::init_sensor() { } return false; } else if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log = F("VEML6075: sensor initialised / CONF: "); - log += String((uint16_t)(IT << 4) | (HD << 3), BIN); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("VEML6075: sensor initialised / CONF: "), String((uint16_t)(IT << 4) | (HD << 3), BIN))); } } return true; diff --git a/src/src/PluginStructs/P116_data_struct.cpp b/src/src/PluginStructs/P116_data_struct.cpp index b4f0dfe1ba..203a16c76d 100644 --- a/src/src/PluginStructs/P116_data_struct.cpp +++ b/src/src/PluginStructs/P116_data_struct.cpp @@ -169,7 +169,9 @@ bool P116_data_struct::plugin_init(struct EventStruct *event) { DebounceCounter = 0; // debounce counter if (nullptr == st77xx) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("ST77xx: Init start.")); + #endif // ifndef BUILD_NO_DEBUG uint8_t initRoptions = 0xFF; #ifdef ESP32 @@ -320,7 +322,7 @@ bool P116_data_struct::plugin_init(struct EventStruct *event) { } # endif // ifndef BUILD_NO_DEBUG } else { - addLog(LOG_LEVEL_INFO, F("ST77xx: No init?")); + addLog(LOG_LEVEL_ERROR, F("ST77xx: No init?")); } if (nullptr != st77xx) { diff --git a/src/src/PluginStructs/P119_data_struct.cpp b/src/src/PluginStructs/P119_data_struct.cpp index 06ca401316..f5c187fe53 100644 --- a/src/src/PluginStructs/P119_data_struct.cpp +++ b/src/src/PluginStructs/P119_data_struct.cpp @@ -115,11 +115,19 @@ bool P119_data_struct::init_sensor() { itg3205 = new (std::nothrow) ITG3205(_i2cAddress); if (initialized()) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("ITG3205: Initializing Gyro...")); + #endif // ifndef BUILD_NO_DEBUG itg3205->initGyro(); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("ITG3205: Calibrating Gyro...")); + #endif // ifndef BUILD_NO_DEBUG itg3205->calibrate(); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("ITG3205: Calibration done.")); + #else // ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("ITG3205: Init done.")); + #endif // ifndef BUILD_NO_DEBUG } else { addLog(LOG_LEVEL_ERROR, F("ITG3205: Initialization of Gyro failed.")); return false; diff --git a/src/src/PluginStructs/P120_data_struct.cpp b/src/src/PluginStructs/P120_data_struct.cpp index 26509fe336..93b0d0e697 100644 --- a/src/src/PluginStructs/P120_data_struct.cpp +++ b/src/src/PluginStructs/P120_data_struct.cpp @@ -71,7 +71,9 @@ bool P120_data_struct::read_sensor(struct EventStruct *event) { } if (initialized()) { - _x = 0; _y = 0; _z = 0; + _x = 0; + _y = 0; + _z = 0; adxl345->readAccel(&_x, &_y, &_z); _XA[_aUsed] = _x; _YA[_aUsed] = _y; @@ -134,7 +136,8 @@ bool P120_data_struct::read_data(struct EventStruct *event) const const uint8_t pconfigIndex = i + P120_QUERY1_CONFIG_POS; float value = 0.0f; - switch (static_cast(PCONFIG(pconfigIndex))) { + switch (static_cast(PCONFIG(pconfigIndex))) + { case valueType::Empty: break; case valueType::X_RAW: @@ -238,6 +241,7 @@ bool P120_data_struct::init_sensor(struct EventStruct *event) { } else { # ifdef ESP32 auto spi_ptr = getSPIBusForTask(event->TaskIndex); + if (!spi_ptr) { return false; } @@ -252,6 +256,8 @@ bool P120_data_struct::init_sensor(struct EventStruct *event) { if (initialized()) { uint8_t act = 0, freeFall = 0, singleTap = 0, doubleTap = 0; + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("ADXL345: Initializing sensor for "); @@ -263,6 +269,7 @@ bool P120_data_struct::init_sensor(struct EventStruct *event) { log += F("..."); addLogMove(LOG_LEVEL_INFO, log); } + # endif // ifndef BUILD_NO_DEBUG adxl345->powerOn(); adxl345->setRangeSetting(2 ^ (get2BitFromUL(P120_CONFIG_FLAGS1, P120_FLAGS1_RANGE) + 1)); // Range is stored in 2 bits, only 4 possible // options @@ -373,12 +380,13 @@ void P120_data_struct::sensor_check_interrupt(struct EventStruct *event) { // Do not call again until you need to recheck for triggered actions uint8_t interrupts = adxl345->getInterruptSource(); String payload; + const bool doLog = bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_LOG_ACTIVITY); payload.reserve(30); // Free Fall Detection if (adxl345->triggered(interrupts, ADXL345_FREE_FALL)) { - if (bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_LOG_ACTIVITY)) { + if (doLog) { addLog(LOG_LEVEL_INFO, F("ADXL345: *** FREE FALL ***")); } payload = F("FreeFall="); @@ -390,7 +398,7 @@ void P120_data_struct::sensor_check_interrupt(struct EventStruct *event) { if (adxl345->triggered(interrupts, ADXL345_INACTIVITY) && bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_SEND_ACTIVITY)) { if (!inactivityTriggered) { - if (bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_LOG_ACTIVITY)) { + if (doLog) { addLog(LOG_LEVEL_INFO, F("ADXL345: *** INACTIVITY ***")); } payload = F("Inactivity="); @@ -408,7 +416,7 @@ void P120_data_struct::sensor_check_interrupt(struct EventStruct *event) { if (adxl345->triggered(interrupts, ADXL345_ACTIVITY) && bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_SEND_ACTIVITY)) { if (!activityTriggered) { - if (bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_LOG_ACTIVITY)) { + if (doLog) { addLog(LOG_LEVEL_INFO, F("ADXL345: *** ACTIVITY ***")); } payload = F("Activity="); @@ -427,14 +435,14 @@ void P120_data_struct::sensor_check_interrupt(struct EventStruct *event) { if (adxl345->triggered(interrupts, ADXL345_DOUBLE_TAP) || (adxl345->triggered(interrupts, ADXL345_SINGLE_TAP))) { if (adxl345->triggered(interrupts, ADXL345_SINGLE_TAP)) { - if (bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_LOG_ACTIVITY)) { + if (doLog) { addLog(LOG_LEVEL_INFO, F("ADXL345: *** TAP ***")); } payload = F("Tapped"); } - if (adxl345->triggered(interrupts, ADXL345_DOUBLE_TAP)) { // tonhuisman: Double-tap overrides single-tap event - if (bitRead(P120_CONFIG_FLAGS1, P120_FLAGS1_LOG_ACTIVITY)) { // This is on purpose and as intended! + if (adxl345->triggered(interrupts, ADXL345_DOUBLE_TAP)) { // tonhuisman: Double-tap overrides single-tap event + if (doLog) { // This is on purpose and as intended! addLog(LOG_LEVEL_INFO, F("ADXL345: *** DOUBLE TAP ***")); } payload = F("DoubleTapped"); @@ -792,7 +800,8 @@ void P120_data_struct::plugin_get_device_value_names(struct EventStruct *event) } const __FlashStringHelper * P120_data_struct::valuename(uint8_t value_nr, bool displayString) { - switch (static_cast(value_nr)) { + switch (static_cast(value_nr)) + { case valueType::Empty: return displayString ? F("Empty") : F(""); case valueType::X_RAW: return displayString ? F("X RAW") : F("X"); case valueType::Y_RAW: return displayString ? F("Y RAW") : F("Y"); @@ -808,9 +817,6 @@ const __FlashStringHelper * P120_data_struct::valuename(uint8_t value_nr, bool d return F(""); } -bool P120_data_struct::isXYZ(valueType vtype) -{ - return vtype >= valueType::X_RAW && vtype <= valueType::Z_g; -} +bool P120_data_struct::isXYZ(valueType vtype) { return vtype >= valueType::X_RAW && vtype <= valueType::Z_g; } #endif // if defined(USES_P120) || defined(USES_P125) diff --git a/src/src/PluginStructs/P123_data_struct.cpp b/src/src/PluginStructs/P123_data_struct.cpp index f4501d51a2..bf810a3bfa 100644 --- a/src/src/PluginStructs/P123_data_struct.cpp +++ b/src/src/PluginStructs/P123_data_struct.cpp @@ -144,7 +144,7 @@ bool P123_data_struct::init(struct EventStruct *event) { addLogMove(LOG_LEVEL_INFO, concat(concat(F("P123 DEBUG Plugin"), nullptr != touchscreen ? F(" & touchscreen") : F("")), F(" initialized."))); } else { - addLog(LOG_LEVEL_INFO, F("P123 DEBUG Touchscreen initialization FAILED.")); + addLog(LOG_LEVEL_ERROR, F("P123 DEBUG Touchscreen initialization FAILED.")); # endif // PLUGIN_123_DEBUG } return isInitialized(); diff --git a/src/src/PluginStructs/P128_data_struct.cpp b/src/src/PluginStructs/P128_data_struct.cpp index 5a0a5b0d29..68c3360517 100644 --- a/src/src/PluginStructs/P128_data_struct.cpp +++ b/src/src/PluginStructs/P128_data_struct.cpp @@ -46,8 +46,8 @@ bool P128_data_struct::plugin_read(struct EventStruct *event) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat( F("Lights: mode: %s lastmode: %s fadetime: %d fadedelay: %d"), - String(P128_modeType_toString(mode)).c_str(), - String(P128_modeType_toString(savemode)).c_str(), + FsP(P128_modeType_toString(mode)), + FsP(P128_modeType_toString(savemode)), (int)UserVar[event->BaseVarIndex + 2], (int)UserVar[event->BaseVarIndex + 3])); } diff --git a/src/src/PluginStructs/P131_data_struct.cpp b/src/src/PluginStructs/P131_data_struct.cpp index 6dc8944649..584ad0952d 100644 --- a/src/src/PluginStructs/P131_data_struct.cpp +++ b/src/src/PluginStructs/P131_data_struct.cpp @@ -99,7 +99,7 @@ bool P131_data_struct::plugin_init(struct EventStruct *event) { addLogMove(LOG_LEVEL_INFO, log); } } else { - addLog(LOG_LEVEL_INFO, F("NEOMATRIX: Init failed.")); + addLog(LOG_LEVEL_ERROR, F("NEOMATRIX: Init failed.")); # endif // ifndef BUILD_NO_DEBUG } @@ -453,11 +453,7 @@ bool P131_data_struct::plugin_write(struct EventStruct *event, const String& str # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("NEOMATRIX: set line "); - log += event->Par2; - log += F(": "); - log += strings[x]; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("NEOMATRIX: set line %d: %s"), event->Par2, strings[x].c_str())); } # endif // ifndef BUILD_NO_DEBUG } diff --git a/src/src/PluginStructs/P134_data_struct.cpp b/src/src/PluginStructs/P134_data_struct.cpp index 10ddf2cddf..74a99a1a88 100644 --- a/src/src/PluginStructs/P134_data_struct.cpp +++ b/src/src/PluginStructs/P134_data_struct.cpp @@ -99,9 +99,11 @@ bool P134_data_struct::plugin_read(struct EventStruct *event) { if (measurementStatus == A02YYUW_status_e::STATUS_OK) { UserVar.setFloat(event->TaskIndex, 0, static_cast(measuredDistance)); + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("A02YYUW: Distance value = "), static_cast(measuredDistance))); } + #endif // ifndef BUILD_NO_DEBUG success = true; } else { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { diff --git a/src/src/PluginStructs/P135_data_struct.cpp b/src/src/PluginStructs/P135_data_struct.cpp index b4e37c57cb..ddc65e0f97 100644 --- a/src/src/PluginStructs/P135_data_struct.cpp +++ b/src/src/PluginStructs/P135_data_struct.cpp @@ -95,7 +95,9 @@ bool P135_data_struct::plugin_read(struct EventStruct *event) { getMeasure = false; singleShotStarted = true; + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("SCD4x: SingleShot measurement started.")); + # endif // ifndef BUILD_NO_DEBUG } if (getMeasure && scd4x->readMeasurement()) { @@ -140,63 +142,69 @@ bool P135_data_struct::plugin_read(struct EventStruct *event) { # if P135_FEATURE_RESET_COMMANDS if (operation != SCD4x_Operations_e::None) { - switch (operation) { + switch (operation) + { # if P135_FEATURE_FACTORYRESET - case SCD4x_Operations_e::RunFactoryReset: { // May take up to 1200 mSec + case SCD4x_Operations_e::RunFactoryReset: // May take up to 1200 mSec + { success = scd4x->performFactoryReset(); - String log = F("SCD4x: Factory reset "); uint8_t lvl = LOG_LEVEL_INFO; if (success) { initialized = startPeriodicMeasurements(); // Select the correct periodic measurement, and start a READ Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + P135_STOP_MEASUREMENT_DELAY); - log += F("success."); } else { - lvl = LOG_LEVEL_ERROR; - log += F("failed!"); + lvl = LOG_LEVEL_ERROR; + } + + if (loglevelActiveFor(lvl)) { + addLog(lvl, concat(F("SCD4x: Factory reset "), success ? F("success.") : F("failed!"))); } - addLog(lvl, log); break; } # endif // if P135_FEATURE_FACTORYRESET - case SCD4x_Operations_e::RunSelfTest: { // May take up to 10 seconds! + case SCD4x_Operations_e::RunSelfTest: // May take up to 10 seconds! + { success = scd4x->performSelfTest(); - String log = F("SCD4x: Sensor self-test "); uint8_t lvl = LOG_LEVEL_INFO; if (success) { initialized = startPeriodicMeasurements(); // Select the correct periodic measurement, and start a READ Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + P135_STOP_MEASUREMENT_DELAY); - log += F("success."); } else { - lvl = LOG_LEVEL_ERROR; - log += F("failed!"); + lvl = LOG_LEVEL_ERROR; + } + + if (loglevelActiveFor(lvl)) { + addLog(lvl, concat(F("SCD4x: Sensor self-test "), success ? F("success.") : F("failed!"))); } - addLog(lvl, log); break; } - case SCD4x_Operations_e::RunForcedRecalibration: { // May take up to 400 mSec + case SCD4x_Operations_e::RunForcedRecalibration: // May take up to 400 mSec + { float frcCorrection = 0.0f; success = scd4x->performForcedRecalibration(frcValue, &frcCorrection); frcValue = 0; - String log = F("SCD4x: Forced Recalibration "); uint8_t lvl = LOG_LEVEL_INFO; if (success) { initialized = startPeriodicMeasurements(); // Select the correct periodic measurement, and start a READ Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + P135_STOP_MEASUREMENT_DELAY); - log += strformat(F("success. New setting: %d, correction: %.2f"), frcValue, frcCorrection); } else { - lvl = LOG_LEVEL_ERROR; - log += F("failed!"); + lvl = LOG_LEVEL_ERROR; + } + + if (loglevelActiveFor(lvl)) { + addLog(lvl, concat(F("SCD4x: Forced Recalibration "), + success ? strformat(F("success. New setting: %d, correction: %.2f"), frcValue, frcCorrection) : F("failed!"))); } - addLog(lvl, log); break; } - case SCD4x_Operations_e::None: { // To keep the compiler and developer happy :-) + case SCD4x_Operations_e::None: // To keep the compiler and developer happy :-) + { break; } } diff --git a/src/src/PluginStructs/P138_data_struct.cpp b/src/src/PluginStructs/P138_data_struct.cpp index f4513afa03..cdf0b2c4c6 100644 --- a/src/src/PluginStructs/P138_data_struct.cpp +++ b/src/src/PluginStructs/P138_data_struct.cpp @@ -43,7 +43,9 @@ int Plugin_138_QueryVType(uint8_t value_nr) { // Constructor // **************************************************************************/ P138_data_struct::P138_data_struct(struct EventStruct *event) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("IP5306: Init.")); + #endif // ifndef BUILD_NO_DEBUG _ip5306 = new (std::nothrow) arduino::ip5306(); // Default address and I2C Wire object } diff --git a/src/src/PluginStructs/P141_data_struct.cpp b/src/src/PluginStructs/P141_data_struct.cpp index bb3a1f6843..8e62db171d 100644 --- a/src/src/PluginStructs/P141_data_struct.cpp +++ b/src/src/PluginStructs/P141_data_struct.cpp @@ -73,7 +73,9 @@ bool P141_data_struct::plugin_init(struct EventStruct *event) { if (nullptr == pcd8544) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("PCD8544: Init start.")); + #endif // ifndef BUILD_NO_DEBUG pcd8544 = new (std::nothrow) Adafruit_PCD8544(P141_DC_PIN, P141_CS_PIN, P141_RST_PIN # ifdef ESP32 @@ -95,7 +97,7 @@ bool P141_data_struct::plugin_init(struct EventStruct *event) { } # endif // ifndef BUILD_NO_DEBUG } else { - addLog(LOG_LEVEL_INFO, F("PCD8544: No init?")); + addLog(LOG_LEVEL_ERROR, F("PCD8544: No init?")); } if (nullptr != pcd8544) { diff --git a/src/src/PluginStructs/P142_data_struct.cpp b/src/src/PluginStructs/P142_data_struct.cpp index 1bf8b56bad..bfef8d99b2 100644 --- a/src/src/PluginStructs/P142_data_struct.cpp +++ b/src/src/PluginStructs/P142_data_struct.cpp @@ -55,9 +55,11 @@ bool P142_data_struct::init(struct EventStruct *event) { as5600->setOffset(_angleOffset); } + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("AS5600: Initialization "), isInitialized() ? F("succeeded") : F("failed"))); } + #endif // ifndef BUILD_NO_DEBUG return isInitialized(); } diff --git a/src/src/PluginStructs/P150_data_struct.cpp b/src/src/PluginStructs/P150_data_struct.cpp index 97fd42d9fe..0ef5141de0 100644 --- a/src/src/PluginStructs/P150_data_struct.cpp +++ b/src/src/PluginStructs/P150_data_struct.cpp @@ -64,6 +64,7 @@ bool P150_data_struct::plugin_read(struct EventStruct *event) { UserVar.setFloat(event->TaskIndex, 0, _finalTempC); UserVar.setFloat(event->TaskIndex, 1, _digitalTempC); + #ifndef BUILD_NO_DEBUG if (_logEnabled && loglevelActiveFor(LOG_LEVEL_INFO)) { String log = strformat(F("TMP117: Temperature: %sC"), formatUserVarNoCheck(event, 0).c_str()); @@ -72,6 +73,7 @@ bool P150_data_struct::plugin_read(struct EventStruct *event) { } addLogMove(LOG_LEVEL_INFO, log); } + #endif // ifndef BUILD_NO_DEBUG if (P150_GET_CONF_CONVERSION_MODE == P150_CONVERSION_ONE_SHOT) { setConfig(); // Start the next one-shot measurement diff --git a/src/src/PluginStructs/P153_data_struct.cpp b/src/src/PluginStructs/P153_data_struct.cpp index 25418dddae..b80a475f8a 100644 --- a/src/src/PluginStructs/P153_data_struct.cpp +++ b/src/src/PluginStructs/P153_data_struct.cpp @@ -170,6 +170,7 @@ bool P153_data_struct::plugin_read(struct EventStruct *event) { } } + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { const String taskName = getTaskDeviceName(event->TaskIndex); addLogMove(LOG_LEVEL_INFO, strformat(F("%s: Temperature: %s"), @@ -180,6 +181,7 @@ bool P153_data_struct::plugin_read(struct EventStruct *event) { taskName.c_str(), formatUserVarNoCheck(event, 1).c_str())); } + #endif // ifndef BUILD_NO_DEBUG } else { UserVar.setFloat(event->TaskIndex, 0, NAN); UserVar.setFloat(event->TaskIndex, 1, NAN); diff --git a/src/src/PluginStructs/P159_data_struct.cpp b/src/src/PluginStructs/P159_data_struct.cpp index d267582941..bf33ded3c9 100644 --- a/src/src/PluginStructs/P159_data_struct.cpp +++ b/src/src/PluginStructs/P159_data_struct.cpp @@ -158,9 +158,11 @@ bool P159_data_struct::processSensor(struct EventStruct *event) { break; } - if (P159_state_e::Running != sState) { // FIXME Remove log + if (P159_state_e::Running != sState) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("LD2410: Starting state: %d duration: %d msec."), static_cast(sState), timePassedSince(iStart))); + #endif // ifndef BUILD_NO_DEBUG } } // isValid() @@ -322,7 +324,9 @@ bool P159_data_struct::plugin_webform_save(struct EventStruct *event) { const uint16_t idle = getFormItemIntCustomArgName(idx++); const uint8_t gMove = getFormItemIntCustomArgName(idx++); const uint8_t gStat = getFormItemIntCustomArgName(idx++); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("LD2410: Save sensitivity settings to sensor, start...")); + #endif // ifndef BUILD_NO_DEBUG radar->requestConfigurationModeBegin(); radar->setMaxValues(gMove, gStat, idle); const uint16_t maxGate = radar->cfgMaxGate(); diff --git a/src/src/PluginStructs/P162_data_struct.cpp b/src/src/PluginStructs/P162_data_struct.cpp index 9939a63460..6294c35d61 100644 --- a/src/src/PluginStructs/P162_data_struct.cpp +++ b/src/src/PluginStructs/P162_data_struct.cpp @@ -78,7 +78,9 @@ bool P162_data_struct::hw_reset() { digitalWrite(_rstPin, LOW); delayMicroseconds(1); // Reset requires low signal for at least 150 nsec, so 1 microsecond should suffice digitalWrite(_rstPin, HIGH); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("Digipot: Hardware reset applied.")); + #endif // ifndef BUILD_NO_DEBUG return true; } return false; @@ -106,7 +108,9 @@ bool P162_data_struct::plugin_write(struct EventStruct *event, updateUserVars(event); write_pot(event, P162_BOTH_POT_SEL, _pot0_value); // Single command success = true; + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("Digipot: Software reset applied.")); + #endif // ifndef BUILD_NO_DEBUG } } else diff --git a/src/src/PluginStructs/P167_data_struct.cpp b/src/src/PluginStructs/P167_data_struct.cpp index e8e8cdb69f..02ec3bf48d 100644 --- a/src/src/PluginStructs/P167_data_struct.cpp +++ b/src/src/PluginStructs/P167_data_struct.cpp @@ -150,9 +150,11 @@ P167_data_struct::~P167_data_struct() { bool P167_data_struct::setupDevice(uint8_t i2caddr) { _i2caddr = i2caddr; + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("SEN5x: Setup with address= 0x%02x"), _i2caddr)); } + #endif // ifndef BUILD_NO_DEBUG return true; } diff --git a/src/src/PluginStructs/P168_data_struct.cpp b/src/src/PluginStructs/P168_data_struct.cpp index 5d71cf803a..5d22c711ad 100644 --- a/src/src/PluginStructs/P168_data_struct.cpp +++ b/src/src/PluginStructs/P168_data_struct.cpp @@ -38,7 +38,9 @@ bool P168_data_struct::init(struct EventStruct *event) { veml->setPowerSaveMode(_psm_mode); veml->enable(true); + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("VEML : 6030/7700 Initialized.")); + #endif // ifndef BUILD_NO_DEBUG initialized = true; } else { diff --git a/src/src/PluginStructs/P173_data_struct.cpp b/src/src/PluginStructs/P173_data_struct.cpp index f102f6fdd1..b6d911f2a6 100644 --- a/src/src/PluginStructs/P173_data_struct.cpp +++ b/src/src/PluginStructs/P173_data_struct.cpp @@ -62,12 +62,14 @@ bool P173_data_struct::plugin_read(struct EventStruct *event) { success = true; errorCount = 0; + #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("%s: Temperature: %s, Humidity: %s"), getTaskDeviceName(event->TaskIndex).c_str(), formatUserVarNoCheck(event, 0).c_str(), formatUserVarNoCheck(event, 1).c_str())); } + #endif // ifndef BUILD_NO_DEBUG } else { addLog(LOG_LEVEL_ERROR, concat(F("SHTC3: READ CRC Error, data: 0x"), formatToHex_array(data, 6))); errorCount++; diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index c6e737f7d3..10ec077452 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -100,7 +100,11 @@ void handle_controllers() { mqttDiscoveryTimeout = random(10, MQTT_DISCOVERY_MAX_DELAY_0_1_SECONDS); if (loglevelActiveFor(LOG_LEVEL_INFO)) { + #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Start AutoDiscovery on Save. Starting in %.1f sec."), mqttDiscoveryTimeout / 10)); + #else // ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Discovery starting in %.1f sec."), mqttDiscoveryTimeout / 10)); + #endif // ifndef BUILD_NO_DEBUG } } # endif // if FEATURE_MQTT_DISCOVER diff --git a/src/src/WebServer/Rules.cpp b/src/src/WebServer/Rules.cpp index ac30ce099a..97f18b1a3f 100644 --- a/src/src/WebServer/Rules.cpp +++ b/src/src/WebServer/Rules.cpp @@ -461,7 +461,7 @@ bool handle_rules_edit(String originalUri, bool isAddNew) { if (f) { - addLog(LOG_LEVEL_INFO, String(F(" Write to file: ")) + fileName); + addLog(LOG_LEVEL_INFO, concat(F(" Write to file: "), fileName)); f.print(rules); f.close(); } diff --git a/src/src/WebServer/SetupPage.cpp b/src/src/WebServer/SetupPage.cpp index 9d9a4316f4..a4a1b1bbb7 100644 --- a/src/src/WebServer/SetupPage.cpp +++ b/src/src/WebServer/SetupPage.cpp @@ -145,9 +145,7 @@ void handle_setup() { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String reconnectlog = F("WIFI : Credentials Changed, retry connection. SSID: "); - reconnectlog += ssid; - addLogMove(LOG_LEVEL_INFO, reconnectlog); + addLogMove(LOG_LEVEL_INFO, concat(F("WIFI : Credentials Changed, retry connection. SSID: "), ssid)); } # endif // ifndef BUILD_NO_DEBUG status = HANDLE_SETUP_CONNECTING_STAGE; From f8e57ae2074bb6de4a667611d4b7e3cbf86951d7 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 13 Aug 2026 11:29:00 +0200 Subject: [PATCH 40/44] [Storage] Use correct Settings accessor for external timesource --- src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp index 25bf0cc312..b1e42f6a94 100644 --- a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp +++ b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp @@ -18,11 +18,11 @@ constexpr uint32_t sizeof_rtcsram_slot = sizeof(SRAM_STORAGE_FLOAT_TYPE); * Returns the external rtc type if SRAM is available */ uint8_t checkRTCSRAMEnabled() { - const ExtTimeSource_e extRtcType = static_cast(Settings.ExternalTimeSource); + const ExtTimeSource_e extRtcType = Settings.ExtTimeSource(); if ((ExtTimeSource_e::DS1307 == extRtcType) || (ExtTimeSource_e::DS3232 == extRtcType)) { // RTC with SRAM Configured? - return Settings.ExternalTimeSource; + return static_cast(Settings.ExtTimeSource()); } return (uint8_t)0; } @@ -31,7 +31,7 @@ uint8_t checkRTCSRAMEnabled() { * Get the RTC I2C Address */ uint8_t getRTCI2CAddress() { - const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + const ExtTimeSource_e type = Settings.ExtTimeSource(); switch (type) { @@ -50,10 +50,10 @@ uint8_t getRTCI2CAddress() { } /** - * Switch to I2C Bus and multiplexer channel of External EEPROM + * Switch to I2C Bus and multiplexer channel of RTC SRAM */ uint8_t selectRTCSRAMI2CBus() { - const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + const ExtTimeSource_e type = Settings.ExtTimeSource(); if (checkRTCSRAMEnabled()) { // RTC Module Configured? # if FEATURE_I2C_MULTIPLE @@ -78,7 +78,7 @@ uint8_t selectRTCSRAMI2CBus() { } if (0 == I2C_wakeup(getRTCI2CAddress())) { - return Settings.ExternalTimeSource; + return static_cast(Settings.ExtTimeSource()); } } return (uint8_t)0; @@ -88,7 +88,7 @@ uint8_t selectRTCSRAMI2CBus() { * RTC SRAM size in bytes */ uint32_t getRTCSRAMSize() { - const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + const ExtTimeSource_e type = Settings.ExtTimeSource(); switch (type) { @@ -124,7 +124,7 @@ uint32_t getRTCSRAMAddressForSlot(uint32_t slot) { } /** - * EEPROM available number of slots, max use all available space minus some administrative bytes + * RTC SRAM available number of slots, max use all available space */ uint32_t getRTCSRAMMaxSlots() { if (checkRTCSRAMEnabled()) { @@ -140,7 +140,7 @@ uint32_t getRTCSRAMMaxSlots() { } /** - * EEPROM write value to slot if the slot is valid + * RTC SRAM write value to slot if the slot is valid */ bool writeRTCSRAMSlot(uint32_t slot, SRAM_STORAGE_FLOAT_TYPE data) @@ -148,7 +148,7 @@ bool writeRTCSRAMSlot(uint32_t slot, const uint32_t addr = getRTCSRAMAddressForSlot(slot); if ((addr != std::numeric_limits::max()) && (selectRTCSRAMI2CBus() > 0)) { - const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + const ExtTimeSource_e type = Settings.ExtTimeSource(); uint8_t _b[sizeof_rtcsram_slot]{}; switch (type) @@ -193,7 +193,7 @@ SRAM_STORAGE_FLOAT_TYPE readRTCSRAMSlot(uint32_t slot) { const uint32_t addr = getRTCSRAMAddressForSlot(slot); if ((addr != std::numeric_limits::max()) && (selectRTCSRAMI2CBus() > 0)) { - const ExtTimeSource_e type = static_cast(Settings.ExternalTimeSource); + const ExtTimeSource_e type = Settings.ExtTimeSource(); uint8_t _b[sizeof_rtcsram_slot]{}; switch (type) From 1d185d5fb67e2df8e7695b5d3ec477a942eb90a4 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 15 Aug 2026 16:51:43 +0200 Subject: [PATCH 41/44] [Storage] Use best available data type, and max number of decimals, on EepromVar page --- src/src/WebServer/EepromVarPage.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/src/WebServer/EepromVarPage.cpp b/src/src/WebServer/EepromVarPage.cpp index d0ee360b43..e60d6882f1 100644 --- a/src/src/WebServer/EepromVarPage.cpp +++ b/src/src/WebServer/EepromVarPage.cpp @@ -52,7 +52,7 @@ void handle_eepromvars() { uint32_t count{}; for (uint32_t slot = 0; slot < maxSlots; ++slot) { - const float value = ESPEasy::eeprom::readEEPROMSlot(slot); + const ESPEASY_RULES_FLOAT_TYPE value = ESPEasy::eeprom::readEEPROMSlot(slot); if (slot % 50 == 0) { delay(0); } @@ -62,9 +62,9 @@ void handle_eepromvars() { addHtmlInt(slot); html_TD(); # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - addHtml(doubleToString(value)); + addHtml(doubleToString(value, ESPEASY_DOUBLE_NR_DECIMALS, true)); # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - addHtml(toString(value)); + addHtml(toString(value, ESPEASY_FLOAT_NR_DECIMALS, true)); # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE html_TD(2); } @@ -121,7 +121,7 @@ void handle_eepromvars() { uint32_t count{}; for (uint32_t slot = 0; slot < maxSlots; ++slot) { - const float value = ESPEasy::eeprom::readRTCSRAMSlot(slot); + const ESPEASY_RULES_FLOAT_TYPE value = ESPEasy::eeprom::readRTCSRAMSlot(slot); if (slot % 50 == 0) { delay(0); } @@ -131,9 +131,9 @@ void handle_eepromvars() { addHtmlInt(slot); html_TD(); # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - addHtml(doubleToString(value)); + addHtml(doubleToString(value, ESPEASY_DOUBLE_NR_DECIMALS, true)); # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - addHtml(toString(value)); + addHtml(toString(value, ESPEASY_FLOAT_NR_DECIMALS, true)); # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE html_TD(2); } From ac1266736519ab3c3e5cb9c23a6e95bcc492f08d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 17 Aug 2026 17:08:01 +0200 Subject: [PATCH 42/44] [Storage] Add support for PCF8583 RTC with 240 bytes SRAM --- lib/Adafruit_RTClib/src/RTClib.cpp | 181 ++++++++++++++++++ lib/Adafruit_RTClib/src/RTClib.h | 34 ++++ src/Custom-sample.h | 3 +- src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp | 41 ++++ src/src/CustomBuild/define_plugin_sets.h | 15 ++ src/src/DataTypes/TimeSource.cpp | 7 +- src/src/DataTypes/TimeSource.h | 5 + src/src/Helpers/ESPEasy_time.cpp | 43 +++++ src/src/WebServer/AdvancedConfigPage.cpp | 20 +- src/src/WebServer/EepromVarPage.cpp | 2 +- src/src/WebServer/I2C_Scanner.cpp | 4 +- 11 files changed, 348 insertions(+), 7 deletions(-) diff --git a/lib/Adafruit_RTClib/src/RTClib.cpp b/lib/Adafruit_RTClib/src/RTClib.cpp index e465752c2c..1c51a59718 100644 --- a/lib/Adafruit_RTClib/src/RTClib.cpp +++ b/lib/Adafruit_RTClib/src/RTClib.cpp @@ -1546,6 +1546,187 @@ void RTC_PCF8563::writeSqwPinMode(Pcf8563SqwPinMode mode) { } // END RTC_PCF8563 implementation +// START RTC_PCF8583 implementation + +// Code inspired by https://github.com/xoseperez/pcf8583 + +/**************************************************************************/ +/*! + @brief Start I2C for the PCF8583 and test succesful connection + @details Use altAddress() before calling begin() to use the alternate address (0x51) + @return True if Wire can find PCF8583 or false otherwise. +*/ +/**************************************************************************/ + +boolean RTC_PCF8583::begin(TwoWire *wireInstance) { + RTCWireBus = wireInstance; + RTCWireBus->beginTransmission(_addr); + if (RTCWireBus->endTransmission() == 0) { + return true; + } + return false; +} + +void RTC_PCF8583::altAddress() { + _addr = PCF8583_ADDRESS_1; +} + +/**************************************************************************/ +/*! + @brief Check the status of the VL bit in the VL_SECONDS register. + @details The PCF8583 does not support voltage-low detector. + @return False +*/ +/**************************************************************************/ + +boolean RTC_PCF8583::lostPower(void) { + return false; // Not supported +} + +/**************************************************************************/ +/*! + @brief Set the date and time + @param dt DateTime to set +*/ +/**************************************************************************/ +void RTC_PCF8583::adjust(const DateTime &dt) { + + uint16_t year = dt.year() - PCF8583_BASE_YEAR; + const uint8_t offset = year & 0xFC; + year -= offset; + RTCWireBus->beginTransmission(_addr); + RTCWireBus->_I2C_WRITE(PCF8583_VL_SECONDS); // start at location 2, VL_SECONDS + RTCWireBus->_I2C_WRITE(bin2bcd(dt.second())); + RTCWireBus->_I2C_WRITE(bin2bcd(dt.minute())); + RTCWireBus->_I2C_WRITE(bin2bcd(dt.hour())); + RTCWireBus->_I2C_WRITE(bin2bcd(dt.day()) | (year < 6)); + RTCWireBus->_I2C_WRITE(bin2bcd(0)); // skip weekdays + RTCWireBus->_I2C_WRITE(bin2bcd(dt.month())); + RTCWireBus->_I2C_WRITE(bin2bcd(dt.year() - 2000)); + RTCWireBus->endTransmission(); + + RTCWireBus->beginTransmission(_addr); + RTCWireBus->_I2C_WRITE(PCF8583_OFFSET_YEAR); // Update offset and year + RTCWireBus->_I2C_WRITE(offset); + RTCWireBus->_I2C_WRITE(year); + RTCWireBus->endTransmission(); +} + +/**************************************************************************/ +/*! + @brief Get the current date/time + @return DateTime object containing the current date/time +*/ +/**************************************************************************/ + +DateTime RTC_PCF8583::now() { + RTCWireBus->beginTransmission(_addr); + RTCWireBus->_I2C_WRITE((byte)PCF8583_VL_SECONDS); + RTCWireBus->endTransmission(); + + RTCWireBus->requestFrom(_addr, (size_t)6); + const uint8_t ss = bcd2bin(RTCWireBus->_I2C_READ() & 0x7F); + const uint8_t mm = bcd2bin(RTCWireBus->_I2C_READ() & 0x7F); + const uint8_t hh = bcd2bin(RTCWireBus->_I2C_READ() & 0x3F); + const uint8_t d_r = RTCWireBus->_I2C_READ(); + const uint8_t d = bcd2bin(d_r & 0x3F); + const uint16_t year = d_r >> 6; + RTCWireBus->_I2C_READ(); // skip 'weekdays' + const uint8_t m = bcd2bin(RTCWireBus->_I2C_READ() & 0x1F); + + const uint8_t last = read_i2c_register(_addr, PCF8583_LAST_YEAR, RTCWireBus); + uint8_t offset = read_i2c_register(_addr, PCF8583_OFFSET_YEAR, RTCWireBus); + + // we have changed the year: happy new year!!! + if (last != year) { + + // we have overflowed the year value: update the offset + if (last > year) { + offset += 4; + write_i2c_register(_addr, PCF8583_OFFSET_YEAR, offset, RTCWireBus); + } + + write_i2c_register(_addr, PCF8583_LAST_YEAR, year, RTCWireBus); + } + const uint16_t y = PCF8583_BASE_YEAR + offset + year; + + return DateTime(y, m, d, hh, mm, ss); +} + +/**************************************************************************/ +/*! + @brief Resets the STOP bit in register Control_1 +*/ +/**************************************************************************/ +void RTC_PCF8583::start(void) { + const uint8_t ctlreg = read_i2c_register(_addr, PCF8583_CONTROL_1, RTCWireBus); + write_i2c_register(_addr, PCF8583_CONTROL_1, ctlreg & 0x7F, + RTCWireBus); +} + +/**************************************************************************/ +/*! + @brief Sets the STOP bit in register Control_1 +*/ +/**************************************************************************/ +void RTC_PCF8583::stop(void) { + const uint8_t ctlreg = read_i2c_register(_addr, PCF8583_CONTROL_1, RTCWireBus); + write_i2c_register(PCF8523_ADDRESS, PCF8583_CONTROL_1, ctlreg | 0x80, + RTCWireBus); +} + +/**************************************************************************/ +/*! + @brief Is the PCF8583 running? Check the STOP bit in register Control_1 + @return 1 if the RTC is running, 0 if not +*/ +/**************************************************************************/ +uint8_t RTC_PCF8583::isrunning() { + const uint8_t ctlreg = read_i2c_register(_addr, PCF8583_CONTROL_1, RTCWireBus); + return !(ctlreg >> 7); // bit 7 = 0 => running +} + +/**************************************************************************/ +/*! + @brief Read data from the PCF8583's NVRAM + @param buf Pointer to a buffer to store the data - make sure it's large + enough to hold size bytes + @param size Number of bytes to read + @param address Starting NVRAM address, from 0 to 235 +*/ +/**************************************************************************/ +void RTC_PCF8583::readnvram(uint8_t *buf, uint8_t size, uint8_t address) { + const int addrByte = PCF8583_NVRAM + address; + RTCWireBus->beginTransmission(_addr); + RTCWireBus->_I2C_WRITE(addrByte); + RTCWireBus->endTransmission(); + + RTCWireBus->requestFrom(_addr, size); + for (uint8_t pos = 0; pos < size; ++pos) { + buf[pos] = RTCWireBus->_I2C_READ(); + } +} + +/**************************************************************************/ +/*! + @brief Write data to the PCF8583 NVRAM + @param address Starting NVRAM address, from 0 to 248 + @param buf Pointer to buffer containing the data to write + @param size Number of bytes in buf to write to NVRAM +*/ +/**************************************************************************/ +void RTC_PCF8583::writenvram(uint8_t address, uint8_t *buf, uint8_t size) { + const int addrByte = PCF8583_NVRAM + address; + RTCWireBus->beginTransmission(_addr); + RTCWireBus->_I2C_WRITE(addrByte); + for (uint8_t pos = 0; pos < size; ++pos) { + RTCWireBus->_I2C_WRITE(buf[pos]); + } + RTCWireBus->endTransmission(); +} + +// END RTC_PCF8583 implementation + /**************************************************************************/ /*! @brief Convert the day of the week to a representation suitable for diff --git a/lib/Adafruit_RTClib/src/RTClib.h b/lib/Adafruit_RTClib/src/RTClib.h index 6642b25f94..fb7d781036 100644 --- a/lib/Adafruit_RTClib/src/RTClib.h +++ b/lib/Adafruit_RTClib/src/RTClib.h @@ -59,6 +59,16 @@ class TimeSpan; ///< temperature value #define DS3232_NVRAM 0x14 ///< Start of RAM registers - 236 bytes, 0x14 to 0xFF +#define PCF8583_ADDRESS 0x50 ///< I2C address for PCF8583 +#define PCF8583_ADDRESS_1 0x51 ///< Alternate I2C address for PCF8583 (A0 = high) +#define PCF8583_CONTROL_1 0x00 ///< Control and status register 1 +#define PCF8583_VL_SECONDS 0x02 ///< register address for VL_SECONDS +#define PCF8583_NVRAM 0x10 ///< Start of RAM registers - 240 bytes, 0x10 to 0xFF + +#define PCF8583_OFFSET_YEAR 0x08 +#define PCF8583_LAST_YEAR 0x09 +#define PCF8583_BASE_YEAR 2012 + /** Constants */ #define SECONDS_PER_DAY 86400L ///< 60 * 60 * 24 #define SECONDS_FROM_1970_TO_2000 \ @@ -457,6 +467,30 @@ class RTC_PCF8563 { TwoWire *RTCWireBus; }; +/**************************************************************************/ +/*! + @brief RTC based on the PCF8583 chip connected via I2C and the Wire library +*/ +/**************************************************************************/ + +class RTC_PCF8583 { +public: + boolean begin(TwoWire *wireInstance = &Wire); + void altAddress(); + boolean lostPower(void); + void adjust(const DateTime &dt); + DateTime now(); + void start(void); + void stop(void); + uint8_t isrunning(); + void readnvram(uint8_t *buf, uint8_t size, uint8_t address); + void writenvram(uint8_t address, uint8_t *buf, uint8_t size); + +protected: + TwoWire *RTCWireBus; + uint8_t _addr = PCF8583_ADDRESS; +}; + /**************************************************************************/ /*! @brief RTC using the internal millis() clock, has to be initialized before diff --git a/src/Custom-sample.h b/src/Custom-sample.h index d6d740cdce..0247d3b210 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -31,7 +31,7 @@ // #define FEATURE_SD 1 // Enable SD card support // #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url // #define FEATURE_EEPROM_EXTERNAL 1 // Enable support for AT24Cxxx EEPROM chips AT24C32(4kB)..AT24C1024(128kB), optional: AT24C2048(256kB) and FRAM MB85RC32(4kB)..MB85RC1M(128kB), optional: MB85RC2M(256kB) -// #define FEATURE_RTC_SRAM_STORAGE 1 // Enable storing values in supported RTC Clock chip SRAM (DS1307, DS3232) +// #define FEATURE_RTC_SRAM_STORAGE 1 // Enable storing values in supported RTC Clock chip SRAM (DS1307, DS3232, PCF8583) #ifdef BUILD_GIT # undef BUILD_GIT @@ -229,6 +229,7 @@ #define FEATURE_SSDP 1 #define FEATURE_EXT_RTC 1 // Support for external RTC clock modules like PCF8563/PCF8523/DS3231/DS1307 +#define FEATURE_EXT_RTC_PCF8583 1 // As we enable External RTC clock modules, we can also include PCF8583 #define FEATURE_PLUGIN_STATS 1 // Support collecting historic data + computing stats on historic data #ifdef ESP8266 diff --git a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp index b1e42f6a94..7c6fd8a5d7 100644 --- a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp +++ b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp @@ -21,6 +21,10 @@ uint8_t checkRTCSRAMEnabled() { const ExtTimeSource_e extRtcType = Settings.ExtTimeSource(); if ((ExtTimeSource_e::DS1307 == extRtcType) || + # if FEATURE_EXT_RTC_PCF8583 + (ExtTimeSource_e::PCF8583 == extRtcType) || + (ExtTimeSource_e::PCF8583a == extRtcType) || + # endif // if FEATURE_EXT_RTC_PCF8583 (ExtTimeSource_e::DS3232 == extRtcType)) { // RTC with SRAM Configured? return static_cast(Settings.ExtTimeSource()); } @@ -39,6 +43,11 @@ uint8_t getRTCI2CAddress() { return DS1307_ADDRESS; case ExtTimeSource_e::DS3232: return DS3231_ADDRESS; + # if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: + case ExtTimeSource_e::PCF8583a: + return PCF8583_ADDRESS; + # endif // if FEATURE_EXT_RTC_PCF8583 case ExtTimeSource_e::DS3231: case ExtTimeSource_e::PCF8523: case ExtTimeSource_e::PCF8563: @@ -65,6 +74,10 @@ uint8_t selectRTCSRAMI2CBus() { switch (type) { case ExtTimeSource_e::DS1307: + # if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: + case ExtTimeSource_e::PCF8583a: + # endif // if FEATURE_EXT_RTC_PCF8583 I2CSelect_Max100kHz_ClockSpeed(i2cBus); break; case ExtTimeSource_e::DS3232: @@ -96,6 +109,11 @@ uint32_t getRTCSRAMSize() { return 56ul; case ExtTimeSource_e::DS3232: return 240ul; + # if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: + case ExtTimeSource_e::PCF8583a: + return 240ul; + # endif // if FEATURE_EXT_RTC_PCF8583 case ExtTimeSource_e::DS3231: case ExtTimeSource_e::PCF8523: case ExtTimeSource_e::PCF8563: @@ -175,6 +193,20 @@ bool writeRTCSRAMSlot(uint32_t slot, } return true; } + # if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: + case ExtTimeSource_e::PCF8583a: + { + RTC_PCF8583 rtc; + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); + const SRAM_STORAGE_FLOAT_TYPE oldData = *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; + + if (!essentiallyEqual(oldData, data)) { + rtc.writenvram(addr, (uint8_t *)&data, sizeof_rtcsram_slot); + } + return true; + } + # endif // if FEATURE_EXT_RTC_PCF8583 case ExtTimeSource_e::DS3231: case ExtTimeSource_e::PCF8523: case ExtTimeSource_e::PCF8563: @@ -210,6 +242,15 @@ SRAM_STORAGE_FLOAT_TYPE readRTCSRAMSlot(uint32_t slot) { rtc.readnvram(_b, sizeof_rtcsram_slot, addr); return *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; } + # if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: + case ExtTimeSource_e::PCF8583a: + { + RTC_PCF8583 rtc; + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); + return *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; + } + # endif // if FEATURE_EXT_RTC_PCF8583 case ExtTimeSource_e::DS3231: case ExtTimeSource_e::PCF8523: case ExtTimeSource_e::PCF8563: diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 78a7f10eec..73a6c853fa 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -711,6 +711,10 @@ To create/register a plugin, you have to : #undef FEATURE_EXT_RTC #endif #define FEATURE_EXT_RTC 0 + #ifdef FEATURE_EXT_RTC_PCF8583 + #undef FEATURE_EXT_RTC_PCF8583 + #endif + #define FEATURE_EXT_RTC_PCF8583 0 #ifdef FEATURE_SYSLOG #undef FEATURE_SYSLOG #endif @@ -3209,6 +3213,10 @@ To create/register a plugin, you have to : #undef FEATURE_EXT_RTC #endif #define FEATURE_EXT_RTC 0 + #ifdef FEATURE_EXT_RTC_PCF8583 + #undef FEATURE_EXT_RTC_PCF8583 + #endif + #define FEATURE_EXT_RTC_PCF8583 0 #ifndef BUILD_NO_DEBUG #define BUILD_NO_DEBUG #endif @@ -4357,6 +4365,13 @@ To create/register a plugin, you have to : #endif #endif #endif // ifndef FEATURE_RTC_SRAM_STORAGE +#ifndef FEATURE_EXT_RTC_PCF8583 + #if defined(PLUGIN_BUILD_MAX_ESP32) + #define FEATURE_EXT_RTC_PCF8583 1 // Also include PCF8583, that has 240 bytes of SRAM + #else // if defined(PLUGIN_BUILD_MAX_ESP32) + #define FEATURE_EXT_RTC_PCF8583 0 // Also include PCF8583, that has 240 bytes of SRAM + #endif // if defined(PLUGIN_BUILD_MAX_ESP32) +#endif #else // if FEATURE_EXT_RTC #define FEATURE_RTC_SRAM_STORAGE 0 // Not available #endif // if FEATURE_EXT_RTC diff --git a/src/src/DataTypes/TimeSource.cpp b/src/src/DataTypes/TimeSource.cpp index 5f462c77b0..45aca6e65d 100644 --- a/src/src/DataTypes/TimeSource.cpp +++ b/src/src/DataTypes/TimeSource.cpp @@ -2,13 +2,18 @@ const __FlashStringHelper* toString(ExtTimeSource_e timeSource) { - switch (timeSource) { + switch (timeSource) + { case ExtTimeSource_e::None: break; case ExtTimeSource_e::DS1307: return F("DS1307"); case ExtTimeSource_e::DS3231: return F("DS3231"); case ExtTimeSource_e::DS3232: return F("DS3232"); case ExtTimeSource_e::PCF8523: return F("PCF8523"); case ExtTimeSource_e::PCF8563: return F("PCF8563"); + #if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: return F("PCF8583 (0x50)"); + case ExtTimeSource_e::PCF8583a: return F("PCF8583 (0x51)"); + #endif // if FEATURE_EXT_RTC_PCF8583 } return F("-"); } diff --git a/src/src/DataTypes/TimeSource.h b/src/src/DataTypes/TimeSource.h index c61afa366d..461226f2d8 100644 --- a/src/src/DataTypes/TimeSource.h +++ b/src/src/DataTypes/TimeSource.h @@ -11,6 +11,11 @@ enum class ExtTimeSource_e { PCF8523, PCF8563, DS3232, + #if FEATURE_EXT_RTC_PCF8583 + PCF8583, + PCF8583a, + #endif // if FEATURE_EXT_RTC_PCF8583 + }; diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index fed2d1b70c..dbf4c0c7f6 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -1030,6 +1030,30 @@ bool ESPEasy_time::ExtRTC_get(uint32_t& unixtime) unixtime = rtc.now().unixtime(); break; } + #if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: + case ExtTimeSource_e::PCF8583a: + { + I2CSelect_Max100kHz_ClockSpeed(i2cBus); + RTC_PCF8583 rtc; + + if (ExtTimeSource_e::PCF8583a == Settings.ExtTimeSource()) { + rtc.altAddress(); // Set alternative address (0x51) + } + + if (!rtc.begin()) { + // Not found + break; + } + + if (rtc.lostPower() || !rtc.isrunning()) { + // Cannot get the time from the module + break; + } + unixtime = rtc.now().unixtime(); + break; + } + #endif // if FEATURE_EXT_RTC_PCF8583 } if (unixtime != 0) { @@ -1110,6 +1134,25 @@ bool ESPEasy_time::ExtRTC_set(uint32_t unixtime) } break; } + #if FEATURE_EXT_RTC_PCF8583 + case ExtTimeSource_e::PCF8583: + case ExtTimeSource_e::PCF8583a: + { + I2CSelect_Max100kHz_ClockSpeed(i2cBus); + RTC_PCF8583 rtc; + + if (ExtTimeSource_e::PCF8583a == Settings.ExtTimeSource()) { + rtc.altAddress(); // Set alternative address (0x51) + } + + if (rtc.begin()) { + rtc.adjust(DateTime(unixtime)); + rtc.start(); + timeAdjusted = true; + } + break; + } + #endif // if FEATURE_EXT_RTC_PCF8583 } if (timeAdjusted) { diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 62392f311d..47641f2325 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -465,15 +465,29 @@ void addFormDstSelect(bool isStart, uint16_t choice) { void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) { addRowLabel(label); - const __FlashStringHelper * options[] = - { F("None"), F("DS1307"), F("DS3231"), F("DS3232"), F("PCF8523"), F("PCF8563")}; + const __FlashStringHelper * options[] = { + F("None"), + toString(ExtTimeSource_e::DS1307), + toString(ExtTimeSource_e::DS3231), + toString(ExtTimeSource_e::DS3232), + toString(ExtTimeSource_e::PCF8523), + toString(ExtTimeSource_e::PCF8563), + #if FEATURE_EXT_RTC_PCF8583 + toString(ExtTimeSource_e::PCF8583), + toString(ExtTimeSource_e::PCF8583a), + #endif // if FEATURE_EXT_RTC_PCF8583 + }; constexpr int optionValues[] = { static_cast(ExtTimeSource_e::None), static_cast(ExtTimeSource_e::DS1307), static_cast(ExtTimeSource_e::DS3231), static_cast(ExtTimeSource_e::DS3232), static_cast(ExtTimeSource_e::PCF8523), - static_cast(ExtTimeSource_e::PCF8563) + static_cast(ExtTimeSource_e::PCF8563), + #if FEATURE_EXT_RTC_PCF8583 + static_cast(ExtTimeSource_e::PCF8583), + static_cast(ExtTimeSource_e::PCF8583a), + #endif // if FEATURE_EXT_RTC_PCF8583 }; const FormSelectorOptions selector(NR_ELEMENTS(optionValues), options, optionValues); diff --git a/src/src/WebServer/EepromVarPage.cpp b/src/src/WebServer/EepromVarPage.cpp index e60d6882f1..059570c121 100644 --- a/src/src/WebServer/EepromVarPage.cpp +++ b/src/src/WebServer/EepromVarPage.cpp @@ -103,7 +103,7 @@ void handle_eepromvars() { html_TR(); html_table_header(F("External RTC SRAM"), 300); - html_table_header(F(""), + html_table_header(toString(Settings.ExtTimeSource()), 500); html_table_header(F(""), 400); diff --git a/src/src/WebServer/I2C_Scanner.cpp b/src/src/WebServer/I2C_Scanner.cpp index 8848544a74..c9e9f10577 100644 --- a/src/src/WebServer/I2C_Scanner.cpp +++ b/src/src/WebServer/I2C_Scanner.cpp @@ -313,11 +313,13 @@ String getKnownI2Cdevice(uint8_t address) { result += F("PCF8591,MCP3221,LM75A,INA219"); break; case 0x50: + result += F("PCF8583,AT24Cxx,MB85RCxx"); + break; case 0x52: result += F("AT24Cxx,MB85RCxx"); break; case 0x51: - result += F("PCF8563,AT24Cxx,MB85RCxx"); + result += F("PCF8563,PCF8583,AT24Cxx,MB85RCxx"); break; case 0x53: result += F("ADXL345,LTR390,AT24Cxx,MB85RCxx"); From 8675411c3c20e5a3f46f53e00cb6314b33bfcc43 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 17 Aug 2026 21:38:40 +0200 Subject: [PATCH 43/44] [Storage] Fix support for PCF8583 RTC --- src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp index 7c6fd8a5d7..0f584ba818 100644 --- a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp +++ b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp @@ -45,8 +45,9 @@ uint8_t getRTCI2CAddress() { return DS3231_ADDRESS; # if FEATURE_EXT_RTC_PCF8583 case ExtTimeSource_e::PCF8583: - case ExtTimeSource_e::PCF8583a: return PCF8583_ADDRESS; + case ExtTimeSource_e::PCF8583a: + return PCF8583_ADDRESS_1; # endif // if FEATURE_EXT_RTC_PCF8583 case ExtTimeSource_e::DS3231: case ExtTimeSource_e::PCF8523: @@ -198,6 +199,11 @@ bool writeRTCSRAMSlot(uint32_t slot, case ExtTimeSource_e::PCF8583a: { RTC_PCF8583 rtc; + + if (ExtTimeSource_e::PCF8583a == Settings.ExtTimeSource()) { + rtc.altAddress(); // Set alternative address (0x51) + } + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); const SRAM_STORAGE_FLOAT_TYPE oldData = *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; @@ -247,6 +253,11 @@ SRAM_STORAGE_FLOAT_TYPE readRTCSRAMSlot(uint32_t slot) { case ExtTimeSource_e::PCF8583a: { RTC_PCF8583 rtc; + + if (ExtTimeSource_e::PCF8583a == Settings.ExtTimeSource()) { + rtc.altAddress(); // Set alternative address (0x51) + } + rtc.readnvram(_b, sizeof_rtcsram_slot, addr); return *(SRAM_STORAGE_FLOAT_TYPE *)&_b[0]; } From f347ae2bc3183375e7f5e38b085c3170892e21a6 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 17 Aug 2026 22:42:30 +0200 Subject: [PATCH 44/44] [Storage] Fix SRAM size for DS3232 RTC --- src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp index 0f584ba818..3910c50ee8 100644 --- a/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp +++ b/src/ESPEasy/eeprom/Helpers/RTCSRAMStorage.cpp @@ -109,7 +109,7 @@ uint32_t getRTCSRAMSize() { case ExtTimeSource_e::DS1307: return 56ul; case ExtTimeSource_e::DS3232: - return 240ul; + return 236ul; # if FEATURE_EXT_RTC_PCF8583 case ExtTimeSource_e::PCF8583: case ExtTimeSource_e::PCF8583a: