From 11070fbd1715180e222c878d950fa7293b1ec149 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 17 Oct 2026 03:32:20 +0200 Subject: [PATCH 01/12] [Build] Reduce build size using static functions As suggested here: https://github.com/letscontrolit/ESPEasy/pull/5654#issuecomment-6222575013 --- src/src/Commands/Blynk.cpp | 2 +- src/src/Commands/Blynk.h | 1 - src/src/Commands/Controller.cpp | 2 +- src/src/Commands/GPIO.cpp | 32 +- src/src/Commands/HTTP.cpp | 2 +- src/src/Commands/HTTP.h | 11 - src/src/Commands/InternalCommands.cpp | 6 +- src/src/Commands/InternalCommands_decoder.cpp | 2 +- src/src/Commands/Networks.cpp | 2 +- src/src/Commands/SDCARD.cpp | 2 +- src/src/Commands/SDCARD.h | 2 - src/src/Commands/Tasks.cpp | 16 +- src/src/Commands/Tasks.h | 2 + src/src/Commands/Timer.cpp | 2 +- src/src/Commands/i2c.cpp | 2 +- src/src/Commands/i2c.h | 4 - .../DataStructs/ESPEasy_packed_raw_data.cpp | 8 +- src/src/DataStructs/ESPEasy_packed_raw_data.h | 8 +- src/src/DataStructs/TimingStats.cpp | 8 +- src/src/DataStructs/TimingStats.h | 6 +- src/src/DataTypes/IntendedRebootReason.cpp | 2 +- src/src/DataTypes/LogLevels.cpp | 2 +- src/src/DataTypes/NodeTypeID.cpp | 32 +- src/src/DataTypes/SchedulerIntervalTimer.cpp | 2 +- src/src/ESPEasyCore/ESPEasyRules.cpp | 2151 +++++++++-------- src/src/ESPEasyCore/ESPEasyRules.h | 81 +- src/src/ESPEasyCore/ESPEasy_loop.cpp | 2 +- src/src/ESPEasyCore/ESPEasy_setup.cpp | 4 +- src/src/Helpers/Audio.cpp | 2 +- src/src/Helpers/Audio.h | 1 - src/src/Helpers/Dallas1WireHelper.cpp | 562 ++--- src/src/Helpers/Dallas1WireHelper.h | 99 +- src/src/Helpers/ESPEasyStatistics.cpp | 2 +- src/src/Helpers/ESPEasyStatistics.h | 2 - src/src/Helpers/Networking.cpp | 2 +- src/src/Helpers/StringProvider.cpp | 16 +- src/src/Helpers/SystemVariables.cpp | 16 +- src/src/Helpers/_CPlugin_DomoticzHelper.cpp | 2 +- src/src/Helpers/_CPlugin_DomoticzHelper.h | 8 - src/src/Helpers/_CPlugin_Helper.cpp | 2 +- src/src/Helpers/_CPlugin_Helper.h | 4 - src/src/Helpers/_CPlugin_Helper_webform.cpp | 4 +- src/src/Helpers/_CPlugin_Helper_webform.h | 2 - src/src/Helpers/_Plugin_SensorTypeHelper.cpp | 4 +- src/src/Helpers/_Plugin_SensorTypeHelper.h | 1 - 45 files changed, 1539 insertions(+), 1586 deletions(-) diff --git a/src/src/Commands/Blynk.cpp b/src/src/Commands/Blynk.cpp index 1afec809d..f765a0ffb 100644 --- a/src/src/Commands/Blynk.cpp +++ b/src/src/Commands/Blynk.cpp @@ -14,7 +14,7 @@ //#include "../../ESPEasy_fdwdecl.h" -controllerIndex_t firstEnabledBlynk_ControllerIndex() { +static controllerIndex_t firstEnabledBlynk_ControllerIndex() { for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(i); diff --git a/src/src/Commands/Blynk.h b/src/src/Commands/Blynk.h index 1e11c447f..b0a9162f2 100644 --- a/src/src/Commands/Blynk.h +++ b/src/src/Commands/Blynk.h @@ -8,7 +8,6 @@ #include "../DataTypes/ControllerIndex.h" -controllerIndex_t firstEnabledBlynk_ControllerIndex(); // FIXME: this should go to PLUGIN_WRITE in _C012.ino const __FlashStringHelper * Command_Blynk_Get(struct EventStruct *event, diff --git a/src/src/Commands/Controller.cpp b/src/src/Commands/Controller.cpp index d1bb204c2..76aab97f7 100644 --- a/src/src/Commands/Controller.cpp +++ b/src/src/Commands/Controller.cpp @@ -12,7 +12,7 @@ #include "../Helpers/Misc.h" // controllerIndex = (event->Par1 - 1); Par1 is here for 1 ... CONTROLLER_MAX -bool validControllerVar(struct EventStruct *event, controllerIndex_t& controllerIndex) +static bool validControllerVar(struct EventStruct *event, controllerIndex_t& controllerIndex) { if (event->Par1 <= 0) { return false; } controllerIndex = static_cast(event->Par1 - 1); diff --git a/src/src/Commands/GPIO.cpp b/src/src/Commands/GPIO.cpp index 997f6131b..95e5466f4 100644 --- a/src/src/Commands/GPIO.cpp +++ b/src/src/Commands/GPIO.cpp @@ -27,33 +27,33 @@ // Normally those would be declared in the .h file as private members // But since these are not part of a class, forward declare them in the .cpp //void createAndSetPortStatus_Mode_State(uint32_t key, uint8_t newMode, int8_t newState); -const __FlashStringHelper * getPluginIDAndPrefix(char selection, pluginID_t& pluginID, bool& success); -void logErrorGpioOffline(const __FlashStringHelper * prefix, int port); -void logErrorGpioOutOfRange(const __FlashStringHelper * prefix, int port, const char* Line = nullptr); -void logErrorGpioNotOutput(const __FlashStringHelper * prefix, int port); -void logErrorModeOutOfRange(const __FlashStringHelper * prefix, int port); -bool gpio_monitor_helper(int port, struct EventStruct *event, const char* Line); -bool gpio_unmonitor_helper(int port, struct EventStruct *event, const char* Line); +static const __FlashStringHelper * getPluginIDAndPrefix(char selection, pluginID_t& pluginID, bool& success); +static void logErrorGpioOffline(const __FlashStringHelper * prefix, int port); +static void logErrorGpioOutOfRange(const __FlashStringHelper * prefix, int port, const char* Line = nullptr); +static void logErrorGpioNotOutput(const __FlashStringHelper * prefix, int port); +static void logErrorModeOutOfRange(const __FlashStringHelper * prefix, int port); +static bool gpio_monitor_helper(int port, struct EventStruct *event, const char* Line); +static bool gpio_unmonitor_helper(int port, struct EventStruct *event, const char* Line); #ifdef USES_P009 -bool mcpgpio_range_pattern_helper(struct EventStruct *event, const char* Line, bool isWritePattern); +static bool mcpgpio_range_pattern_helper(struct EventStruct *event, const char* Line, bool isWritePattern); #endif #ifdef USES_P019 -bool pcfgpio_range_pattern_helper(struct EventStruct *event, const char* Line, bool isWritePattern); +static bool pcfgpio_range_pattern_helper(struct EventStruct *event, const char* Line, bool isWritePattern); #endif -bool gpio_mode_range_helper(uint8_t pin, uint8_t pinMode, struct EventStruct *event, const char* Line); +static bool gpio_mode_range_helper(uint8_t pin, uint8_t pinMode, struct EventStruct *event, const char* Line); #ifdef USES_P019 -uint8_t getPcfAddress(uint8_t pin); +static uint8_t getPcfAddress(uint8_t pin); #endif -bool setGPIOMode(uint8_t pin, uint8_t mode); +static bool setGPIOMode(uint8_t pin, uint8_t mode); #ifdef USES_P019 -bool setPCFMode(uint8_t pin, uint8_t mode); +static bool setPCFMode(uint8_t pin, uint8_t mode); #endif #ifdef USES_P009 -bool setMCPMode(uint8_t pin, uint8_t mode); -bool mcpgpio_plugin_range_helper(uint8_t pin1, uint8_t pin2, uint16_t &result); +static bool setMCPMode(uint8_t pin, uint8_t mode); +static bool mcpgpio_plugin_range_helper(uint8_t pin1, uint8_t pin2, uint16_t &result); #endif #ifdef USES_P019 -bool pcfgpio_plugin_range_helper(uint8_t pin1, uint8_t pin2, uint16_t &result); +static bool pcfgpio_plugin_range_helper(uint8_t pin1, uint8_t pin2, uint16_t &result); #endif diff --git a/src/src/Commands/HTTP.cpp b/src/src/Commands/HTTP.cpp index 31b876905..26b03039b 100644 --- a/src/src/Commands/HTTP.cpp +++ b/src/src/Commands/HTTP.cpp @@ -17,7 +17,7 @@ #endif // if FEATURE_JSON_EVENT #if FEATURE_SEND_TO_HTTP || FEATURE_POST_TO_HTTP || FEATURE_PUT_TO_HTTP -const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, +static const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, const __FlashStringHelper *logIdentifier, const __FlashStringHelper *HttpMethod, const char *Line, diff --git a/src/src/Commands/HTTP.h b/src/src/Commands/HTTP.h index 124881324..2ee4dcf5c 100644 --- a/src/src/Commands/HTTP.h +++ b/src/src/Commands/HTTP.h @@ -3,17 +3,6 @@ #include "../../ESPEasy_common.h" -#if FEATURE_SEND_TO_HTTP || FEATURE_POST_TO_HTTP || FEATURE_PUT_TO_HTTP -const __FlashStringHelper* httpEmitToHTTP(struct EventStruct *event, - const __FlashStringHelper *logIdentifier, - const __FlashStringHelper *HttpMethod, - const char *Line, - const int timeout, - const bool waitForAck, - const bool useHeader, - const bool useBody, - const bool useHttps); -#endif // if FEATURE_SEND_TO_HTTP || FEATURE_POST_TO_HTTP || FEATURE_PUT_TO_HTTP #if FEATURE_SEND_TO_HTTP const __FlashStringHelper* Command_HTTP_SendToHTTP(struct EventStruct *event, const char *Line); diff --git a/src/src/Commands/InternalCommands.cpp b/src/src/Commands/InternalCommands.cpp index a23a71118..bb84c3852 100644 --- a/src/src/Commands/InternalCommands.cpp +++ b/src/src/Commands/InternalCommands.cpp @@ -62,7 +62,7 @@ #include "../Helpers/StringConverter.h" -bool checkNrArguments(const char *cmd, const String& Line, int nrArguments) { +static bool checkNrArguments(const char *cmd, const String& Line, int nrArguments) { if (nrArguments < 0) { return true; } // 0 arguments means argument on pos1 is valid (the command) and argpos 2 should not be there. @@ -129,7 +129,7 @@ bool checkNrArguments(const char *cmd, const String& Line, int nrArguments) { return true; } -bool checkSourceFlags(EventValueSource::Enum source, EventValueSourceGroup::Enum group) { +static bool checkSourceFlags(EventValueSource::Enum source, EventValueSourceGroup::Enum group) { if (EventValueSource::partOfGroup(source, group)) { return true; } @@ -174,7 +174,7 @@ bool InternalCommands::do_command_case_all_restricted(command_function pFunc, return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); } -bool do_command_case_check(command_case_data & data, +static bool do_command_case_check(command_case_data & data, int nrArguments, EventValueSourceGroup::Enum group) { diff --git a/src/src/Commands/InternalCommands_decoder.cpp b/src/src/Commands/InternalCommands_decoder.cpp index 59a8d9ee9..4cbdf5f76 100644 --- a/src/src/Commands/InternalCommands_decoder.cpp +++ b/src/src/Commands/InternalCommands_decoder.cpp @@ -326,7 +326,7 @@ const char Internal_commands_w[] PROGMEM = ; #endif -const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) +static const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) { const char *haystack = nullptr; diff --git a/src/src/Commands/Networks.cpp b/src/src/Commands/Networks.cpp index 929d997e2..3b69ccd5b 100644 --- a/src/src/Commands/Networks.cpp +++ b/src/src/Commands/Networks.cpp @@ -20,7 +20,7 @@ #endif // networkIndex = (event->Par1 - 1); Par1 is here for 1 ... NETWORK_MAX -bool validNetworkVar(struct EventStruct *event, ESPEasy::net::networkIndex_t& networkIndex) +static bool validNetworkVar(struct EventStruct *event, ESPEasy::net::networkIndex_t& networkIndex) { if (event->Par1 <= 0) { return false; } networkIndex = static_cast(event->Par1 - 1); diff --git a/src/src/Commands/SDCARD.cpp b/src/src/Commands/SDCARD.cpp index a8aa43f05..03e6a3408 100644 --- a/src/src/Commands/SDCARD.cpp +++ b/src/src/Commands/SDCARD.cpp @@ -12,7 +12,7 @@ # include -void printDirectory(fs::File dir, int numTabs) +static void printDirectory(fs::File dir, int numTabs) { while (true) { fs::File entry = dir.openNextFile(); diff --git a/src/src/Commands/SDCARD.h b/src/src/Commands/SDCARD.h index c09027078..70d0123cc 100644 --- a/src/src/Commands/SDCARD.h +++ b/src/src/Commands/SDCARD.h @@ -6,8 +6,6 @@ # include -void printDirectory(fs::File dir, - int numTabs); const __FlashStringHelper* Command_SD_LS(struct EventStruct *event, const char *Line); String Command_SD_Remove(struct EventStruct *event, diff --git a/src/src/Commands/Tasks.cpp b/src/src/Commands/Tasks.cpp index 459fe9c2a..c5c67e735 100644 --- a/src/src/Commands/Tasks.cpp +++ b/src/src/Commands/Tasks.cpp @@ -22,7 +22,7 @@ #include "../Helpers/StringParser.h" // taskIndex = (event->Par1 - 1); Par1 is here for 1 ... TASKS_MAX -bool validTaskIndexVar(struct EventStruct *event, taskIndex_t& taskIndex) +static bool validTaskIndexVar(struct EventStruct *event, taskIndex_t& taskIndex) { if (event == nullptr) { return false; } @@ -38,7 +38,7 @@ bool validTaskIndexVar(struct EventStruct *event, taskIndex_t& taskIndex) // taskIndex = (event->Par1 - 1); Par1 is here for 1 ... TASKS_MAX // varNr = event->Par2 - 1; -bool validTaskVars(struct EventStruct *event, taskIndex_t& taskIndex, unsigned int& varNr) +static bool validTaskVars(struct EventStruct *event, taskIndex_t& taskIndex, unsigned int& varNr) { if (event == nullptr) { return false; } @@ -56,7 +56,7 @@ bool validTaskVars(struct EventStruct *event, taskIndex_t& taskIndex, unsigned i return false; } -bool validateAndParseTaskIndexArguments(struct EventStruct * event, const char *Line, taskIndex_t &taskIndex) +static bool validateAndParseTaskIndexArguments(struct EventStruct * event, const char *Line, taskIndex_t &taskIndex) { if (!validTaskIndexVar(event, taskIndex)) { String taskName; @@ -75,7 +75,7 @@ bool validateAndParseTaskIndexArguments(struct EventStruct * event, const char * /** * parse TaskName/TaskValue when not numeric for task name and value name and validate values */ -bool validateAndParseTaskValueArguments(struct EventStruct * event, const char *Line, taskIndex_t &taskIndex, unsigned int &varNr) +static bool validateAndParseTaskValueArguments(struct EventStruct * event, const char *Line, taskIndex_t &taskIndex, unsigned int &varNr) { if (!validTaskVars(event, taskIndex, varNr) || (event->Par2 <= 0 || event->Par2 >= VARS_PER_TASK)) // Extra check required because of shortcutting in validTaskVars() { @@ -101,7 +101,7 @@ bool validateAndParseTaskValueArguments(struct EventStruct * event, const char * return true; } -const __FlashStringHelper * taskValueSet(struct EventStruct *event, const char *Line, taskIndex_t& taskIndex, bool& success) +static const __FlashStringHelper * taskValueSet(struct EventStruct *event, const char *Line, taskIndex_t& taskIndex, bool& success) { String TmpStr1; unsigned int varNr; @@ -198,7 +198,7 @@ const __FlashStringHelper * Command_Task_ClearAll(struct EventStruct *event, con return return_command_success_flashstr(); } -const __FlashStringHelper * Command_Task_EnableDisable(struct EventStruct *event, bool enable, const char *Line) +static const __FlashStringHelper * Command_Task_EnableDisable(struct EventStruct *event, bool enable, const char *Line) { taskIndex_t taskIndex; if (validateAndParseTaskIndexArguments(event, Line, taskIndex)) { @@ -216,7 +216,7 @@ const __FlashStringHelper * Command_Task_EnableDisable(struct EventStruct *event } #if FEATURE_PLUGIN_PRIORITY -const __FlashStringHelper * Command_PriorityTask_DisableTask(struct EventStruct *event, const char *Line) +static const __FlashStringHelper * Command_PriorityTask_DisableTask(struct EventStruct *event, const char *Line) { taskIndex_t taskIndex; @@ -272,7 +272,7 @@ const __FlashStringHelper * Command_Task_ValueSetPresentation(struct EventStruct return taskValueSetString(event, Line, F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE)); } -const __FlashStringHelper * taskValueSetString(struct EventStruct *event, const char *Line, const __FlashStringHelper * storageTemplate, const __FlashStringHelper * uomTemplate, const __FlashStringHelper * vTypeTemplate) +static const __FlashStringHelper * taskValueSetString(struct EventStruct *event, const char *Line, const __FlashStringHelper * storageTemplate, const __FlashStringHelper * uomTemplate, const __FlashStringHelper * vTypeTemplate) { String taskName; taskIndex_t tmpTaskIndex = INVALID_TASK_INDEX; diff --git a/src/src/Commands/Tasks.h b/src/src/Commands/Tasks.h index 65678f718..11f35aefc 100644 --- a/src/src/Commands/Tasks.h +++ b/src/src/Commands/Tasks.h @@ -23,11 +23,13 @@ const __FlashStringHelper* Command_Task_ValueSetDerived(struct EventStruct *even const char *Line); const __FlashStringHelper* Command_Task_ValueSetPresentation(struct EventStruct *event, const char *Line); +/* const __FlashStringHelper* taskValueSetString(struct EventStruct *event, const char *Line, const __FlashStringHelper *storageTemplate, const __FlashStringHelper *uomTemplate = nullptr, const __FlashStringHelper *vTypeTemplate = nullptr); +*/ #endif // if FEATURE_STRING_VARIABLES const __FlashStringHelper* Command_Task_ValueToggle(struct EventStruct *event, const char *Line); diff --git a/src/src/Commands/Timer.cpp b/src/src/Commands/Timer.cpp index 17fde6da9..8d84db66b 100644 --- a/src/src/Commands/Timer.cpp +++ b/src/src/Commands/Timer.cpp @@ -10,7 +10,7 @@ #include "../Helpers/Misc.h" #include "../Helpers/Scheduler.h" -const __FlashStringHelper * command_setRulesTimer(int msecFromNow, int timerIndex, int recurringCount, bool startImmediately = false) { +static const __FlashStringHelper * command_setRulesTimer(int msecFromNow, int timerIndex, int recurringCount, bool startImmediately = false) { if (msecFromNow < 0) { addLog(LOG_LEVEL_ERROR, F("TIMER: time must be positive")); diff --git a/src/src/Commands/i2c.cpp b/src/src/Commands/i2c.cpp index 353ca1c23..ff92fc83d 100644 --- a/src/src/Commands/i2c.cpp +++ b/src/src/Commands/i2c.cpp @@ -13,7 +13,7 @@ #include -void i2c_scanI2Cbus(bool dbg, int8_t channel, uint8_t i2cBus) { +static void i2c_scanI2Cbus(bool dbg, int8_t channel, uint8_t i2cBus) { uint8_t error, address; #if FEATURE_I2CMULTIPLEXER diff --git a/src/src/Commands/i2c.h b/src/src/Commands/i2c.h index 2131873db..2186d91ef 100644 --- a/src/src/Commands/i2c.h +++ b/src/src/Commands/i2c.h @@ -3,10 +3,6 @@ #include "../../ESPEasy_common.h" #if FEATURE_I2C -void i2c_scanI2Cbus(bool dbg, - int8_t channel, - uint8_t i2cBus); - const __FlashStringHelper* Command_i2c_Scanner(struct EventStruct *event, const char *Line); #endif diff --git a/src/src/DataStructs/ESPEasy_packed_raw_data.cpp b/src/src/DataStructs/ESPEasy_packed_raw_data.cpp index 2a1a560da..44d51366b 100644 --- a/src/src/DataStructs/ESPEasy_packed_raw_data.cpp +++ b/src/src/DataStructs/ESPEasy_packed_raw_data.cpp @@ -2,7 +2,7 @@ #if FEATURE_PACKED_RAW_DATA -uint8_t getPackedDataTypeSize(PackedData_enum dtype, float& factor, float& offset) { +static uint8_t getPackedDataTypeSize(PackedData_enum dtype, float& factor, float& offset) { offset = 0; factor = 1; if (dtype > 0x1000 && dtype < 0x12FF) { @@ -35,7 +35,7 @@ uint8_t getPackedDataTypeSize(PackedData_enum dtype, float& factor, float& offse return 0; } -void LoRa_uintToBytes(uint64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor) { +static void LoRa_uintToBytes(uint64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor) { // Clip values to upper limit const uint64_t upperlimit = (1ull << (8*byteSize)) - 1; if (value > upperlimit) { value = upperlimit; } @@ -49,7 +49,7 @@ void LoRa_uintToBytes(uint64_t value, uint8_t byteSize, uint8_t *data, uint8_t& } } -void LoRa_intToBytes(int64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor) { +static void LoRa_intToBytes(int64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor) { // Clip values to lower limit const int64_t lowerlimit = (1ull << ((8*byteSize) - 1)) * -1; if (value < lowerlimit) { value = lowerlimit; } @@ -59,7 +59,7 @@ void LoRa_intToBytes(int64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cu LoRa_uintToBytes(value, byteSize, data, cursor); } -String LoRa_base16Encode(uint8_t *data, size_t size) { +static String LoRa_base16Encode(uint8_t *data, size_t size) { String output; output.reserve(size * 2); char buffer[3]; diff --git a/src/src/DataStructs/ESPEasy_packed_raw_data.h b/src/src/DataStructs/ESPEasy_packed_raw_data.h index 78f90bc37..899456040 100644 --- a/src/src/DataStructs/ESPEasy_packed_raw_data.h +++ b/src/src/DataStructs/ESPEasy_packed_raw_data.h @@ -69,13 +69,13 @@ typedef uint32_t PackedData_enum; #define PackedData_vcc 5 #define PackedData_pct_8 6 -uint8_t getPackedDataTypeSize(PackedData_enum dtype, float& factor, float& offset); +//uint8_t getPackedDataTypeSize(PackedData_enum dtype, float& factor, float& offset); -void LoRa_uintToBytes(uint64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor); +//void LoRa_uintToBytes(uint64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor); -void LoRa_intToBytes(int64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor); +//void LoRa_intToBytes(int64_t value, uint8_t byteSize, uint8_t *data, uint8_t& cursor); -String LoRa_base16Encode(uint8_t *data, size_t size); +//String LoRa_base16Encode(uint8_t *data, size_t size); String LoRa_addInt(uint64_t value, PackedData_enum datatype); diff --git a/src/src/DataStructs/TimingStats.cpp b/src/src/DataStructs/TimingStats.cpp index 7d229a0f4..7989ac6d7 100644 --- a/src/src/DataStructs/TimingStats.cpp +++ b/src/src/DataStructs/TimingStats.cpp @@ -97,7 +97,7 @@ const __FlashStringHelper* getPluginFunctionName(int function) { return F("Unknown"); } -bool mustLogFunction(int function) { +static bool mustLogFunction(int function) { if (!Settings.EnableTimingStats()) { return false; } switch (function) { @@ -170,7 +170,7 @@ const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { return F("Unknown"); } -bool mustLogCFunction(CPlugin::Function function) { +static bool mustLogCFunction(CPlugin::Function function) { if (!Settings.EnableTimingStats()) { return false; } switch (function) { @@ -205,14 +205,14 @@ bool mustLogCFunction(CPlugin::Function function) { return false; } -bool mustLogNWFunction(NWPlugin::Function function) +static bool mustLogNWFunction(NWPlugin::Function function) { return Settings.EnableTimingStats(); } // Return flash string type to reduce bin size -const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { +static const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { switch (stat) { case TimingStatsElements::LOADFILE_STATS: return F("Load File"); case TimingStatsElements::SAVEFILE_STATS: return F("Save File"); diff --git a/src/src/DataStructs/TimingStats.h b/src/src/DataStructs/TimingStats.h index c08c3ffa1..0c9d5c2ef 100644 --- a/src/src/DataStructs/TimingStats.h +++ b/src/src/DataStructs/TimingStats.h @@ -180,10 +180,10 @@ private: const __FlashStringHelper* getPluginFunctionName(int function); -bool mustLogFunction(int function); +//bool mustLogFunction(int function); const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function); -bool mustLogCFunction(CPlugin::Function function); -bool mustLogNWFunction(NWPlugin::Function function); +//bool mustLogCFunction(CPlugin::Function function); +//bool mustLogNWFunction(NWPlugin::Function function); String getMiscStatsName(TimingStatsElements stat); void stopTimerTask(deviceIndex_t T, diff --git a/src/src/DataTypes/IntendedRebootReason.cpp b/src/src/DataTypes/IntendedRebootReason.cpp index 51fe1c9bd..f1b84cbe7 100644 --- a/src/src/DataTypes/IntendedRebootReason.cpp +++ b/src/src/DataTypes/IntendedRebootReason.cpp @@ -1,6 +1,6 @@ #include "../DataTypes/IntendedRebootReason.h" -const __FlashStringHelper * toString_f(IntendedRebootReason_e reason) { +static const __FlashStringHelper * toString_f(IntendedRebootReason_e reason) { switch (reason) { case IntendedRebootReason_e::DeepSleep: return F("DeepSleep"); case IntendedRebootReason_e::DelayedReboot: return F("DelayedReboot"); diff --git a/src/src/DataTypes/LogLevels.cpp b/src/src/DataTypes/LogLevels.cpp index 94fff720e..a12049dd6 100644 --- a/src/src/DataTypes/LogLevels.cpp +++ b/src/src/DataTypes/LogLevels.cpp @@ -17,7 +17,7 @@ constexpr int logLevels[] = }; // We can't have a globally declared flash string array on ESP8266, so need to wrap it in a function. -const __FlashStringHelper* getLogLevelDisplayStringFromIndex(uint8_t index) +static const __FlashStringHelper* getLogLevelDisplayStringFromIndex(uint8_t index) { const __FlashStringHelper *logLevelDisplayStrings[] { diff --git a/src/src/DataTypes/NodeTypeID.cpp b/src/src/DataTypes/NodeTypeID.cpp index 84203b244..a4bdd32dd 100644 --- a/src/src/DataTypes/NodeTypeID.cpp +++ b/src/src/DataTypes/NodeTypeID.cpp @@ -2,14 +2,24 @@ #include "../Helpers/StringConverter.h" -String toNodeTypeDisplayString(uint8_t nodeType) { - const __FlashStringHelper* suffix = F(""); - +static const __FlashStringHelper* toNodeTypeDisplayFlashString(uint8_t nodeType) +{ switch (nodeType) { case NODE_TYPE_ID_ESP_EASY_STD: return F("ESP Easy"); case NODE_TYPE_ID_ESP_EASYM_STD: return F("ESP Easy Mega"); - case NODE_TYPE_ID_ESP_EASY32_STD: break; + case NODE_TYPE_ID_RPI_EASY_STD: return F("RPI Easy"); + case NODE_TYPE_ID_ARDUINO_EASY_STD: return F("Arduino Easy"); + case NODE_TYPE_ID_NANO_EASY_STD: return F("Nano Easy"); + default: + return F(""); + } +} + +static const __FlashStringHelper* toNodeType_ESP32_suffix(uint8_t nodeType) { + const __FlashStringHelper* suffix = F(""); + switch (nodeType) + { case NODE_TYPE_ID_ESP_EASY32S2_STD: suffix = F("-S2"); break; case NODE_TYPE_ID_ESP_EASY32S3_STD: suffix = F("-S3"); break; case NODE_TYPE_ID_ESP_EASY32S31_STD: suffix = F("-S31"); break; @@ -23,11 +33,17 @@ String toNodeTypeDisplayString(uint8_t nodeType) { case NODE_TYPE_ID_ESP_EASY32H4_STD: suffix = F("-H4"); break; case NODE_TYPE_ID_ESP_EASY32P4_STD: suffix = F("-P4"); break; case NODE_TYPE_ID_ESP_EASY32P4r3_STD: suffix = F("-P4r3"); break; - case NODE_TYPE_ID_RPI_EASY_STD: return F("RPI Easy"); - case NODE_TYPE_ID_ARDUINO_EASY_STD: return F("Arduino Easy"); - case NODE_TYPE_ID_NANO_EASY_STD: return F("Nano Easy"); + default: - return EMPTY_STRING; + break; } + return suffix; +} + +String toNodeTypeDisplayString(uint8_t nodeType) { + String res(toNodeTypeDisplayFlashString(nodeType)); + if (!res.isEmpty()) return res; + + const __FlashStringHelper* suffix = toNodeType_ESP32_suffix(nodeType); return concat(F("ESP Easy 32"), suffix); } diff --git a/src/src/DataTypes/SchedulerIntervalTimer.cpp b/src/src/DataTypes/SchedulerIntervalTimer.cpp index b5cc0e24f..a2dff5d59 100644 --- a/src/src/DataTypes/SchedulerIntervalTimer.cpp +++ b/src/src/DataTypes/SchedulerIntervalTimer.cpp @@ -3,7 +3,7 @@ #include "../Helpers/StringConverter.h" #ifndef BUILD_NO_DEBUG -const __FlashStringHelper * toString_f(SchedulerIntervalTimer_e timer) { +static const __FlashStringHelper * toString_f(SchedulerIntervalTimer_e timer) { switch (timer) { case SchedulerIntervalTimer_e::TIMER_20MSEC: return F("TIMER_20MSEC"); case SchedulerIntervalTimer_e::TIMER_100MSEC: return F("TIMER_100MSEC"); diff --git a/src/src/ESPEasyCore/ESPEasyRules.cpp b/src/src/ESPEasyCore/ESPEasyRules.cpp index fb8e64073..3303cfb58 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.cpp +++ b/src/src/ESPEasyCore/ESPEasyRules.cpp @@ -30,11 +30,12 @@ #include #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS -#include -#include "../Helpers/JSON_helper.h" +# include +# include "../Helpers/JSON_helper.h" #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS #ifdef WEBSERVER_NEW_RULES + String EventToFileName(const String& eventName) { int size = eventName.length(); int index = eventName.indexOf('='); @@ -42,12 +43,12 @@ String EventToFileName(const String& eventName) { if (index > -1) { size = index; } -#if defined(ESP8266) +# if defined(ESP8266) String fileName = F("rules/"); -#endif // if defined(ESP8266) -#if defined(ESP32) +# endif // if defined(ESP8266) +# if defined(ESP32) String fileName = F("/rules/"); -#endif // if defined(ESP32) +# endif // if defined(ESP32) fileName += eventName.substring(0, size); fileName.replace('#', RULE_FILE_SEPARAROR); fileName.toLowerCase(); @@ -55,20 +56,19 @@ String EventToFileName(const String& eventName) { } String FileNameToEvent(const String& fileName) { -#if defined(ESP8266) +# if defined(ESP8266) String eventName = fileName.substring(6); -#endif // if defined(ESP8266) -#if defined(ESP32) +# endif // if defined(ESP8266) +# if defined(ESP32) String eventName = fileName.substring(7); -#endif // if defined(ESP32) +# endif // if defined(ESP32) eventName.replace(RULE_FILE_SEPARAROR, '#'); return eventName; } -#endif -void checkRuleSets() { - Cache.rulesHelper.closeAllFiles(); -} +#endif // ifdef WEBSERVER_NEW_RULES + +void checkRuleSets() { Cache.rulesHelper.closeAllFiles(); } /********************************************************************************************\ Process next event from event queue @@ -98,22 +98,25 @@ void rulesProcessing(const String& event) { } START_TIMER #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("rulesProcessing")); + checkRAM(F("rulesProcessing")); #endif // ifndef BUILD_NO_RAM_TRACKER #ifndef BUILD_NO_DEBUG const uint32_t timer = millis(); #endif // ifndef BUILD_NO_DEBUG -// #ifndef BUILD_NO_DEBUG + + // #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("EVENT: "), event)); } -// #endif + + // #endif if (Settings.OldRulesEngine()) { bool eventHandled = false; if (Settings.EnableRulesCaching()) { String filename; size_t pos = 0; + if (Cache.rulesHelper.findMatchingRule(event, filename, pos)) { const bool startOnMatched = true; // We already matched the event eventHandled = rulesProcessingFile(filename, event, pos, startOnMatched); @@ -134,7 +137,7 @@ void rulesProcessing(const String& event) { # ifndef BUILD_NO_DEBUG else { addLog(LOG_LEVEL_DEBUG, strformat(F("EVENT: %s is ingnored. File %s not found."), - event.c_str(), fileName.c_str())); + event.c_str(), fileName.c_str())); } # endif // ifndef BUILD_NO_DEBUG #endif // WEBSERVER_NEW_RULES @@ -150,740 +153,11 @@ void rulesProcessing(const String& event) { backgroundtasks(); } -/********************************************************************************************\ - Rules processing - \*********************************************************************************************/ -bool rulesProcessingFile(const String& fileName, - const String& event, - size_t pos, - bool startOnMatched) { - if (!Settings.UseRules || !fileExists(fileName)) { - return false; - } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("rulesProcessingFile")); - #endif // ifndef BUILD_NO_RAM_TRACKER -#ifndef BUILD_NO_DEBUG - - if (Settings.SerialLogLevel == LOG_LEVEL_DEBUG_DEV) { - serialPrint(F("RuleDebug Processing:")); - serialPrintln(fileName); - serialPrintln(F(" flags CMI parse output:")); - } -#endif // ifndef BUILD_NO_DEBUG - - static uint8_t nestingLevel = 0; - - nestingLevel++; - - if (nestingLevel > RULES_MAX_NESTING_LEVEL) { - addLog(LOG_LEVEL_ERROR, F("EVENT: Error: Nesting level exceeded!")); - nestingLevel--; - return false; - } - - - bool match = false; - bool codeBlock = false; - bool isCommand = false; - bool condition[RULES_IF_MAX_NESTING_LEVEL]; - bool ifBranche[RULES_IF_MAX_NESTING_LEVEL]; - uint8_t ifBlock = 0; - uint8_t fakeIfBlock = 0; - - - bool moreAvailable = true; - bool eventHandled = false; - while (moreAvailable && !eventHandled) { - const bool searchNextOnBlock = !codeBlock && !match; - String line = Cache.rulesHelper.readLn(fileName, pos, moreAvailable, searchNextOnBlock); - - // Parse the line and extract the action (if there is any) - String action; - { - START_TIMER - const bool matched_before_parse = match; - bool isOneLiner = false; - parseCompleteNonCommentLine(line, event, action, match, codeBlock, - isCommand, isOneLiner, condition, ifBranche, ifBlock, - fakeIfBlock, startOnMatched); - if ((matched_before_parse && !match) || isOneLiner) { - // We were processing a matching event and now crossed the "endon" - // Or just dealing with a oneliner. - // So we're done processing - eventHandled = true; - backgroundtasks(); - } - STOP_TIMER(RULES_PARSE_LINE); - } - - if (match) // rule matched for one action or a block of actions - { - START_TIMER - processMatchedRule(action, event, - isCommand, condition, - ifBranche, ifBlock, fakeIfBlock); - STOP_TIMER(RULES_PROCESS_MATCHED); - } - } - -/* - if (f) { - f.close(); - } -*/ - - nestingLevel--; - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("rulesProcessingFile2")); - #endif // ifndef BUILD_NO_RAM_TRACKER - backgroundtasks(); - return eventHandled; // && nestingLevel == 0; -} - - /********************************************************************************************\ Parse string commands \*********************************************************************************************/ -bool get_next_inner_bracket(const String& line, unsigned int& startIndex, int& closingIndex, char closingBracket) -{ - if (line.length() <= 1) { - // Not possible to have opening and closing bracket on a line this short. - return false; - } - char openingBracket = closingBracket; - switch (closingBracket) { - case ']': openingBracket = '['; break; - case '}': openingBracket = '{'; break; - case ')': openingBracket = '('; break; - default: - // unknown bracket type - return false; - } - // Closing bracket should not be found on the first position. - closingIndex = line.indexOf(closingBracket, startIndex + 1); - - if (closingIndex == -1) { - // not found - return false; - } - - for (int i = (closingIndex - 1); (i >= static_cast(startIndex)) && (i >= 0); --i) { - if (line[i] == openingBracket) { - startIndex = i; - return true; - } - } - return false; -} - -bool get_next_argument(const String& fullCommand, int& index, String& argument, char separator) -{ - if (index == -1) { - return false; - } - int newIndex = fullCommand.indexOf(separator, index); - - if (newIndex == -1) { - argument = fullCommand.substring(index); - } else { - argument = fullCommand.substring(index, newIndex); - } - - if (argument.startsWith(String(separator))) { - argument = argument.substring(1); - } - - // addLog(LOG_LEVEL_INFO, String("get_next_argument: ") + String(index) + " " + fullCommand + " " + argument); - index = newIndex; - - if (index != -1) { - ++index; - } - return argument.length() > 0; -} - -const char bitwise_functions[] PROGMEM = "bitread|bitset|bitclear|bitwrite|xor|and|or"; -enum class bitwise_functions_e { - bitread, - bitset, - bitclear, - bitwrite, - xor_e, // protected keywords, thus appended _e - and_e, - or_e -}; - - -bool parse_bitwise_functions(const String& cmd_s_lower, const String& arg1, const String& arg2, const String& arg3, int64_t& result) { - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Bitwise: {"); - log += wrapIfContains(cmd_s_lower, ':', '\"'); - log += ':'; - log += wrapIfContains(arg1, ':', '\"'); - - if (arg2.length() > 0) { - log += ':'; - log += wrapIfContains(arg2, ':', '\"'); - - if (arg3.length() > 0) { - log += ':'; - log += wrapIfContains(arg3, ':', '\"'); - } - } - log += '}'; - addLogMove(LOG_LEVEL_DEBUG, log); - } - #endif - - if (cmd_s_lower.length() < 2) { - return false; - } - - int command_i = GetCommandCode(cmd_s_lower.c_str(), bitwise_functions); - if (command_i == -1) { - // No matching function found - return false; - } - - if (cmd_s_lower.startsWith(F("bit"))) { - uint32_t bitnr = 0; - uint64_t iarg2 = 0; - - if (!validUIntFromString(arg1, bitnr) || !validUInt64FromString(arg2, iarg2)) { - return false; - } - - switch(static_cast(command_i)) { - case bitwise_functions_e::bitread: - // Syntax like {bitread:0:123} to get a single decimal '1' - result = bitRead(iarg2, bitnr); - break; - case bitwise_functions_e::bitset: - // Syntax like {bitset:0:122} to set least significant bit of the given nr '122' to '1' => '123' - result = iarg2; - bitSetULL(result, bitnr); - break; - case bitwise_functions_e::bitclear: - // Syntax like {bitclear:0:123} to set least significant bit of the given nr '123' to '0' => '122' - result = iarg2; - bitClearULL(result, bitnr); - break; - case bitwise_functions_e::bitwrite: - { - uint32_t iarg3 = 0; - // Syntax like {bitwrite:0:122:1} to set least significant bit of the given nr '122' to '1' => '123' - if (validUIntFromString(arg3, iarg3)) { - const int bitvalue = (iarg3 & 1); // Only use the last bit of the given parameter - result = iarg2; - bitWriteULL(result, bitnr, bitvalue); - } else { - // Need 3 parameters, but 3rd one is not a valid uint - return false; - } - break; - } - default: - return false; - } - - // all functions starting with "bit" are checked - return true; - } - - uint64_t iarg1, iarg2 = 0; - - if (!validUInt64FromString(arg1, iarg1) || !validUInt64FromString(arg2, iarg2)) { - return false; - } - - switch(static_cast(command_i)) { - case bitwise_functions_e::xor_e: - // Syntax like {xor:127:15} to XOR the binary values 1111111 and 1111 => 1110000 - result = iarg1 ^ iarg2; - break; - case bitwise_functions_e::and_e: - // Syntax like {and:254:15} to AND the binary values 11111110 and 1111 => 1110 - result = iarg1 & iarg2; - break; - case bitwise_functions_e::or_e: - // Syntax like {or:254:15} to OR the binary values 11111110 and 1111 => 11111111 - result = iarg1 | iarg2; - break; - default: - return false; - - } - return true; -} - -bool parse_math_functions(const String& cmd_s_lower, const String& arg1, const String& arg2, const String& arg3, ESPEASY_RULES_FLOAT_TYPE& result) { - ESPEASY_RULES_FLOAT_TYPE farg1; - float farg2, farg3 = 0.0f; - - if (!cmd_s_lower.startsWith("crc") && !validDoubleFromString(arg1, farg1)) { - return false; - } - - if (equals(cmd_s_lower, F("constrain"))) { - // Contrain a value X to be within range of A to B - // Syntax like {constrain:x:a:b} to constrain x in range a...b - if (validFloatFromString(arg2, farg2) && validFloatFromString(arg3, farg3)) { - if (farg2 > farg3) { - const float tmp = farg2; - farg2 = farg3; - farg3 = tmp; - } - result = constrain(farg1, farg2, farg3); - } else { - return false; - } - } else if (cmd_s_lower.startsWith("crc")) { - std::vector argument = parseHexTextData(arg1, 1); - const String crctype = cmd_s_lower.substring(3); - - if (argument.size() > 0) { - if (equals(crctype, F("8"))) { - result = calc_CRC8(&argument[0], argument.size()); - // } else if (equals(crctype, F("16"))) { // FIXME crc16 not supported until needed/used/tested - // result = calc_CRC16((const char *)argument.data(), argument.size()); - } else if (equals(crctype, F("32"))) { - result = calc_CRC32(&argument[0], argument.size()); - } else { - return false; - } - - if (!arg2.isEmpty() && validDoubleFromString(arg2, farg1)) { // Optional expected crc value - result = essentiallyEqual(result, farg1) ? 1.0 : 0.0; // Return 1 if the calculated crc == expected crc - } - } else { - return false; - } - } else { - // No matching function found - return false; - } - return true; -} - -const char string_commands[] PROGMEM = "substring|indexof|indexof_ci|equals|equals_ci|timetomin|timetosec|strtol|tobin|tohex|ord|urlencode" - #if FEATURE_STRING_VARIABLES - "|lookup" - #if FEATURE_EXTENDED_STRING_FUNCTIONS - "|unescape|escape" - "|parse" - #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS - #endif // if FEATURE_STRING_VARIABLES - #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - "|json" - #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - ; -enum class string_commands_e { - substring, - indexof, - indexof_ci, - equals, - equals_ci, - timetomin, - timetosec, - strtol, - tobin, - tohex, - ord, - urlencode, - #if FEATURE_STRING_VARIABLES - lookup, - #if FEATURE_EXTENDED_STRING_FUNCTIONS - unescape, - escape, - parse, - #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS - #endif // if FEATURE_STRING_VARIABLES - #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - json, - #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS -}; - - -static void hideEscaped(String &line, bool &mustReplaceEscapedBracket, bool &mustReplaceEscapedCurlyBracket, bool &mustReplaceEscapedColon) { - String MaskEscapedBracket; - - if (hasEscapedCharacter(line,'(') || hasEscapedCharacter(line,')')) { - // replace the \( and \) with other characters to mask the escaped brackets so we can continue parsing. - // We have to unmask then after we're finished. - MaskEscapedBracket = static_cast(0x11); // ASCII 0x11 = Device control 1 - line.replace(F("\\("), MaskEscapedBracket); - MaskEscapedBracket = static_cast(0x12); // ASCII 0x12 = Device control 2 - line.replace(F("\\)"), MaskEscapedBracket); - mustReplaceEscapedBracket = true; - } - - if (hasEscapedCharacter(line,'{') || hasEscapedCharacter(line,'}')) { - // replace the \{ and \} with other characters to mask the escaped curly brackets so we can continue parsing. - // We have to unmask then after we're finished. - MaskEscapedBracket = static_cast(0x13); // ASCII 0x13 = Device control 3 - line.replace(F("\\{"), MaskEscapedBracket); - MaskEscapedBracket = static_cast(0x14); // ASCII 0x14 = Device control 4 - line.replace(F("\\}"), MaskEscapedBracket); - mustReplaceEscapedCurlyBracket = true; - } - - #if FEATURE_EXTENDED_STRING_FUNCTIONS - if (hasEscapedCharacter(line, ':')) { - // replace the \: with another character to mask the escaped colon so we can continue parsing. - // We have to unmask then after we're finished. - MaskEscapedBracket = static_cast(0x15); // ASCII 0x15 = NAK - line.replace(F("\\:"), MaskEscapedBracket); - mustReplaceEscapedColon = true; - } - #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS -} - -static void hideMasked(String &line, bool mustReplaceMaskedChars) { - if (mustReplaceMaskedChars) { - line.replace('{', static_cast(0x02)); - line.replace('}', static_cast(0x03)); - } -} - -static void restoreMaskedAndEscaped(String &line, bool mustReplaceMaskedChars, bool mustReplaceEscapedBracket, bool mustReplaceEscapedCurlyBracket, bool mustReplaceEscapedColon) { - String MaskEscapedBracket; - - if (mustReplaceMaskedChars) { - // We now have to check if we did mask some parts and unmask them. - // Let's hope we don't mess up any Unicode here. - line.replace(static_cast(0x02), '{'); - line.replace(static_cast(0x03), '}'); - } - - if (mustReplaceEscapedBracket) { - // We now have to check if we did mask some escaped bracket and unmask them. - // Let's hope we don't mess up any Unicode here. - MaskEscapedBracket = static_cast(0x11); // ASCII 0x11 = Device control 1 - line.replace(MaskEscapedBracket, F("\\(")); - MaskEscapedBracket = static_cast(0x12); // ASCII 0x12 = Device control 2 - line.replace(MaskEscapedBracket, F("\\)")); - } - - if (mustReplaceEscapedCurlyBracket) { - // We now have to check if we did mask some escaped curly bracket and unmask them. - // Let's hope we don't mess up any Unicode here. - MaskEscapedBracket = static_cast(0x13); // ASCII 0x13 = Device control 3 - line.replace(MaskEscapedBracket, F("\\{")); - MaskEscapedBracket = static_cast(0x14); // ASCII 0x14 = Device control 4 - line.replace(MaskEscapedBracket, F("\\}")); - } - - #if FEATURE_EXTENDED_STRING_FUNCTIONS - if (mustReplaceEscapedColon) { - // We now have to check if we did mask some escaped colon and unmask them. - // Let's hope we don't mess up any Unicode here. - MaskEscapedBracket = static_cast(0x15); // ASCII 0x15 = NAK - line.replace(MaskEscapedBracket, F("\\:")); - } - #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS -} - -void parse_string_commands(String& line) { - unsigned int startIndex = 0; - int closingIndex; - - #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - DynamicJsonDocument*root = nullptr; - uint16_t lastJsonMessageLength = 512; - - // Cleanup lambda to deallocate resources - auto cleanupJSON = [&root]() { - if (root != nullptr) { - root->clear(); - delete root; - root = nullptr; - } - }; - #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - bool mustReplaceMaskedChars = false; - bool mustReplaceEscapedBracket = false; - bool mustReplaceEscapedCurlyBracket = false; - bool mustReplaceEscapedColon = false; - - hideEscaped(line, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); - - while (get_next_inner_bracket(line, startIndex, closingIndex, '}')) { - // Command without opening and closing brackets. - const String fullCommand = line.substring(startIndex + 1, closingIndex); - const String cmd_s_lower = parseString(fullCommand, 1, ':'); - const String arg1 = parseStringKeepCaseNoTrim(fullCommand, 2, ':'); - const String arg2 = parseStringKeepCaseNoTrim(fullCommand, 3, ':'); - const String arg3 = parseStringKeepCaseNoTrim(fullCommand, 4, ':'); - - if (cmd_s_lower.length() > 0) { - String replacement; // maybe just replace with empty to avoid looping? - uint64_t iarg1, iarg2 = 0; - ESPEASY_RULES_FLOAT_TYPE fresult{}; - int64_t iresult = 0; - int32_t startpos, endpos = -1; - const bool arg1valid = validIntFromString(arg1, startpos); - const bool arg2valid = validIntFromString(arg2, endpos); - - if (parse_math_functions(cmd_s_lower, arg1, arg2, arg3, fresult)) { - const bool trimTrailingZeros = true; - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - replacement = doubleToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); - #else - replacement = floatToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); - #endif - } else if (parse_bitwise_functions(cmd_s_lower, arg1, arg2, arg3, iresult)) { - replacement = ull2String(iresult); - } else { - - int command_i = GetCommandCode(cmd_s_lower.c_str(), string_commands); - if (command_i != -1) { - const string_commands_e command = static_cast(command_i); - - // addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s %s %s %s"), - // cmd_s_lower.c_str(), arg1.c_str(), arg2.c_str(), arg3.c_str())); - - switch (command) { - case string_commands_e::substring: - // substring arduino style (first char included, last char excluded) - // Syntax like 12345{substring:8:12:ANOTHER HELLO WORLD}67890 - - if (arg1valid) { - if (arg2valid){ - replacement = arg3.substring(startpos, endpos); - } else { - replacement = arg3.substring(startpos); - } - } - break; - #if FEATURE_STRING_VARIABLES - case string_commands_e::lookup: - if (arg1valid && arg2valid && startpos > -1 && endpos > -1) { - replacement = arg3.substring(startpos * endpos, (startpos + 1) * endpos); - } - break; - #if FEATURE_EXTENDED_STRING_FUNCTIONS - case string_commands_e::unescape: - case string_commands_e::escape: - replacement = parseStringToEndKeepCaseNoTrim(fullCommand, 2, ':'); - // Undo escape masks - restoreMaskedAndEscaped(replacement, mustReplaceMaskedChars, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); - - stripEscapeCharacters(replacement); // Always strip first to avoid double-escaping - - if (string_commands_e::escape == command) { - addEscapeCharacters(replacement); - } - // Redo escape masks and masked {} characters - hideEscaped(replacement, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); - hideMasked(replacement, mustReplaceMaskedChars); - - break; - #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS - #endif // if FEATURE_STRING_VARIABLES - case string_commands_e::indexof: - case string_commands_e::indexof_ci: - // indexOf arduino style (0-based position of first char returned, -1 if not found, case sensitive), 3rd argument is search-offset - // indexOf_ci : case-insensitive - // Syntax like {indexof:HELLO:"ANOTHER HELLO WORLD"} => 8, {indexof:hello:"ANOTHER HELLO WORLD"} => -1, {indexof_ci:Hello:"ANOTHER HELLO WORLD"} => 8 - // or like {indexof_ci:hello:"ANOTHER HELLO WORLD":10} => -1 - - if (!arg1.isEmpty() - && !arg2.isEmpty()) { - uint32_t offset = 0; - validUIntFromString(arg3, offset); - if (command == string_commands_e::indexof_ci) { - String arg1copy(arg1); - String arg2copy(arg2); - arg1copy.toLowerCase(); - arg2copy.toLowerCase(); - replacement = arg2copy.indexOf(arg1copy, offset); - } else { - replacement = arg2.indexOf(arg1, offset); - } - } - break; - case string_commands_e::equals: - case string_commands_e::equals_ci: - // equals: compare strings 1 = equal, 0 = unequal (case sensitive) - // equals_ci: case-insensitive compare - // Syntax like {equals:HELLO:HELLO} => 1, {equals:hello:HELLO} => 0, {equals_ci:hello:HELLO} => 1, {equals_ci:hello:BLA} => 0 - - if (!arg1.isEmpty() - && !arg2.isEmpty()) { - if (command == string_commands_e::equals_ci) { - replacement = arg2.equalsIgnoreCase(arg1); - } else { - replacement = arg2.equals(arg1); - } - } - break; - case string_commands_e::timetomin: - case string_commands_e::timetosec: - // time to minutes, transform a substring hh:mm to minutes - // time to seconds, transform a substring hh:mm:ss to seconds - // syntax similar to substring - - if (arg1valid - && arg2valid) { - int timeSeconds = 0; - String timeString; - if(timeStringToSeconds(arg3.substring(startpos, endpos), timeSeconds, timeString)) { - if (command == string_commands_e::timetosec) { - replacement = timeSeconds; - } else { // timetomin - replacement = timeSeconds / 60; - } - } - } - break; - case string_commands_e::strtol: - // string to long integer (from cstdlib) - // Syntax like 1234{strtol:16:38}7890 - if (validUInt64FromString(arg1, iarg1) - && validUInt64FromString(arg2, iarg2)) { - replacement = String(strtoul(arg2.c_str(), nullptr, iarg1)); - } - break; - case string_commands_e::tobin: - // Convert to binary string - // Syntax like 1234{tobin:15}7890 - if (validUInt64FromString(arg1, iarg1)) { - replacement = ull2String(iarg1, BIN); - } - break; - case string_commands_e::tohex: - // Convert to HEX string - // Syntax like 1234{tohex:15[,minHexDigits]}7890 - if (validUInt64FromString(arg1, iarg1)) { - if (!validUInt64FromString(arg2, iarg2)) { - iarg2 = 0; - } - replacement = formatToHex_no_prefix(iarg1, iarg2); - } - break; - case string_commands_e::ord: - { - // Give the ordinal/integer value of the first character of a string - // Syntax like let 1,{ord:B} - uint8_t uval = arg1.c_str()[0]; - replacement = String(uval); - } - break; - case string_commands_e::urlencode: - // Convert to url-encoded string - // Syntax like {urlencode:"string to/encode"} - if (!arg1.isEmpty()) { - replacement = URLEncode(arg1); - } - break; - #if FEATURE_STRING_VARIABLES && FEATURE_EXTENDED_STRING_FUNCTIONS - case string_commands_e::parse: - // parse: a function to retrieve nth param (1-based), with optional separator, default: comma - // {parse::[]:} - if (arg1valid) { - String de_escaped = parseStringToEndKeepCaseNoTrim(fullCommand, 4, ':'); - restoreMaskedAndEscaped(de_escaped, mustReplaceMaskedChars, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); - stripEscapeCharacters(de_escaped); - - replacement = parseStringKeepCase(de_escaped, startpos, arg2.isEmpty() ? ',' : arg2[0]); - - addEscapeCharacters(replacement); - hideEscaped(replacement, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); - hideMasked(replacement, mustReplaceMaskedChars); // re-apply - } - break; - #endif // if FEATURE_STRING_VARIABLES && FEATURE_EXTENDED_STRING_FUNCTIONS - #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - case string_commands_e::json: - // json: get a value from a (valid) JSON string - // {json::[]:} - // attribute-to-retrieve can use: level.attr[n].subattr, multi-level supported, arrays[] are 0-based - // asJson: 1 (true), return a JSON compatible value, useful when retrieving a part of the input for passing on, default: 0 (false) - { - String jsonInput = parseStringToEndKeepCase(fullCommand, 4, ':'); - if (arg1.isEmpty() || jsonInput.isEmpty()) { - break; - } - restoreMaskedAndEscaped(jsonInput, mustReplaceMaskedChars, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); - stripEscapeCharacters(jsonInput); - String arg1_ = arg1; - stripEscapeCharacters(arg1_); - - if ((nullptr != root) && (jsonInput.length() * 2.5 > lastJsonMessageLength)) { - cleanupJSON(); - } - - // Resize lastJsonMessageLength if needed - if (jsonInput.length() * 2 > lastJsonMessageLength) { - lastJsonMessageLength = jsonInput.length() * 2; - } - - // Allocate memory for root if needed - if (nullptr == root) { - // Try to allocate in PSRAM or 2nd heap if possible - constexpr unsigned size = sizeof(DynamicJsonDocument); - void *ptr = special_calloc(1, size); - if (ptr) { - root = new (ptr) DynamicJsonDocument(lastJsonMessageLength); // Dynamic allocation - } - } - - if (nullptr != root) { - // if (loglevelActiveFor(LOG_LEVEL_INFO)) { // for development debugging - // addLog(LOG_LEVEL_INFO, strformat(F("parse json: %s"), jsonInput.c_str())); - // } - // Parse the JSON - DeserializationError error = deserializeJson(*root, jsonInput); - - if (!error) { - // if (loglevelActiveFor(LOG_LEVEL_INFO)) { // for development debugging - // addLog(LOG_LEVEL_INFO, strformat(F("parse json for %s"), arg1.c_str())); - // } - replacement = getJsonValue(root, arg1_, arg2valid && 1 == endpos); - } - } - break; - } - #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - } - } - } - - if (replacement.isEmpty()) { - // part in braces is not a supported command. - // replace the {} with other characters to mask the braces so we can continue parsing. - // We have to unmask then after we're finished. - // See: https://github.com/letscontrolit/ESPEasy/issues/2932#issuecomment-596139096 - replacement = line.substring(startIndex, closingIndex + 1); - mustReplaceMaskedChars = true; - hideMasked(replacement, mustReplaceMaskedChars); // apply - } - - // Replace the full command including opening and closing brackets. - line.replace(line.substring(startIndex, closingIndex + 1), replacement); - - /* - if (replacement.length() > 0) { - addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s -> %s"), fullCommand.c_str(), replacement.c_str()); - } - */ - } - } - - #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - - if (nullptr != root) { - cleanupJSON(); - } - #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS - - restoreMaskedAndEscaped(line, mustReplaceMaskedChars, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); -} - -void substitute_eventvalue(String& line, const String& event) { +static void substitute_eventvalue(String& line, const String& event) { if (substitute_eventvalue_CallBack_ptr != nullptr) { substitute_eventvalue_CallBack_ptr(line, event); } @@ -926,7 +200,8 @@ void substitute_eventvalue(String& line, const String& event) { // Syntax: %eventvalueX|Y% // With: X = event value nr, Y = default value when eventvalue does not exist. String defaultValue('0'); - int or_else_pos = line.indexOf('|', eventvalue_pos); + int or_else_pos = line.indexOf('|', eventvalue_pos); + if ((or_else_pos == -1) || (or_else_pos > percent_pos)) { or_else_pos = percent_pos; } else { @@ -941,7 +216,7 @@ void substitute_eventvalue(String& line, const String& event) { line.replace(eventvalue, argString); } else { argc = nr.toInt(); - + // argc will be 0 on invalid int (0 was already handled) if (argc > 0) { String tmpParam; @@ -978,12 +253,15 @@ void substitute_eventvalue(String& line, const String& event) { } } -void parseCompleteNonCommentLine(String& line, const String& event, - String& action, bool& match, - bool& codeBlock, bool& isCommand, bool& isOneLiner, - bool condition[], bool ifBranche[], - uint8_t& ifBlock, uint8_t& fakeIfBlock, - bool startOnMatched) { +/********************************************************************************************\ + Rules processing + \*********************************************************************************************/ +static void parseCompleteNonCommentLine(String& line, const String& event, + String& action, bool& match, + bool& codeBlock, bool& isCommand, bool& isOneLiner, + bool condition[], bool ifBranche[], + uint8_t& ifBlock, uint8_t& fakeIfBlock, + bool startOnMatched) { if (line.length() == 0) { return; } @@ -998,7 +276,7 @@ void parseCompleteNonCommentLine(String& line, const String& event, } if (line.equalsIgnoreCase(F("endon"))) // Check if action block has ended, then we will - // wait for a new "on" rule + // wait for a new "on" rule { isCommand = false; codeBlock = false; @@ -1008,7 +286,7 @@ void parseCompleteNonCommentLine(String& line, const String& event, return; } - const bool lineStartsWith_pct_event = line.startsWith(F("%event"));; + const bool lineStartsWith_pct_event = line.startsWith(F("%event")); isCommand = true; @@ -1033,14 +311,16 @@ void parseCompleteNonCommentLine(String& line, const String& event, // processed { action.clear(); + if (lineStartsWith_on) { ifBlock = 0; fakeIfBlock = 0; String ruleEvent; + if (getEventFromRulesLine(line, ruleEvent, action)) { START_TIMER - match = startOnMatched || ruleMatch(event, ruleEvent); + match = startOnMatched || ruleMatch(event, ruleEvent); STOP_TIMER(RULES_MATCH); } else { match = false; @@ -1058,22 +338,24 @@ void parseCompleteNonCommentLine(String& line, const String& event, } } else { #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { // keep the line for the log action = line; } else { - action = std::move(line); + action = std::move(line); } - #else + #else // ifndef BUILD_NO_DEBUG action = std::move(line); - #endif + #endif // ifndef BUILD_NO_DEBUG } if (isCommand && lineStartsWith_pct_event) { action = concat(F("restrict,"), action); + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, - concat(F("Rules : Prefix command with 'restrict': "), action)); + addLogMove(LOG_LEVEL_ERROR, + concat(F("Rules : Prefix command with 'restrict': "), action)); } } @@ -1091,228 +373,71 @@ void parseCompleteNonCommentLine(String& line, const String& event, #endif // ifndef BUILD_NO_DEBUG } -void processMatchedRule(String& action, const String& event, - bool& isCommand, bool condition[], bool ifBranche[], - uint8_t& ifBlock, uint8_t& fakeIfBlock) { - String lcAction = action; - - lcAction.toLowerCase(); - lcAction.trim(); - - if (fakeIfBlock) { - isCommand = false; - } - else if (ifBlock) { - if (condition[ifBlock - 1] != ifBranche[ifBlock - 1]) { - isCommand = false; - } - } - int split = - lcAction.startsWith(F("elseif ")) ? 0 : -1; // check for optional "elseif" condition - - if (split != -1) { - // Found "elseif" condition - isCommand = false; - - if (ifBlock && !fakeIfBlock) { - if (ifBranche[ifBlock - 1]) { - if (condition[ifBlock - 1]) { - ifBranche[ifBlock - 1] = false; - } - else { - String check = lcAction.substring(split + 7); - check.trim(); - condition[ifBlock - 1] = conditionMatchExtended(check); -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": [elseif "); - log += check; - log += F("]="); - log += boolToString(condition[ifBlock - 1]); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } - } - } - } else { - // check for optional "if" condition - split = lcAction.startsWith(F("if ")) ? 0 : -1; - - if (split != -1) { - if (ifBlock < RULES_IF_MAX_NESTING_LEVEL) { - if (isCommand) { - ifBlock++; - String check = lcAction.substring(split + 3); - check.trim(); - condition[ifBlock - 1] = conditionMatchExtended(check); - ifBranche[ifBlock - 1] = true; -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": [if "); - log += check; - log += F("]="); - log += boolToString(condition[ifBlock - 1]); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } else { - fakeIfBlock++; - } - } else { - fakeIfBlock++; - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": Error: IF Nesting level exceeded!"); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - isCommand = false; - } - } - - if ((equals(lcAction, F("else"))) && !fakeIfBlock) // in case of an "else" block of - // actions, set ifBranche to - // false - { - ifBranche[ifBlock - 1] = false; - isCommand = false; -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": [else]="); - log += boolToString(condition[ifBlock - 1] == ifBranche[ifBlock - 1]); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } - - if (equals(lcAction, F("endif"))) // conditional block ends here - { - if (fakeIfBlock) { - fakeIfBlock--; - } - else if (ifBlock) { - ifBlock--; - } - isCommand = false; - } - - // process the action if it's a command and unconditional, or conditional and - // the condition matches the if or else block. - if (isCommand) { - substitute_eventvalue(action, event); - - const bool executeRestricted = equals(parseString(action, 1), F("restrict")); -// #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String actionlog = executeRestricted ? F("ACT : (restricted) ") : F("ACT : "); - actionlog += action; - addLogMove(LOG_LEVEL_INFO, actionlog); - } -// #endif - if (executeRestricted) { - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED, parseStringToEndKeepCase(action, 2)}); - } else { - // Use action.c_str() here as we need to preserve the action string. - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES, action.c_str()}); - } - delay(0); - } -} - -/********************************************************************************************\ - Check if an event matches to a given rule - \*********************************************************************************************/ - - /********************************************************************************************\ Check expression \*********************************************************************************************/ -bool conditionMatchExtended(String& check) { - int condAnd = -1; - int condOr = -1; - bool rightcond = false; - bool leftcond = conditionMatch(check); // initial check - #ifndef BUILD_NO_DEBUG - String debugstr; +// Balance the count of parentheses (aka round braces) by adding the missing left or right parentheses, if any +// Returns the number of added parentheses, < 0 is left parentheses added, > 0 is right parentheses added +static int balanceParentheses(String& string) { + int left = 0; + int right = 0; - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += boolToString(leftcond); - } - #endif // ifndef BUILD_NO_DEBUG - - do { - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("conditionMatchExtended: "); - log += debugstr; - log += ' '; - log += wrap_String(check, '"'); - addLogMove(LOG_LEVEL_DEBUG, log); + for (unsigned int i = 0; i < string.length(); i++) { + if (string[i] == '(') { + left++; + } else if (string[i] == ')') { + right++; } - #endif // ifndef BUILD_NO_DEBUG - condAnd = check.indexOf(F(" and ")); - condOr = check.indexOf(F(" or ")); - - if ((condAnd > 0) || (condOr > 0)) { // we got AND/OR - if ((condAnd > 0) && (((condOr < 0) /*&& (condOr < condAnd)*/) || - ((condOr > 0) && (condOr > condAnd)))) { // AND is first - check = check.substring(condAnd + 5); - rightcond = conditionMatch(check); - leftcond = (leftcond && rightcond); - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += F(" && "); - } - #endif // ifndef BUILD_NO_DEBUG - } else { // OR is first - check = check.substring(condOr + 4); - rightcond = conditionMatch(check); - leftcond = (leftcond || rightcond); - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += F(" || "); - } - #endif // ifndef BUILD_NO_DEBUG - } - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += boolToString(rightcond); - } - #endif // ifndef BUILD_NO_DEBUG - } - } while (condAnd > 0 || condOr > 0); - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - check = debugstr; } - #endif // ifndef BUILD_NO_DEBUG - return leftcond; + + if (left != right) { + string.reserve(string.length() + abs(right - left)); // Re-allocate max. once + } + + if (left > right) { + for (int i = 0; i < left - right; i++) { + string += ')'; + } + } else if (right > left) { + for (int i = 0; i < right - left; i++) { + string = String('(') + string; // This is quite 'expensive' + } + } + return left - right; } +/********************************************************************************************\ + Matching time notations HH:MM:SS and HH:MM:SS and HH + \*********************************************************************************************/ -void logtimeStringToSeconds(const String& tBuf, int hours, int minutes, int seconds, bool valid) +static bool get_next_argument(const String& fullCommand, int& index, String& argument, char separator) +{ + if (index == -1) { + return false; + } + int newIndex = fullCommand.indexOf(separator, index); + + if (newIndex == -1) { + argument = fullCommand.substring(index); + } else { + argument = fullCommand.substring(index, newIndex); + } + + if (argument.startsWith(String(separator))) { + argument = argument.substring(1); + } + + // addLog(LOG_LEVEL_INFO, String("get_next_argument: ") + String(index) + " " + fullCommand + " " + argument); + index = newIndex; + + if (index != -1) { + ++index; + } + return argument.length() > 0; +} + +static void logtimeStringToSeconds(const String& tBuf, int hours, int minutes, int seconds, bool valid) { #ifndef BUILD_NO_DEBUG @@ -1321,6 +446,7 @@ void logtimeStringToSeconds(const String& tBuf, int hours, int minutes, int seco log = F("timeStringToSeconds: "); log += wrap_String(tBuf, '"'); log += F(" --> "); + if (valid) { log += formatIntLeadingZeroes(hours, 2); log += ':'; @@ -1338,16 +464,18 @@ void logtimeStringToSeconds(const String& tBuf, int hours, int minutes, int seco // convert old and new time string to nr of seconds // return whether it should be considered a time string. -bool timeStringToSeconds(const String& tBuf, int& time_seconds, String& timeString) { +static bool timeStringToSeconds(const String& tBuf, int& time_seconds, String& timeString) { { // Make sure we only check for expected characters // e.g. if 10:11:12 > 7:07 and 10:11:12 < 20:09:04 - // Should only try to match "7:07", not "7:07 and 10:11:12" + // Should only try to match "7:07", not "7:07 and 10:11:12" // Or else it will find "7:07:11" bool done = false; + for (uint8_t pos = 0; !done && timeString.length() < 8 && pos < tBuf.length(); ++pos) { char c = tBuf[pos]; - if (isdigit(c) || c == ':') { + + if (isdigit(c) || (c == ':')) { timeString += c; } else { done = true; @@ -1406,34 +534,7 @@ bool timeStringToSeconds(const String& tBuf, int& time_seconds, String& timeStri return validTime; } -// Balance the count of parentheses (aka round braces) by adding the missing left or right parentheses, if any -// Returns the number of added parentheses, < 0 is left parentheses added, > 0 is right parentheses added -int balanceParentheses(String& string) { - int left = 0; - int right = 0; - for (unsigned int i = 0; i < string.length(); i++) { - if (string[i] == '(') { - left++; - } else if (string[i] == ')') { - right++; - } - } - if (left != right) { - string.reserve(string.length() + abs(right - left)); // Re-allocate max. once - } - if (left > right) { - for (int i = 0; i < left - right; i++) { - string += ')'; - } - } else if (right > left) { - for (int i = 0; i < right - left; i++) { - string = String('(') + string; // This is quite 'expensive' - } - } - return left - right; -} - -bool conditionMatch(const String& check) { +static bool conditionMatch(const String& check) { int posStart, posEnd; char compare; @@ -1449,12 +550,12 @@ bool conditionMatch(const String& check) { ESPEASY_RULES_FLOAT_TYPE Value1{}; ESPEASY_RULES_FLOAT_TYPE Value2{}; - int timeInSec1 = 0; - int timeInSec2 = 0; + int timeInSec1 = 0; + int timeInSec2 = 0; String timeString1, timeString2; - bool validTime1 = timeStringToSeconds(tmpCheck1, timeInSec1, timeString1); - bool validTime2 = timeStringToSeconds(tmpCheck2, timeInSec2, timeString2); - bool result = false; + bool validTime1 = timeStringToSeconds(tmpCheck1, timeInSec1, timeString1); + bool validTime2 = timeStringToSeconds(tmpCheck2, timeInSec2, timeString2); + bool result = false; bool compareTimes = false; @@ -1469,8 +570,9 @@ bool conditionMatch(const String& check) { } else { int condAnd = tmpCheck2.indexOf(F(" and ")); int condOr = tmpCheck2.indexOf(F(" or ")); - if (condAnd > -1 || condOr > -1) { // Only parse first condition, rest will be parsed 'later' - if (condAnd > -1 && (condOr == -1 || condAnd < condOr)) { + + if ((condAnd > -1) || (condOr > -1)) { // Only parse first condition, rest will be parsed 'later' + if ((condAnd > -1) && ((condOr == -1) || (condAnd < condOr))) { tmpCheck2 = tmpCheck2.substring(0, condAnd); } else if (condOr > -1) { tmpCheck2 = tmpCheck2.substring(0, condOr); @@ -1479,16 +581,17 @@ bool conditionMatch(const String& check) { } balanceParentheses(tmpCheck1); balanceParentheses(tmpCheck2); + if (isError(Calculate(tmpCheck1, Value1 #if FEATURE_STRING_VARIABLES , false // suppress logging specific errors when parsing strings #endif // if FEATURE_STRING_VARIABLES - )) || + )) || isError(Calculate(tmpCheck2, Value2 #if FEATURE_STRING_VARIABLES , false // suppress logging specific errors when parsing strings #endif // if FEATURE_STRING_VARIABLES - ))) + ))) { #if FEATURE_STRING_VARIABLES result = compareStringValues(compare, tmpCheck1, tmpCheck2); @@ -1518,19 +621,19 @@ bool conditionMatch(const String& check) { log += '('; const bool trimTrailingZeros = true; - log += compareTimes ? String(timeInSec1) : -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - doubleToString(Value1, 6, trimTrailingZeros); -#else - floatToString(Value1, 6, trimTrailingZeros); -#endif + log += compareTimes ? String(timeInSec1) : +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + doubleToString(Value1, 6, trimTrailingZeros); +# else + floatToString(Value1, 6, trimTrailingZeros); +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE log += wrap_String(check.substring(posStart, posEnd), ' '); // Compare - log += compareTimes ? String(timeInSec2) : -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - doubleToString(Value2, 6, trimTrailingZeros); -#else - floatToString(Value2, 6, trimTrailingZeros); -#endif + log += compareTimes ? String(timeInSec2) : +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + doubleToString(Value2, 6, trimTrailingZeros); +# else + floatToString(Value2, 6, trimTrailingZeros); +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE log += ')'; addLogMove(LOG_LEVEL_DEBUG, log); } @@ -1540,6 +643,980 @@ bool conditionMatch(const String& check) { return result; } +static bool conditionMatchExtended(String& check) { + int condAnd = -1; + int condOr = -1; + bool rightcond = false; + bool leftcond = conditionMatch(check); // initial check + + #ifndef BUILD_NO_DEBUG + String debugstr; + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += boolToString(leftcond); + } + #endif // ifndef BUILD_NO_DEBUG + + do + { + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("conditionMatchExtended: "); + log += debugstr; + log += ' '; + log += wrap_String(check, '"'); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifndef BUILD_NO_DEBUG + condAnd = check.indexOf(F(" and ")); + condOr = check.indexOf(F(" or ")); + + if ((condAnd > 0) || (condOr > 0)) { // we got AND/OR + if ((condAnd > 0) && (((condOr < 0) /*&& (condOr < condAnd)*/) || + ((condOr > 0) && (condOr > condAnd)))) { // AND is first + check = check.substring(condAnd + 5); + rightcond = conditionMatch(check); + leftcond = (leftcond && rightcond); + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += F(" && "); + } + #endif // ifndef BUILD_NO_DEBUG + } else { // OR is first + check = check.substring(condOr + 4); + rightcond = conditionMatch(check); + leftcond = (leftcond || rightcond); + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += F(" || "); + } + #endif // ifndef BUILD_NO_DEBUG + } + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += boolToString(rightcond); + } + #endif // ifndef BUILD_NO_DEBUG + } + } while (condAnd > 0 || condOr > 0); + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + check = debugstr; + } + #endif // ifndef BUILD_NO_DEBUG + return leftcond; +} + +static void processMatchedRule(String& action, const String& event, + bool& isCommand, bool condition[], bool ifBranche[], + uint8_t& ifBlock, uint8_t& fakeIfBlock) { + String lcAction = action; + + lcAction.toLowerCase(); + lcAction.trim(); + + if (fakeIfBlock) { + isCommand = false; + } + else if (ifBlock) { + if (condition[ifBlock - 1] != ifBranche[ifBlock - 1]) { + isCommand = false; + } + } + int split = + lcAction.startsWith(F("elseif ")) ? 0 : -1; // check for optional "elseif" condition + + if (split != -1) { + // Found "elseif" condition + isCommand = false; + + if (ifBlock && !fakeIfBlock) { + if (ifBranche[ifBlock - 1]) { + if (condition[ifBlock - 1]) { + ifBranche[ifBlock - 1] = false; + } + else { + String check = lcAction.substring(split + 7); + check.trim(); + condition[ifBlock - 1] = conditionMatchExtended(check); +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": [elseif "); + log += check; + log += F("]="); + log += boolToString(condition[ifBlock - 1]); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } + } + } + } else { + // check for optional "if" condition + split = lcAction.startsWith(F("if ")) ? 0 : -1; + + if (split != -1) { + if (ifBlock < RULES_IF_MAX_NESTING_LEVEL) { + if (isCommand) { + ifBlock++; + String check = lcAction.substring(split + 3); + check.trim(); + condition[ifBlock - 1] = conditionMatchExtended(check); + ifBranche[ifBlock - 1] = true; +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": [if "); + log += check; + log += F("]="); + log += boolToString(condition[ifBlock - 1]); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } else { + fakeIfBlock++; + } + } else { + fakeIfBlock++; + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": Error: IF Nesting level exceeded!"); + addLogMove(LOG_LEVEL_ERROR, log); + } + } + isCommand = false; + } + } + + if ((equals(lcAction, F("else"))) && !fakeIfBlock) // in case of an "else" block of + // actions, set ifBranche to + // false + { + ifBranche[ifBlock - 1] = false; + isCommand = false; +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": [else]="); + log += boolToString(condition[ifBlock - 1] == ifBranche[ifBlock - 1]); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } + + if (equals(lcAction, F("endif"))) // conditional block ends here + { + if (fakeIfBlock) { + fakeIfBlock--; + } + else if (ifBlock) { + ifBlock--; + } + isCommand = false; + } + + // process the action if it's a command and unconditional, or conditional and + // the condition matches the if or else block. + if (isCommand) { + substitute_eventvalue(action, event); + + const bool executeRestricted = equals(parseString(action, 1), F("restrict")); + + // #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String actionlog = executeRestricted ? F("ACT : (restricted) ") : F("ACT : "); + actionlog += action; + addLogMove(LOG_LEVEL_INFO, actionlog); + } + + // #endif + if (executeRestricted) { + ExecuteCommand_all({ EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED, parseStringToEndKeepCase(action, 2) }); + } else { + // Use action.c_str() here as we need to preserve the action string. + ExecuteCommand_all({ EventValueSource::Enum::VALUE_SOURCE_RULES, action.c_str() }); + } + delay(0); + } +} + +bool rulesProcessingFile(const String& fileName, + const String& event, + size_t pos, + bool startOnMatched) { + if (!Settings.UseRules || !fileExists(fileName)) { + return false; + } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("rulesProcessingFile")); + #endif // ifndef BUILD_NO_RAM_TRACKER +#ifndef BUILD_NO_DEBUG + + if (Settings.SerialLogLevel == LOG_LEVEL_DEBUG_DEV) { + serialPrint(F("RuleDebug Processing:")); + serialPrintln(fileName); + serialPrintln(F(" flags CMI parse output:")); + } +#endif // ifndef BUILD_NO_DEBUG + + static uint8_t nestingLevel = 0; + + nestingLevel++; + + if (nestingLevel > RULES_MAX_NESTING_LEVEL) { + addLog(LOG_LEVEL_ERROR, F("EVENT: Error: Nesting level exceeded!")); + nestingLevel--; + return false; + } + + + bool match = false; + bool codeBlock = false; + bool isCommand = false; + bool condition[RULES_IF_MAX_NESTING_LEVEL]; + bool ifBranche[RULES_IF_MAX_NESTING_LEVEL]; + uint8_t ifBlock = 0; + uint8_t fakeIfBlock = 0; + + + bool moreAvailable = true; + bool eventHandled = false; + + while (moreAvailable && !eventHandled) { + const bool searchNextOnBlock = !codeBlock && !match; + String line = Cache.rulesHelper.readLn(fileName, pos, moreAvailable, searchNextOnBlock); + + // Parse the line and extract the action (if there is any) + String action; + { + START_TIMER + const bool matched_before_parse = match; + bool isOneLiner = false; + parseCompleteNonCommentLine(line, event, action, match, codeBlock, + isCommand, isOneLiner, condition, ifBranche, ifBlock, + fakeIfBlock, startOnMatched); + + if ((matched_before_parse && !match) || isOneLiner) { + // We were processing a matching event and now crossed the "endon" + // Or just dealing with a oneliner. + // So we're done processing + eventHandled = true; + backgroundtasks(); + } + STOP_TIMER(RULES_PARSE_LINE); + } + + if (match) // rule matched for one action or a block of actions + { + START_TIMER + processMatchedRule(action, event, + isCommand, condition, + ifBranche, ifBlock, fakeIfBlock); + STOP_TIMER(RULES_PROCESS_MATCHED); + } + } + + /* + if (f) { + f.close(); + } + */ + + nestingLevel--; + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("rulesProcessingFile2")); + #endif // ifndef BUILD_NO_RAM_TRACKER + backgroundtasks(); + return eventHandled; // && nestingLevel == 0; +} + +/********************************************************************************************\ + Parse string commands + \*********************************************************************************************/ +static bool get_next_inner_bracket(const String& line, unsigned int& startIndex, int& closingIndex, char closingBracket) +{ + if (line.length() <= 1) { + // Not possible to have opening and closing bracket on a line this short. + return false; + } + char openingBracket = closingBracket; + + switch (closingBracket) + { + case ']': openingBracket = '['; + break; + case '}': openingBracket = '{'; + break; + case ')': openingBracket = '('; + break; + default: + // unknown bracket type + return false; + } + + // Closing bracket should not be found on the first position. + closingIndex = line.indexOf(closingBracket, startIndex + 1); + + if (closingIndex == -1) { + // not found + return false; + } + + for (int i = (closingIndex - 1); (i >= static_cast(startIndex)) && (i >= 0); --i) { + if (line[i] == openingBracket) { + startIndex = i; + return true; + } + } + return false; +} + +const char bitwise_functions[] PROGMEM = "bitread|bitset|bitclear|bitwrite|xor|and|or"; +enum class bitwise_functions_e { + bitread, + bitset, + bitclear, + bitwrite, + xor_e, // protected keywords, thus appended _e + and_e, + or_e + +}; + +static bool parse_bitwise_functions(const String& cmd_s_lower, const String& arg1, const String& arg2, const String& arg3, int64_t& result) { + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Bitwise: {"); + log += wrapIfContains(cmd_s_lower, ':', '\"'); + log += ':'; + log += wrapIfContains(arg1, ':', '\"'); + + if (arg2.length() > 0) { + log += ':'; + log += wrapIfContains(arg2, ':', '\"'); + + if (arg3.length() > 0) { + log += ':'; + log += wrapIfContains(arg3, ':', '\"'); + } + } + log += '}'; + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifndef BUILD_NO_DEBUG + + if (cmd_s_lower.length() < 2) { + return false; + } + + int command_i = GetCommandCode(cmd_s_lower.c_str(), bitwise_functions); + + if (command_i == -1) { + // No matching function found + return false; + } + + if (cmd_s_lower.startsWith(F("bit"))) { + uint32_t bitnr = 0; + uint64_t iarg2 = 0; + + if (!validUIntFromString(arg1, bitnr) || !validUInt64FromString(arg2, iarg2)) { + return false; + } + + switch (static_cast(command_i)) + { + case bitwise_functions_e::bitread: + // Syntax like {bitread:0:123} to get a single decimal '1' + result = bitRead(iarg2, bitnr); + break; + case bitwise_functions_e::bitset: + // Syntax like {bitset:0:122} to set least significant bit of the given nr '122' to '1' => '123' + result = iarg2; + bitSetULL(result, bitnr); + break; + case bitwise_functions_e::bitclear: + // Syntax like {bitclear:0:123} to set least significant bit of the given nr '123' to '0' => '122' + result = iarg2; + bitClearULL(result, bitnr); + break; + case bitwise_functions_e::bitwrite: + { + uint32_t iarg3 = 0; + + // Syntax like {bitwrite:0:122:1} to set least significant bit of the given nr '122' to '1' => '123' + if (validUIntFromString(arg3, iarg3)) { + const int bitvalue = (iarg3 & 1); // Only use the last bit of the given parameter + result = iarg2; + bitWriteULL(result, bitnr, bitvalue); + } else { + // Need 3 parameters, but 3rd one is not a valid uint + return false; + } + break; + } + default: + return false; + } + + // all functions starting with "bit" are checked + return true; + } + + uint64_t iarg1, iarg2 = 0; + + if (!validUInt64FromString(arg1, iarg1) || !validUInt64FromString(arg2, iarg2)) { + return false; + } + + switch (static_cast(command_i)) + { + case bitwise_functions_e::xor_e: + // Syntax like {xor:127:15} to XOR the binary values 1111111 and 1111 => 1110000 + result = iarg1 ^ iarg2; + break; + case bitwise_functions_e::and_e: + // Syntax like {and:254:15} to AND the binary values 11111110 and 1111 => 1110 + result = iarg1 & iarg2; + break; + case bitwise_functions_e::or_e: + // Syntax like {or:254:15} to OR the binary values 11111110 and 1111 => 11111111 + result = iarg1 | iarg2; + break; + default: + return false; + + } + return true; +} + +static bool parse_math_functions(const String & cmd_s_lower, + const String & arg1, + const String & arg2, + const String & arg3, + ESPEASY_RULES_FLOAT_TYPE& result) { + ESPEASY_RULES_FLOAT_TYPE farg1; + float farg2, farg3 = 0.0f; + + if (!cmd_s_lower.startsWith("crc") && !validDoubleFromString(arg1, farg1)) { + return false; + } + + if (equals(cmd_s_lower, F("constrain"))) { + // Contrain a value X to be within range of A to B + // Syntax like {constrain:x:a:b} to constrain x in range a...b + if (validFloatFromString(arg2, farg2) && validFloatFromString(arg3, farg3)) { + if (farg2 > farg3) { + const float tmp = farg2; + farg2 = farg3; + farg3 = tmp; + } + result = constrain(farg1, farg2, farg3); + } else { + return false; + } + } else if (cmd_s_lower.startsWith("crc")) { + std::vector argument = parseHexTextData(arg1, 1); + const String crctype = cmd_s_lower.substring(3); + + if (argument.size() > 0) { + if (equals(crctype, F("8"))) { + result = calc_CRC8(&argument[0], argument.size()); + + // } else if (equals(crctype, F("16"))) { // FIXME crc16 not supported until needed/used/tested + // result = calc_CRC16((const char *)argument.data(), argument.size()); + } else if (equals(crctype, F("32"))) { + result = calc_CRC32(&argument[0], argument.size()); + } else { + return false; + } + + if (!arg2.isEmpty() && validDoubleFromString(arg2, farg1)) { // Optional expected crc value + result = essentiallyEqual(result, farg1) ? 1.0 : 0.0; // Return 1 if the calculated crc == expected crc + } + } else { + return false; + } + } else { + // No matching function found + return false; + } + return true; +} + +const char string_commands[] PROGMEM = "substring|indexof|indexof_ci|equals|equals_ci|timetomin|timetosec|strtol|tobin|tohex|ord|urlencode" + #if FEATURE_STRING_VARIABLES + "|lookup" + # if FEATURE_EXTENDED_STRING_FUNCTIONS + "|unescape|escape" + "|parse" + # endif // if FEATURE_EXTENDED_STRING_FUNCTIONS + #endif // if FEATURE_STRING_VARIABLES + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + "|json" + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS +; +enum class string_commands_e { + substring, + indexof, + indexof_ci, + equals, + equals_ci, + timetomin, + timetosec, + strtol, + tobin, + tohex, + ord, + urlencode, + #if FEATURE_STRING_VARIABLES + lookup, + # if FEATURE_EXTENDED_STRING_FUNCTIONS + unescape, + escape, + parse, + # endif // if FEATURE_EXTENDED_STRING_FUNCTIONS + #endif // if FEATURE_STRING_VARIABLES + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + json, + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + +}; + +static void hideEscaped(String& line, bool& mustReplaceEscapedBracket, bool& mustReplaceEscapedCurlyBracket, bool& mustReplaceEscapedColon) { + String MaskEscapedBracket; + + if (hasEscapedCharacter(line, '(') || hasEscapedCharacter(line, ')')) { + // replace the \( and \) with other characters to mask the escaped brackets so we can continue parsing. + // We have to unmask then after we're finished. + MaskEscapedBracket = static_cast(0x11); // ASCII 0x11 = Device control 1 + line.replace(F("\\("), MaskEscapedBracket); + MaskEscapedBracket = static_cast(0x12); // ASCII 0x12 = Device control 2 + line.replace(F("\\)"), MaskEscapedBracket); + mustReplaceEscapedBracket = true; + } + + if (hasEscapedCharacter(line, '{') || hasEscapedCharacter(line, '}')) { + // replace the \{ and \} with other characters to mask the escaped curly brackets so we can continue parsing. + // We have to unmask then after we're finished. + MaskEscapedBracket = static_cast(0x13); // ASCII 0x13 = Device control 3 + line.replace(F("\\{"), MaskEscapedBracket); + MaskEscapedBracket = static_cast(0x14); // ASCII 0x14 = Device control 4 + line.replace(F("\\}"), MaskEscapedBracket); + mustReplaceEscapedCurlyBracket = true; + } + + #if FEATURE_EXTENDED_STRING_FUNCTIONS + + if (hasEscapedCharacter(line, ':')) { + // replace the \: with another character to mask the escaped colon so we can continue parsing. + // We have to unmask then after we're finished. + MaskEscapedBracket = static_cast(0x15); // ASCII 0x15 = NAK + line.replace(F("\\:"), MaskEscapedBracket); + mustReplaceEscapedColon = true; + } + #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS +} + +static void hideMasked(String& line, bool mustReplaceMaskedChars) { + if (mustReplaceMaskedChars) { + line.replace('{', static_cast(0x02)); + line.replace('}', static_cast(0x03)); + } +} + +static void restoreMaskedAndEscaped(String& line, + bool mustReplaceMaskedChars, + bool mustReplaceEscapedBracket, + bool mustReplaceEscapedCurlyBracket, + bool mustReplaceEscapedColon) { + String MaskEscapedBracket; + + if (mustReplaceMaskedChars) { + // We now have to check if we did mask some parts and unmask them. + // Let's hope we don't mess up any Unicode here. + line.replace(static_cast(0x02), '{'); + line.replace(static_cast(0x03), '}'); + } + + if (mustReplaceEscapedBracket) { + // We now have to check if we did mask some escaped bracket and unmask them. + // Let's hope we don't mess up any Unicode here. + MaskEscapedBracket = static_cast(0x11); // ASCII 0x11 = Device control 1 + line.replace(MaskEscapedBracket, F("\\(")); + MaskEscapedBracket = static_cast(0x12); // ASCII 0x12 = Device control 2 + line.replace(MaskEscapedBracket, F("\\)")); + } + + if (mustReplaceEscapedCurlyBracket) { + // We now have to check if we did mask some escaped curly bracket and unmask them. + // Let's hope we don't mess up any Unicode here. + MaskEscapedBracket = static_cast(0x13); // ASCII 0x13 = Device control 3 + line.replace(MaskEscapedBracket, F("\\{")); + MaskEscapedBracket = static_cast(0x14); // ASCII 0x14 = Device control 4 + line.replace(MaskEscapedBracket, F("\\}")); + } + + #if FEATURE_EXTENDED_STRING_FUNCTIONS + + if (mustReplaceEscapedColon) { + // We now have to check if we did mask some escaped colon and unmask them. + // Let's hope we don't mess up any Unicode here. + MaskEscapedBracket = static_cast(0x15); // ASCII 0x15 = NAK + line.replace(MaskEscapedBracket, F("\\:")); + } + #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS +} + +void parse_string_commands(String& line) { + unsigned int startIndex = 0; + int closingIndex; + + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + DynamicJsonDocument*root = nullptr; + uint16_t lastJsonMessageLength = 512; + + // Cleanup lambda to deallocate resources + auto cleanupJSON = [&root]() { + if (root != nullptr) { + root->clear(); + delete root; + root = nullptr; + } + }; + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + bool mustReplaceMaskedChars = false; + bool mustReplaceEscapedBracket = false; + bool mustReplaceEscapedCurlyBracket = false; + bool mustReplaceEscapedColon = false; + + hideEscaped(line, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); + + while (get_next_inner_bracket(line, startIndex, closingIndex, '}')) { + // Command without opening and closing brackets. + const String fullCommand = line.substring(startIndex + 1, closingIndex); + const String cmd_s_lower = parseString(fullCommand, 1, ':'); + const String arg1 = parseStringKeepCaseNoTrim(fullCommand, 2, ':'); + const String arg2 = parseStringKeepCaseNoTrim(fullCommand, 3, ':'); + const String arg3 = parseStringKeepCaseNoTrim(fullCommand, 4, ':'); + + if (cmd_s_lower.length() > 0) { + String replacement; // maybe just replace with empty to avoid looping? + uint64_t iarg1, iarg2 = 0; + ESPEASY_RULES_FLOAT_TYPE fresult{}; + int64_t iresult = 0; + int32_t startpos, endpos = -1; + const bool arg1valid = validIntFromString(arg1, startpos); + const bool arg2valid = validIntFromString(arg2, endpos); + + if (parse_math_functions(cmd_s_lower, arg1, arg2, arg3, fresult)) { + const bool trimTrailingZeros = true; + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + replacement = doubleToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); + #else + replacement = floatToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (parse_bitwise_functions(cmd_s_lower, arg1, arg2, arg3, iresult)) { + replacement = ull2String(iresult); + } else { + + int command_i = GetCommandCode(cmd_s_lower.c_str(), string_commands); + + if (command_i != -1) { + const string_commands_e command = static_cast(command_i); + + // addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s %s %s %s"), + // cmd_s_lower.c_str(), arg1.c_str(), arg2.c_str(), arg3.c_str())); + + switch (command) + { + case string_commands_e::substring: + // substring arduino style (first char included, last char excluded) + // Syntax like 12345{substring:8:12:ANOTHER HELLO WORLD}67890 + + if (arg1valid) { + if (arg2valid) { + replacement = arg3.substring(startpos, endpos); + } else { + replacement = arg3.substring(startpos); + } + } + break; + #if FEATURE_STRING_VARIABLES + case string_commands_e::lookup: + + if (arg1valid && arg2valid && (startpos > -1) && (endpos > -1)) { + replacement = arg3.substring(startpos * endpos, (startpos + 1) * endpos); + } + break; + # if FEATURE_EXTENDED_STRING_FUNCTIONS + case string_commands_e::unescape: + case string_commands_e::escape: + replacement = parseStringToEndKeepCaseNoTrim(fullCommand, 2, ':'); + + // Undo escape masks + restoreMaskedAndEscaped(replacement, + mustReplaceMaskedChars, + mustReplaceEscapedBracket, + mustReplaceEscapedCurlyBracket, + mustReplaceEscapedColon); + + stripEscapeCharacters(replacement); // Always strip first to avoid double-escaping + + if (string_commands_e::escape == command) { + addEscapeCharacters(replacement); + } + + // Redo escape masks and masked {} characters + hideEscaped(replacement, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); + hideMasked(replacement, mustReplaceMaskedChars); + + break; + # endif // if FEATURE_EXTENDED_STRING_FUNCTIONS + #endif // if FEATURE_STRING_VARIABLES + case string_commands_e::indexof: + case string_commands_e::indexof_ci: + // indexOf arduino style (0-based position of first char returned, -1 if not found, case sensitive), 3rd argument is + // search-offset + // indexOf_ci : case-insensitive + // Syntax like {indexof:HELLO:"ANOTHER HELLO WORLD"} => 8, {indexof:hello:"ANOTHER HELLO WORLD"} => -1, + // {indexof_ci:Hello:"ANOTHER HELLO WORLD"} => 8 + // or like {indexof_ci:hello:"ANOTHER HELLO WORLD":10} => -1 + + if (!arg1.isEmpty() + && !arg2.isEmpty()) { + uint32_t offset = 0; + validUIntFromString(arg3, offset); + + if (command == string_commands_e::indexof_ci) { + String arg1copy(arg1); + String arg2copy(arg2); + arg1copy.toLowerCase(); + arg2copy.toLowerCase(); + replacement = arg2copy.indexOf(arg1copy, offset); + } else { + replacement = arg2.indexOf(arg1, offset); + } + } + break; + case string_commands_e::equals: + case string_commands_e::equals_ci: + // equals: compare strings 1 = equal, 0 = unequal (case sensitive) + // equals_ci: case-insensitive compare + // Syntax like {equals:HELLO:HELLO} => 1, {equals:hello:HELLO} => 0, {equals_ci:hello:HELLO} => 1, {equals_ci:hello:BLA} => 0 + + if (!arg1.isEmpty() + && !arg2.isEmpty()) { + if (command == string_commands_e::equals_ci) { + replacement = arg2.equalsIgnoreCase(arg1); + } else { + replacement = arg2.equals(arg1); + } + } + break; + case string_commands_e::timetomin: + case string_commands_e::timetosec: + // time to minutes, transform a substring hh:mm to minutes + // time to seconds, transform a substring hh:mm:ss to seconds + // syntax similar to substring + + if (arg1valid + && arg2valid) { + int timeSeconds = 0; + String timeString; + + if (timeStringToSeconds(arg3.substring(startpos, endpos), timeSeconds, timeString)) { + if (command == string_commands_e::timetosec) { + replacement = timeSeconds; + } else { // timetomin + replacement = timeSeconds / 60; + } + } + } + break; + case string_commands_e::strtol: + + // string to long integer (from cstdlib) + // Syntax like 1234{strtol:16:38}7890 + if (validUInt64FromString(arg1, iarg1) + && validUInt64FromString(arg2, iarg2)) { + replacement = String(strtoul(arg2.c_str(), nullptr, iarg1)); + } + break; + case string_commands_e::tobin: + + // Convert to binary string + // Syntax like 1234{tobin:15}7890 + if (validUInt64FromString(arg1, iarg1)) { + replacement = ull2String(iarg1, BIN); + } + break; + case string_commands_e::tohex: + + // Convert to HEX string + // Syntax like 1234{tohex:15[,minHexDigits]}7890 + if (validUInt64FromString(arg1, iarg1)) { + if (!validUInt64FromString(arg2, iarg2)) { + iarg2 = 0; + } + replacement = formatToHex_no_prefix(iarg1, iarg2); + } + break; + case string_commands_e::ord: + { + // Give the ordinal/integer value of the first character of a string + // Syntax like let 1,{ord:B} + uint8_t uval = arg1.c_str()[0]; + replacement = String(uval); + break; + } + case string_commands_e::urlencode: + + // Convert to url-encoded string + // Syntax like {urlencode:"string to/encode"} + if (!arg1.isEmpty()) { + replacement = URLEncode(arg1); + } + break; + #if FEATURE_STRING_VARIABLES && FEATURE_EXTENDED_STRING_FUNCTIONS + case string_commands_e::parse: + + // parse: a function to retrieve nth param (1-based), with optional separator, default: comma + // {parse::[]:} + if (arg1valid) { + String de_escaped = parseStringToEndKeepCaseNoTrim(fullCommand, 4, ':'); + restoreMaskedAndEscaped(de_escaped, + mustReplaceMaskedChars, + mustReplaceEscapedBracket, + mustReplaceEscapedCurlyBracket, + mustReplaceEscapedColon); + stripEscapeCharacters(de_escaped); + + replacement = parseStringKeepCase(de_escaped, startpos, arg2.isEmpty() ? ',' : arg2[0]); + + addEscapeCharacters(replacement); + hideEscaped(replacement, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); + hideMasked(replacement, mustReplaceMaskedChars); // re-apply + } + break; + #endif // if FEATURE_STRING_VARIABLES && FEATURE_EXTENDED_STRING_FUNCTIONS + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + case string_commands_e::json: + // json: get a value from a (valid) JSON string + // {json::[]:} + // attribute-to-retrieve can use: level.attr[n].subattr, multi-level supported, arrays[] are 0-based + // asJson: 1 (true), return a JSON compatible value, useful when retrieving a part of the input for passing on, default: 0 + // (false) + { + String jsonInput = parseStringToEndKeepCase(fullCommand, 4, ':'); + + if (arg1.isEmpty() || jsonInput.isEmpty()) { + break; + } + restoreMaskedAndEscaped(jsonInput, + mustReplaceMaskedChars, + mustReplaceEscapedBracket, + mustReplaceEscapedCurlyBracket, + mustReplaceEscapedColon); + stripEscapeCharacters(jsonInput); + String arg1_ = arg1; + stripEscapeCharacters(arg1_); + + if ((nullptr != root) && (jsonInput.length() * 2.5 > lastJsonMessageLength)) { + cleanupJSON(); + } + + // Resize lastJsonMessageLength if needed + if (jsonInput.length() * 2 > lastJsonMessageLength) { + lastJsonMessageLength = jsonInput.length() * 2; + } + + // Allocate memory for root if needed + if (nullptr == root) { + // Try to allocate in PSRAM or 2nd heap if possible + constexpr unsigned size = sizeof(DynamicJsonDocument); + void *ptr = special_calloc(1, size); + + if (ptr) { + root = new (ptr) DynamicJsonDocument(lastJsonMessageLength); // Dynamic allocation + } + } + + if (nullptr != root) { + // if (loglevelActiveFor(LOG_LEVEL_INFO)) { // for development debugging + // addLog(LOG_LEVEL_INFO, strformat(F("parse json: %s"), jsonInput.c_str())); + // } + // Parse the JSON + DeserializationError error = deserializeJson(*root, jsonInput); + + if (!error) { + // if (loglevelActiveFor(LOG_LEVEL_INFO)) { // for development debugging + // addLog(LOG_LEVEL_INFO, strformat(F("parse json for %s"), arg1.c_str())); + // } + replacement = getJsonValue(root, arg1_, arg2valid && 1 == endpos); + } + } + break; + } + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + } + } + } + + if (replacement.isEmpty()) { + // part in braces is not a supported command. + // replace the {} with other characters to mask the braces so we can continue parsing. + // We have to unmask then after we're finished. + // See: https://github.com/letscontrolit/ESPEasy/issues/2932#issuecomment-596139096 + replacement = line.substring(startIndex, closingIndex + 1); + mustReplaceMaskedChars = true; + hideMasked(replacement, mustReplaceMaskedChars); // apply + } + + // Replace the full command including opening and closing brackets. + line.replace(line.substring(startIndex, closingIndex + 1), replacement); + + /* + if (replacement.length() > 0) { + addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s -> %s"), fullCommand.c_str(), replacement.c_str()); + } + */ + } + } + + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + + if (nullptr != root) { + cleanupJSON(); + } + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + + restoreMaskedAndEscaped(line, mustReplaceMaskedChars, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); +} + +/********************************************************************************************\ + Check if an event matches to a given rule + \*********************************************************************************************/ + /********************************************************************************************\ Generate rule events based on task refresh \*********************************************************************************************/ @@ -1552,7 +1629,7 @@ void createRuleEvents(struct EventStruct *event) { if (!validDeviceIndex(DeviceIndex)) { return; } const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - String taskName = getTaskDeviceName(event->TaskIndex); + String taskName = getTaskDeviceName(event->TaskIndex); #if FEATURE_STRING_VARIABLES String postfix; const String search = getDerivedValueSearchAndPostfix(taskName, postfix); @@ -1563,10 +1640,11 @@ void createRuleEvents(struct EventStruct *event) { if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { size_t expectedSize = 2 + getTaskDeviceName(event->TaskIndex).length(); expectedSize += Cache.getTaskDeviceValueName(event->TaskIndex, 0).length(); - + bool appendCompleteStringvalue = false; String eventString; + if (reserve_special(eventString, expectedSize + event->String2.length())) { appendCompleteStringvalue = true; } else if (!reserve_special(eventString, expectedSize + 24)) { @@ -1579,6 +1657,7 @@ void createRuleEvents(struct EventStruct *event) { eventString += Cache.getTaskDeviceValueName(event->TaskIndex, 0); eventString += '='; eventString += '`'; + if (appendCompleteStringvalue) { eventString += event->String2; } else { @@ -1587,12 +1666,13 @@ void createRuleEvents(struct EventStruct *event) { eventString += event->String2.substring(event->String2.length() - 10); } eventString += '`'; - eventQueue.addMove(std::move(eventString)); + eventQueue.addMove(std::move(eventString)); } else if (Settings.CombineTaskValues_SingleEvent(event->TaskIndex)) { String eventvalues; reserve_special(eventvalues, 32); // Enough for most use cases, prevent lots of memory allocations. uint8_t varNr = 0; + for (; varNr < valueCount; ++varNr) { if (varNr != 0) { eventvalues += ','; @@ -1600,14 +1680,17 @@ void createRuleEvents(struct EventStruct *event) { eventvalues += formatUserVarNoCheck(event, varNr); } #if FEATURE_STRING_VARIABLES + if (Settings.EventAndLogDerivedTaskValues(event->TaskIndex)) { auto it = customStringVar.begin(); + while (it != customStringVar.end()) { if (it->first.startsWith(search) && it->first.endsWith(postfix)) { if (!it->second.isEmpty()) { String value(it->second); value = parseTemplateAndCalculate(value); + if (varNr != 0) { eventvalues += ','; } @@ -1629,18 +1712,22 @@ void createRuleEvents(struct EventStruct *event) { } } #if FEATURE_STRING_VARIABLES + if (!Settings.CombineTaskValues_SingleEvent(event->TaskIndex) // Send events of derived values for all sensorTypes when enabled && Settings.EventAndLogDerivedTaskValues(event->TaskIndex)) { taskName.toLowerCase(); auto it = customStringVar.begin(); + while (it != customStringVar.end()) { if (it->first.startsWith(search) && it->first.endsWith(postfix)) { - String valueName = it->first.substring(search.length(), it->first.indexOf('-')); + String valueName = it->first.substring(search.length(), it->first.indexOf('-')); const String vname2 = getDerivedValueName(taskName, valueName); + if (!vname2.isEmpty()) { valueName = vname2; } + if (!it->second.isEmpty()) { String value(it->second); value = parseTemplateAndCalculate(value); diff --git a/src/src/ESPEasyCore/ESPEasyRules.h b/src/src/ESPEasyCore/ESPEasyRules.h index 9d2de45ec..a5e415885 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.h +++ b/src/src/ESPEasyCore/ESPEasyRules.h @@ -14,7 +14,7 @@ String EventToFileName(const String& eventName); String FileNameToEvent(const String& fileName); -#endif +#endif // ifdef WEBSERVER_NEW_RULES void checkRuleSets(); @@ -27,7 +27,7 @@ bool processNextEvent(); /********************************************************************************************\ Rules processing \*********************************************************************************************/ -void rulesProcessing(const String& event); +void rulesProcessing(const String& event); /********************************************************************************************\ Rules processing @@ -35,89 +35,16 @@ void rulesProcessing(const String& event); \*********************************************************************************************/ bool rulesProcessingFile(const String& fileName, const String& event, - size_t pos = 0, - bool startOnMatched = false); - + size_t pos = 0, + bool startOnMatched = false); /********************************************************************************************\ Parse string commands \*********************************************************************************************/ -bool get_next_inner_bracket(const String& line, - unsigned int& startIndex, - int & closingIndex, - char closingBracket); - -bool get_next_argument(const String& fullCommand, - int & index, - String & argument, - char separator); - -bool parse_bitwise_functions(const String& cmd_s_lower, - const String& arg1, - const String& arg2, - const String& arg3, - uint32_t & result); -bool parse_math_functions(const String& cmd_s_lower, - const String& arg1, - const String& arg2, - const String& arg3, - ESPEASY_RULES_FLOAT_TYPE & result); - void parse_string_commands(String& line); - -void substitute_eventvalue(String & line, - const String& event); - -void parseCompleteNonCommentLine(String& line, - const String& event, - String& action, - bool & match, - bool & codeBlock, - bool & isCommand, - bool & isOneLiner, - bool condition[], - bool ifBranche[], - uint8_t & ifBlock, - uint8_t & fakeIfBlock, - bool startOnMatched); - -void processMatchedRule(String& action, - const String& event, - bool & isCommand, - bool condition[], - bool ifBranche[], - uint8_t & ifBlock, - uint8_t & fakeIfBlock); - - -/********************************************************************************************\ - Check expression - \*********************************************************************************************/ -bool conditionMatchExtended(String& check); - - -bool conditionMatch(const String& check); - -/********************************************************************************************\ - Matching time notations HH:MM:SS and HH:MM:SS and HH - \*********************************************************************************************/ - -void logtimeStringToSeconds(const String& tBuf, - int hours, - int minutes, - int seconds, - bool valid); - -// convert old and new time string to nr of seconds -// return whether it should be considered a time string. -bool timeStringToSeconds(const String& tBuf, - int & time_seconds, - String& timeString); - - /********************************************************************************************\ Generate rule events based on task refresh \*********************************************************************************************/ diff --git a/src/src/ESPEasyCore/ESPEasy_loop.cpp b/src/src/ESPEasyCore/ESPEasy_loop.cpp index 8eaef522b..b21432ed3 100644 --- a/src/src/ESPEasyCore/ESPEasy_loop.cpp +++ b/src/src/ESPEasyCore/ESPEasy_loop.cpp @@ -26,7 +26,7 @@ #include "../Helpers/StringConverter.h" #include "../WebServer/ESPEasy_WebServer.h" -void updateLoopStats() { +static void updateLoopStats() { ++loopCounter; ++loopCounter_full; diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index fd90b1386..547d924d0 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -117,11 +117,13 @@ void RTOS_HandleSchedule(void *parameter) /*********************************************************************************************\ * ISR call back function for handling the watchdog. \*********************************************************************************************/ -void sw_watchdog_callback(void *arg) +/* +static void sw_watchdog_callback(void *arg) { yield(); // feed the WD ++sw_watchdog_callback_count; } +*/ /*********************************************************************************************\ * SETUP diff --git a/src/src/Helpers/Audio.cpp b/src/src/Helpers/Audio.cpp index cc8e0610b..ab1c16d5d 100644 --- a/src/src/Helpers/Audio.cpp +++ b/src/src/Helpers/Audio.cpp @@ -39,7 +39,7 @@ static bool rtttlPlaying = false; # if FEATURE_ANYRTTTL_ASYNC static String rtttlMelody; -void clear_rtttl_melody() { +static 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 diff --git a/src/src/Helpers/Audio.h b/src/src/Helpers/Audio.h index 3ede5281f..a2d12ea79 100644 --- a/src/src/Helpers/Audio.h +++ b/src/src/Helpers/Audio.h @@ -19,7 +19,6 @@ bool play_rtttl(int8_t _pin, const char *p); # if FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC void update_rtttl(); -void clear_rtttl_melody(); void set_rtttl_melody(String& melody); # endif // if FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC #endif // if FEATURE_RTTTL diff --git a/src/src/Helpers/Dallas1WireHelper.cpp b/src/src/Helpers/Dallas1WireHelper.cpp index 0a7602e3a..25bc457db 100644 --- a/src/src/Helpers/Dallas1WireHelper.cpp +++ b/src/src/Helpers/Dallas1WireHelper.cpp @@ -51,7 +51,7 @@ int32_t usec_release{}; int32_t presence_start{}; int32_t presence_end{}; -void DALLAS_IRAM_ATTR Dallas_pinModeInput(uint32_t gpio_pin_rx, uint32_t gpio_pin_tx) +static void DALLAS_IRAM_ATTR Dallas_pinModeInput(uint32_t gpio_pin_rx, uint32_t gpio_pin_tx) { if (gpio_pin_rx == gpio_pin_tx) { // let pin float, pull up will raise level @@ -67,7 +67,7 @@ void DALLAS_IRAM_ATTR Dallas_pinModeInput(uint32_t gpio_pin_rx, uint32_t gpio_pi } } -void DALLAS_IRAM_ATTR Dallas_pinWrite(uint32_t gpio_pin_rx, uint32_t gpio_pin_tx, bool pinstate) +static void DALLAS_IRAM_ATTR Dallas_pinWrite(uint32_t gpio_pin_rx, uint32_t gpio_pin_tx, bool pinstate) { DIRECT_pinWrite_ISR(gpio_pin_tx, pinstate); @@ -136,7 +136,7 @@ int Dallas_measure_rise_time(int8_t gpio_pin_rx, int8_t gpio_pin_tx) // References to 1-wire family codes: // http://owfs.sourceforge.net/simple_family.html // https://github.com/owfs/owfs-doc/wiki/1Wire-Device-List -const __FlashStringHelper* Dallas_getModel(uint8_t family, const bool hasFixedResolution) { +static const __FlashStringHelper* Dallas_getModel(uint8_t family, const bool hasFixedResolution) { switch (family) { case 0x28: return F("DS18B20"); @@ -178,7 +178,7 @@ uint64_t Dallas_addr_to_uint64(const uint8_t addr[]) { return tmpAddr_64; } -void Dallas_uint64_to_addr(uint64_t value, uint8_t addr[]) { +static void Dallas_uint64_to_addr(uint64_t value, uint8_t addr[]) { uint8_t i = 8; while (i > 0) { @@ -188,6 +188,189 @@ void Dallas_uint64_to_addr(uint64_t value, uint8_t addr[]) { } } +static bool Dallas_plugin(pluginID_t pluginID) +{ + constexpr pluginID_t PLUGIN_ID_P004_DALLAS_TEMP(4); + constexpr pluginID_t PLUGIN_ID_P080_DALLAS_IBUTTON(80); + constexpr pluginID_t PLUGIN_ID_P100_DS2423_COUNTER(100); + + return (pluginID == PLUGIN_ID_P004_DALLAS_TEMP) || + (pluginID == PLUGIN_ID_P080_DALLAS_IBUTTON) || + (pluginID == PLUGIN_ID_P100_DS2423_COUNTER); +} + +/*********************************************************************************************\ +* Dallas Read bit +\*********************************************************************************************/ +static uint8_t DALLAS_IRAM_ATTR Dallas_read_bit_ISR( + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + uint32_t& start) +{ + uint8_t r = 0; + + ISR_noInterrupts(); + + Dallas_pinLow; + start = micros(); + + // delayMicroseconds(1); + // Make sure there is at least 5 usec 'low' level. + // Minimum state by Infineon, however other brands, like Analog, + // claim lower minimum. + while (usecPassedSince_fast(start) < 4) {} + + // Set to 'input', state will be pulled high by pull-up resistor + // This will take t_RC, which is typically 2 - 3 usec. + // We have to wait upto 15 usec to see the pin be pulled up, + // otherwise it is apparently be pulled low by the sensor + Dallas_pinInput; + + if (Dallas_waitForPinHigh(gpio_pin_rx, start, 15 + usec_release)) { + // Wait for max 15 usec to have a high level + r = 1; + Dallas_pinHigh; + } + + ISR_interrupts(); + + if (r == 0) { + // Wait upto 60 usec to have a high level + // Pin should be released within 15 ... 60 usec from start of read slot. + // We must make sure we don't try to do a strong pull-up + // while the sensor is still trying to pull-down. + Dallas_waitForPinHigh(gpio_pin_rx, start, 60); + } + + // Pull high again so we can support parasite mode for 1 sensor + // Sending out a high signal level is stronger than a pull-up resistor can supply + Dallas_pinHigh; + + return r; +} + +static uint8_t Dallas_read_bit(int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + if (gpio_pin_rx == -1) { return 0; } + + if (gpio_pin_tx == -1) { return 0; } + + uint32_t start; + const uint8_t r = Dallas_read_bit_ISR(gpio_pin_rx, gpio_pin_tx, start); + + // Recovery time, make sure we at least wait for 10 usec + RC-rise time + delayMicroseconds(2 * usec_release + 10); + + while (usecPassedSince_fast(start) < 60) { + // Allow for some + // Wait for another 45 usec + // Complete read cycle: + // LOW: 6 usec + // Float: 9 msec + // Read value. Typically the sensor keeps the level low for 27 usec. + // Wait for 55 usec => complete cycle = 6 + 9 + 55 = 70 usec. + } + return r; +} + +/*********************************************************************************************\ +* Dallas Write bit +\*********************************************************************************************/ +static void DALLAS_IRAM_ATTR Dallas_write_bit_ISR(uint8_t v, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + int32_t low_time, + int32_t high_time, + uint32_t& start) +{ + ISR_noInterrupts(); + start = micros(); + Dallas_pinLow; + + while (usecPassedSince_fast(start) < low_time) { + // output remains low + } + start = micros(); + Dallas_pinHigh; + ISR_interrupts(); +} + +static void Dallas_write_bit(uint8_t v, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + if (gpio_pin_tx == -1) { return; } + + // Determine times in usec for high and low + // write 1: low 6 usec, high 64 usec (no less than 6 usec low, or else scanning will no longer work) + // write 0: low 60 usec, high 20 usec + // High time is based on the recovery time, which is detected during reset + const int32_t low_time = (v & 1) ? 7 : 60; + const int32_t high_time = (v & 1) ? (40 + usec_release) : (2 * usec_release + 20); // Recovery time + uint32_t start = 0; + + Dallas_write_bit_ISR(v, gpio_pin_rx, gpio_pin_tx, low_time, high_time, start); + + // Minimum Recovery time + delayMicroseconds(10); + + while (usecPassedSince_fast(start) < high_time) { + // output remains high + } +} + +/*********************************************************************************************\ +* Dallas Write byte +\*********************************************************************************************/ +static void Dallas_write(uint8_t ByteToWrite, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + uint8_t bitMask; + + Dallas_pinHigh; + +# ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + Dallas_write_bit((bitMask & ByteToWrite) ? 1 : 0, gpio_pin_rx, gpio_pin_tx); + } +# ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN +} + +/*********************************************************************************************\ +* Standard function to initiate addressing a sensor. +\*********************************************************************************************/ +static bool Dallas_address_ROM(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + if (!Dallas_reset(gpio_pin_rx, gpio_pin_tx)) { return false; } + Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM + + for (uint8_t i = 0; i < 8; i++) { + Dallas_write(ROM[i], gpio_pin_rx, gpio_pin_tx); + } + return true; +} + +// read power supply +# ifndef LIMIT_BUILD_SIZE + +static bool Dallas_is_parasite(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx, bool& isParasitePowered) +{ + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { + return false; + } + Dallas_write(0xB4, gpio_pin_rx, gpio_pin_tx); // read power supply + isParasitePowered = !Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); + return isParasitePowered; +} + +# endif // ifndef LIMIT_BUILD_SIZE + void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables) { if ((gpio_pin_rx == -1) || (gpio_pin_tx == -1) || @@ -344,6 +527,23 @@ void Dallas_show_sensor_stats_webform_load(const Dallas_SensorData& sensor_data) # endif // ifndef LIMIT_BUILD_SIZE +static void Dallas_plugin_set_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index) +{ + if (var_index >= 4) { + return; + } + LoadTaskSettings(TaskIndex); + const uint32_t mask = ~(0xFF << (var_index * 8)); + + for (uint8_t x = 0; x < 8; x++) { + uint32_t value = (uint32_t)ExtraTaskSettings.TaskDevicePluginConfigLong[x]; + value &= mask; + value += (static_cast(addr[x]) << (var_index * 8)); + ExtraTaskSettings.TaskDevicePluginConfigLong[x] = (int32_t)value; + } + Cache.updateExtraTaskSettingsCache(); +} + void Dallas_addr_selector_webform_save(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables) { if ((gpio_pin_rx == -1) || @@ -368,17 +568,6 @@ void Dallas_addr_selector_webform_save(taskIndex_t TaskIndex, int8_t gpio_pin_rx } } -bool Dallas_plugin(pluginID_t pluginID) -{ - constexpr pluginID_t PLUGIN_ID_P004_DALLAS_TEMP(4); - constexpr pluginID_t PLUGIN_ID_P080_DALLAS_IBUTTON(80); - constexpr pluginID_t PLUGIN_ID_P100_DS2423_COUNTER(100); - - return (pluginID == PLUGIN_ID_P004_DALLAS_TEMP) || - (pluginID == PLUGIN_ID_P080_DALLAS_IBUTTON) || - (pluginID == PLUGIN_ID_P100_DS2423_COUNTER); -} - void Dallas_plugin_get_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index) { if (var_index >= 4) { @@ -391,23 +580,6 @@ void Dallas_plugin_get_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_i } } -void Dallas_plugin_set_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index) -{ - if (var_index >= 4) { - return; - } - LoadTaskSettings(TaskIndex); - const uint32_t mask = ~(0xFF << (var_index * 8)); - - for (uint8_t x = 0; x < 8; x++) { - uint32_t value = (uint32_t)ExtraTaskSettings.TaskDevicePluginConfigLong[x]; - value &= mask; - value += (static_cast(addr[x]) << (var_index * 8)); - ExtraTaskSettings.TaskDevicePluginConfigLong[x] = (int32_t)value; - } - Cache.updateExtraTaskSettingsCache(); -} - /*********************************************************************************************\ Dallas Scan bus \*********************************************************************************************/ @@ -430,21 +602,6 @@ uint8_t Dallas_scan(uint8_t getDeviceROM, uint8_t *ROM, int8_t gpio_pin_rx, int8 return devCount; } -// read power supply -# ifndef LIMIT_BUILD_SIZE - -bool Dallas_is_parasite(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx, bool& isParasitePowered) -{ - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { - return false; - } - Dallas_write(0xB4, gpio_pin_rx, gpio_pin_tx); // read power supply - isParasitePowered = !Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); - return isParasitePowered; -} - -# endif // ifndef LIMIT_BUILD_SIZE - /* void Dallas_startConversion(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) { @@ -458,10 +615,91 @@ bool Dallas_is_parasite(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pi } */ +/*********************************************************************************************\ +* Dallas Read byte +\*********************************************************************************************/ +static uint8_t Dallas_read(int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if (Dallas_read_bit(gpio_pin_rx, gpio_pin_tx)) { + r |= bitMask; + } + } + + return r; +} + +/*********************************************************************************************\ +* Dallas Calculate CRC8 and compare it of addr[0-7] and compares it to addr[8] +\*********************************************************************************************/ +static bool Dallas_crc8(const uint8_t *addr) +{ + uint8_t crc = 0; + uint8_t len = 8; + + while (len--) + { + uint8_t inbyte = *addr++; // from 0 to 7 + + for (uint8_t i = 8; i; i--) + { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + + if (mix) { crc ^= 0x8C; } + inbyte >>= 1; + } + } + return crc == *addr; // addr 8 +} + +/*********************************************************************************************\ +* Dallas Calculate CRC16 +\*********************************************************************************************/ +# ifdef USES_P100 + +static uint16_t Dallas_crc16(const uint8_t *input, uint16_t len, uint16_t crc) +{ + static const uint8_t oddparity[16] = + { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + for (uint16_t i = 0; i < len; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) { + crc ^= 0xC001; + } + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + + return crc; +} + +# endif // ifdef USES_P100 + /*********************************************************************************************\ * Dallas Read temperature from scratchpad \*********************************************************************************************/ -Dallas_read_result Dallas_readTemp(const uint8_t ROM[8], float *value, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +enum class Dallas_read_result { + OK, + NoReply, + CRCerr, + PowerOnResetValue + +}; + +static Dallas_read_result Dallas_readTemp(const uint8_t ROM[8], float *value, int8_t gpio_pin_rx, int8_t gpio_pin_tx) { int16_t DSTemp; uint8_t ScratchPad[12]{}; @@ -673,7 +911,7 @@ bool Dallas_readCounter(const uint8_t ROM[8], float *value, int8_t gpio_pin_rx, /*********************************************************************************************\ * Dallas Check for MAX31826 fixed 12 bit resolution, see datasheet page 9 'Memory' \*********************************************************************************************/ -bool Dallas_check_hasFixedResolution(const uint8_t ROM[8], const uint8_t ScratchPad[12]) { +static bool Dallas_check_hasFixedResolution(const uint8_t ROM[8], const uint8_t ScratchPad[12]) { return (0x3B == ROM[0]) && // MAX31826: Family code 0x3B (0xFF == ScratchPad[2]) && // All 1s (0xFF == ScratchPad[3]) && @@ -1068,232 +1306,6 @@ uint8_t Dallas_search(uint8_t *newAddr, int8_t gpio_pin_rx, int8_t gpio_pin_tx) return search_result; } -/*********************************************************************************************\ -* Dallas Read byte -\*********************************************************************************************/ -uint8_t Dallas_read(int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - uint8_t bitMask; - uint8_t r = 0; - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - if (Dallas_read_bit(gpio_pin_rx, gpio_pin_tx)) { - r |= bitMask; - } - } - - return r; -} - -/*********************************************************************************************\ -* Dallas Write byte -\*********************************************************************************************/ -void Dallas_write(uint8_t ByteToWrite, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - uint8_t bitMask; - - Dallas_pinHigh; - -# ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); -# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - Dallas_write_bit((bitMask & ByteToWrite) ? 1 : 0, gpio_pin_rx, gpio_pin_tx); - } -# ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN -} - -/*********************************************************************************************\ -* Dallas Read bit -\*********************************************************************************************/ -uint8_t Dallas_read_bit(int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - if (gpio_pin_rx == -1) { return 0; } - - if (gpio_pin_tx == -1) { return 0; } - - uint32_t start; - const uint8_t r = Dallas_read_bit_ISR(gpio_pin_rx, gpio_pin_tx, start); - - // Recovery time, make sure we at least wait for 10 usec + RC-rise time - delayMicroseconds(2 * usec_release + 10); - - while (usecPassedSince_fast(start) < 60) { - // Allow for some - // Wait for another 45 usec - // Complete read cycle: - // LOW: 6 usec - // Float: 9 msec - // Read value. Typically the sensor keeps the level low for 27 usec. - // Wait for 55 usec => complete cycle = 6 + 9 + 55 = 70 usec. - } - return r; -} - -uint8_t DALLAS_IRAM_ATTR Dallas_read_bit_ISR( - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - uint32_t& start) -{ - uint8_t r = 0; - - ISR_noInterrupts(); - - Dallas_pinLow; - start = micros(); - - // delayMicroseconds(1); - // Make sure there is at least 5 usec 'low' level. - // Minimum state by Infineon, however other brands, like Analog, - // claim lower minimum. - while (usecPassedSince_fast(start) < 4) {} - - // Set to 'input', state will be pulled high by pull-up resistor - // This will take t_RC, which is typically 2 - 3 usec. - // We have to wait upto 15 usec to see the pin be pulled up, - // otherwise it is apparently be pulled low by the sensor - Dallas_pinInput; - - if (Dallas_waitForPinHigh(gpio_pin_rx, start, 15 + usec_release)) { - // Wait for max 15 usec to have a high level - r = 1; - Dallas_pinHigh; - } - - ISR_interrupts(); - - if (r == 0) { - // Wait upto 60 usec to have a high level - // Pin should be released within 15 ... 60 usec from start of read slot. - // We must make sure we don't try to do a strong pull-up - // while the sensor is still trying to pull-down. - Dallas_waitForPinHigh(gpio_pin_rx, start, 60); - } - - // Pull high again so we can support parasite mode for 1 sensor - // Sending out a high signal level is stronger than a pull-up resistor can supply - Dallas_pinHigh; - - return r; -} - -/*********************************************************************************************\ -* Dallas Write bit -\*********************************************************************************************/ -void Dallas_write_bit(uint8_t v, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - if (gpio_pin_tx == -1) { return; } - - // Determine times in usec for high and low - // write 1: low 6 usec, high 64 usec (no less than 6 usec low, or else scanning will no longer work) - // write 0: low 60 usec, high 20 usec - // High time is based on the recovery time, which is detected during reset - const int32_t low_time = (v & 1) ? 7 : 60; - const int32_t high_time = (v & 1) ? (40 + usec_release) : (2 * usec_release + 20); // Recovery time - uint32_t start = 0; - - Dallas_write_bit_ISR(v, gpio_pin_rx, gpio_pin_tx, low_time, high_time, start); - - // Minimum Recovery time - delayMicroseconds(10); - - while (usecPassedSince_fast(start) < high_time) { - // output remains high - } -} - -void DALLAS_IRAM_ATTR Dallas_write_bit_ISR(uint8_t v, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - int32_t low_time, - int32_t high_time, - uint32_t& start) -{ - ISR_noInterrupts(); - start = micros(); - Dallas_pinLow; - - while (usecPassedSince_fast(start) < low_time) { - // output remains low - } - start = micros(); - Dallas_pinHigh; - ISR_interrupts(); -} - -/*********************************************************************************************\ -* Standard function to initiate addressing a sensor. -\*********************************************************************************************/ -bool Dallas_address_ROM(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - if (!Dallas_reset(gpio_pin_rx, gpio_pin_tx)) { return false; } - Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM - - for (uint8_t i = 0; i < 8; i++) { - Dallas_write(ROM[i], gpio_pin_rx, gpio_pin_tx); - } - return true; -} - -/*********************************************************************************************\ -* Dallas Calculate CRC8 and compare it of addr[0-7] and compares it to addr[8] -\*********************************************************************************************/ -bool Dallas_crc8(const uint8_t *addr) -{ - uint8_t crc = 0; - uint8_t len = 8; - - while (len--) - { - uint8_t inbyte = *addr++; // from 0 to 7 - - for (uint8_t i = 8; i; i--) - { - uint8_t mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - - if (mix) { crc ^= 0x8C; } - inbyte >>= 1; - } - } - return crc == *addr; // addr 8 -} - -/*********************************************************************************************\ -* Dallas Calculate CRC16 -\*********************************************************************************************/ -uint16_t Dallas_crc16(const uint8_t *input, uint16_t len, uint16_t crc) -{ - static const uint8_t oddparity[16] = - { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; - - for (uint16_t i = 0; i < len; i++) { - // Even though we're just copying a byte from the input, - // we'll be doing 16-bit computation with it. - uint16_t cdata = input[i]; - cdata = (cdata ^ crc) & 0xff; - crc >>= 8; - - if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) { - crc ^= 0xC001; - } - - cdata <<= 6; - crc ^= cdata; - cdata <<= 1; - crc ^= cdata; - } - - return crc; -} - void Dallas_SensorData::clear() { addr = 0u; value = 0.0f; diff --git a/src/src/Helpers/Dallas1WireHelper.h b/src/src/Helpers/Dallas1WireHelper.h index 9c5766464..fd98cb8ae 100644 --- a/src/src/Helpers/Dallas1WireHelper.h +++ b/src/src/Helpers/Dallas1WireHelper.h @@ -50,9 +50,10 @@ struct Dallas_SensorData { bool valueRead{}; bool lastReadError{}; bool fixed_resolution{}; -#ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE bool parasitePowered{}; -#endif +# endif + }; @@ -63,7 +64,7 @@ struct Dallas_SensorData { extern unsigned char ROM_NO[8]; extern uint8_t LastDiscrepancy; extern uint8_t LastFamilyDiscrepancy; -extern bool LastDeviceFlag; +extern bool LastDeviceFlag; /*********************************************************************************************\ @@ -77,42 +78,30 @@ extern int32_t presence_end; // End presence condition (minimal 60 usec, typ: /*********************************************************************************************\ Format 1-wire address \*********************************************************************************************/ -const __FlashStringHelper* Dallas_getModel(uint8_t family, - const bool hasFixedResolution = false); +String Dallas_format_address(const uint8_t addr[], + const bool hasFixedResolution = false); -String Dallas_format_address(const uint8_t addr[], - const bool hasFixedResolution = false); +uint64_t Dallas_addr_to_uint64(const uint8_t addr[]); -uint64_t Dallas_addr_to_uint64(const uint8_t addr[]); +void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + uint8_t nrVariables = 1); -void Dallas_uint64_to_addr(uint64_t value, - uint8_t addr[]); - -void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - uint8_t nrVariables = 1); - -#ifndef LIMIT_BUILD_SIZE +# ifndef LIMIT_BUILD_SIZE void Dallas_show_sensor_stats_webform_load(const Dallas_SensorData& sensor_data); -#endif +# endif void Dallas_addr_selector_webform_save(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables = 1); -bool Dallas_plugin(pluginID_t pluginID); - // Load ROM address from tasksettings void Dallas_plugin_get_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index = 0); -void Dallas_plugin_set_addr(uint8_t addr[], - taskIndex_t TaskIndex, - uint8_t var_index = 0); - /*********************************************************************************************\ Dallas Scan bus @@ -125,13 +114,6 @@ uint8_t Dallas_scan(uint8_t getDeviceROM, int8_t gpio_pin_rx, int8_t gpio_pin_tx); -// read power supply -#ifndef LIMIT_BUILD_SIZE -bool Dallas_is_parasite(const uint8_t ROM[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - bool & isParasitePowered); -#endif /* void Dallas_startConversion(const uint8_t ROM[8], int8_t gpio_pin_rx, @@ -141,16 +123,6 @@ bool Dallas_is_parasite(const uint8_t ROM[8], /*********************************************************************************************\ * Dallas data from scratchpad \*********************************************************************************************/ -enum class Dallas_read_result { - OK, - NoReply, - CRCerr, - PowerOnResetValue -}; -Dallas_read_result Dallas_readTemp(const uint8_t ROM[8], - float *value, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); # ifdef USES_P080 bool Dallas_readiButton(const uint8_t addr[8], @@ -205,63 +177,34 @@ uint8_t Dallas_search(uint8_t *newAddr, int8_t gpio_pin_rx, int8_t gpio_pin_tx); -/*********************************************************************************************\ -* Dallas Read byte -\*********************************************************************************************/ -uint8_t Dallas_read(int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas Write byte -\*********************************************************************************************/ -void Dallas_write(uint8_t ByteToWrite, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); /*********************************************************************************************\ * Dallas Read bit * See https://github.com/espressif/arduino-esp32/issues/1335 \*********************************************************************************************/ -uint8_t Dallas_read_bit(int8_t gpio_pin_rx, +/* + uint8_t Dallas_read_bit(int8_t gpio_pin_rx, int8_t gpio_pin_tx); -uint8_t Dallas_read_bit_ISR(int8_t gpio_pin_rx, + uint8_t Dallas_read_bit_ISR(int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint32_t& start); - + */ /*********************************************************************************************\ * Dallas Write bit * See https://github.com/espressif/arduino-esp32/issues/1335 \*********************************************************************************************/ -void Dallas_write_bit(uint8_t v, +/* + void Dallas_write_bit(uint8_t v, int8_t gpio_pin_rx, int8_t gpio_pin_tx); -void Dallas_write_bit_ISR(uint8_t v, + void Dallas_write_bit_ISR(uint8_t v, int8_t gpio_pin_rx, int8_t gpio_pin_tx, int32_t low_time, int32_t high_time, uint32_t& start); - -/*********************************************************************************************\ -* Standard function to initiate addressing a sensor. -\*********************************************************************************************/ -bool Dallas_address_ROM(const uint8_t ROM[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas Calculate CRC8 and compare it of addr[0-7] and compares it to addr[8] -\*********************************************************************************************/ -bool Dallas_crc8(const uint8_t *addr); - -/*********************************************************************************************\ -* Dallas Calculate CRC16 -\*********************************************************************************************/ -uint16_t Dallas_crc16(const uint8_t *input, - uint16_t len, - uint16_t crc); - + */ #endif // if FEATURE_DALLAS_HELPER diff --git a/src/src/Helpers/ESPEasyStatistics.cpp b/src/src/Helpers/ESPEasyStatistics.cpp index 5c53f1f2b..cf48fb105 100644 --- a/src/src/Helpers/ESPEasyStatistics.cpp +++ b/src/src/Helpers/ESPEasyStatistics.cpp @@ -9,7 +9,7 @@ #include "../Helpers/_Plugin_init.h" -void stream_json_timing_stats(const TimingStats& stats, long timeSinceLastReset) { +static void stream_json_timing_stats(const TimingStats& stats, long timeSinceLastReset) { uint32_t minVal, maxVal; uint32_t count = stats.getMinMax(minVal, maxVal); float call_per_sec = static_cast(count) / static_cast(timeSinceLastReset) * 1000.0f; diff --git a/src/src/Helpers/ESPEasyStatistics.h b/src/src/Helpers/ESPEasyStatistics.h index 717ad1ed9..6d0c83317 100644 --- a/src/src/Helpers/ESPEasyStatistics.h +++ b/src/src/Helpers/ESPEasyStatistics.h @@ -9,8 +9,6 @@ #ifdef WEBSERVER_NEW_UI #include "../DataStructs/TimingStats.h" -void stream_json_timing_stats(const TimingStats& stats, long timeSinceLastReset); - void jsonStatistics(bool clearStats); #endif #endif // if FEATURE_TIMING_STATS diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 7f6f2ab1a..609548fdc 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1227,7 +1227,7 @@ bool resolveHostByName(const char *aHostname, IPAddress& aResult, uint32_t timeo uint32_t start = millis(); for (int attempt = 0; - attempt < 2 && !resolvedIP && timePassedSince(start) < timeout_ms; + attempt < 2 && !resolvedIP && timePassedSince(start) < static_cast(timeout_ms); ++attempt) { #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index 4ba33d6d1..9ce189104 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -57,22 +57,22 @@ # define KV_SETID(S) #endif // ifndef LIMIT_BUILD_SIZE -KeyValueStruct make_kv(const __FlashStringHelper *key, int val) +static KeyValueStruct make_kv(const __FlashStringHelper *key, int val) { return KeyValueStruct(key, val, KeyValueStruct::Format::Default); } -KeyValueStruct make_kv(const __FlashStringHelper *key, uint32_t val) +static KeyValueStruct make_kv(const __FlashStringHelper *key, uint32_t val) { return KeyValueStruct(key, val, KeyValueStruct::Format::Default); } -KeyValueStruct make_kv(const __FlashStringHelper *key, uint64_t val) +static KeyValueStruct make_kv(const __FlashStringHelper *key, uint64_t val) { return KeyValueStruct(key, val, KeyValueStruct::Format::Default); } -KeyValueStruct make_kv(const __FlashStringHelper *key, const float & val, +static KeyValueStruct make_kv(const __FlashStringHelper *key, const float & val, uint8_t nrDecimals) { KeyValueStruct kv(key, KeyValueStruct::Format::Default); @@ -80,22 +80,22 @@ KeyValueStruct make_kv(const __FlashStringHelper *key, const float & val, return kv; } -KeyValueStruct make_kv(const __FlashStringHelper *key, const __FlashStringHelper *val) +static KeyValueStruct make_kv(const __FlashStringHelper *key, const __FlashStringHelper *val) { return KeyValueStruct(key, val, KeyValueStruct::Format::Default); } -KeyValueStruct make_kv(const __FlashStringHelper *key, String &&val) +static KeyValueStruct make_kv(const __FlashStringHelper *key, String &&val) { return KeyValueStruct(key, val, KeyValueStruct::Format::Default); } -KeyValueStruct make_kv(const __FlashStringHelper *key, String &&val, KeyValueStruct::Format format) +static KeyValueStruct make_kv(const __FlashStringHelper *key, String &&val, KeyValueStruct::Format format) { return KeyValueStruct(key, val, format); } -KeyValueStruct make_kv(const __FlashStringHelper *key, const IPAddress& val, bool includeZone = false) +static KeyValueStruct make_kv(const __FlashStringHelper *key, const IPAddress& val, bool includeZone = false) { ValueStruct v; v.setIPAddress(val, includeZone); diff --git a/src/src/Helpers/SystemVariables.cpp b/src/src/Helpers/SystemVariables.cpp index c3ee2c91e..7a325bc3e 100644 --- a/src/src/Helpers/SystemVariables.cpp +++ b/src/src/Helpers/SystemVariables.cpp @@ -44,7 +44,7 @@ #if defined(ESP8266) # include -int WIFI_CONNECT_STATUS() { +static int WIFI_CONNECT_STATUS() { if (WiFi.isConnected()) { return 7; } @@ -53,7 +53,7 @@ int WIFI_CONNECT_STATUS() { #endif // if defined(ESP8266) #if defined(ESP32) # include -int WIFI_CONNECT_STATUS() { +static int WIFI_CONNECT_STATUS() { if (WiFi.STA.connected()) { if (WiFi.STA.hasIP()) { return 7; @@ -65,7 +65,7 @@ int WIFI_CONNECT_STATUS() { #endif // if defined(ESP32) -String getReplacementString(const String& format, const String& s) { +static String getReplacementString(const String& format, const String& s) { int startpos = s.indexOf(format); int endpos = s.indexOf('%', startpos + 1); if (endpos == -1) { @@ -87,19 +87,19 @@ String getReplacementString(const String& format, const String& s) { return R; } -void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { +static void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { const String R(getReplacementString(format, s)); repl(R, node_time.getSunriseTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); } -void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { +static void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { const String R(getReplacementString(format, s)); repl(R, node_time.getSunsetTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); } -String timeReplacement_leadZero(int value) +static String timeReplacement_leadZero(int value) { char valueString[5] = { 0 }; @@ -108,7 +108,7 @@ String timeReplacement_leadZero(int value) } // FIXME TD-er: Try to match these with StringProvider::getValue -LabelType::Enum SystemVariables2LabelType(SystemVariables::Enum enumval) { +static LabelType::Enum SystemVariables2LabelType(SystemVariables::Enum enumval) { LabelType::Enum label = LabelType::MAX_LABEL; switch (enumval) @@ -327,7 +327,7 @@ String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { while (__pos__ != -1) { (S((T_str), s, useURLencode)); __pos__ = s.indexOf(T_str, __pos__ + 1);} // Parse %vN% to replace ESPEasy variables -bool parse_pct_v_num_pct(String& s, boolean useURLencode, int start_pos) +static bool parse_pct_v_num_pct(String& s, boolean useURLencode, int start_pos) { const String key_prefix = F("%v"); int v_index = s.indexOf(key_prefix, start_pos); diff --git a/src/src/Helpers/_CPlugin_DomoticzHelper.cpp b/src/src/Helpers/_CPlugin_DomoticzHelper.cpp index 7551cf902..6359c128b 100644 --- a/src/src/Helpers/_CPlugin_DomoticzHelper.cpp +++ b/src/src/Helpers/_CPlugin_DomoticzHelper.cpp @@ -27,7 +27,7 @@ // 1=Comfortable // 2=Dry // 3=Wet -int humStatDomoticz(struct EventStruct *event, uint8_t rel_index) { +static int humStatDomoticz(struct EventStruct *event, uint8_t rel_index) { userVarIndex_t userVarIndex = event->BaseVarIndex + rel_index; if (validTaskVarIndex(rel_index) && validUserVarIndex(userVarIndex)) { diff --git a/src/src/Helpers/_CPlugin_DomoticzHelper.h b/src/src/Helpers/_CPlugin_DomoticzHelper.h index 139be6d34..5065df21c 100644 --- a/src/src/Helpers/_CPlugin_DomoticzHelper.h +++ b/src/src/Helpers/_CPlugin_DomoticzHelper.h @@ -7,14 +7,6 @@ # include "../Helpers/_CPlugin_Helper.h" -// HUM_STAT can be one of: - -// 0=Normal -// 1=Comfortable -// 2=Dry -// 3=Wet -int humStatDomoticz(struct EventStruct *event, - uint8_t rel_index); int mapRSSItoDomoticz(); diff --git a/src/src/Helpers/_CPlugin_Helper.cpp b/src/src/Helpers/_CPlugin_Helper.cpp index 7ddb13878..ee96fb36d 100644 --- a/src/src/Helpers/_CPlugin_Helper.cpp +++ b/src/src/Helpers/_CPlugin_Helper.cpp @@ -84,7 +84,7 @@ bool safeReadStringUntil(Stream & input, #ifndef BUILD_NO_DEBUG -void log_connecting_to(const __FlashStringHelper *prefix, cpluginID_t cpluginID, ControllerSettingsStruct& ControllerSettings) { +static void log_connecting_to(const __FlashStringHelper *prefix, cpluginID_t cpluginID, ControllerSettingsStruct& ControllerSettings) { if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { addLogMove(LOG_LEVEL_DEBUG, strformat(F("%s%s connecting to %s"), diff --git a/src/src/Helpers/_CPlugin_Helper.h b/src/src/Helpers/_CPlugin_Helper.h index 07ab6c270..2682d03ac 100644 --- a/src/src/Helpers/_CPlugin_Helper.h +++ b/src/src/Helpers/_CPlugin_Helper.h @@ -32,10 +32,6 @@ bool safeReadStringUntil(Stream & input, unsigned int timeout = 1000); -#ifndef BUILD_NO_DEBUG -void log_connecting_to(const __FlashStringHelper * prefix, cpluginID_t cpluginID, ControllerSettingsStruct& ControllerSettings); -#endif // ifndef BUILD_NO_DEBUG - bool count_connection_results(bool success, const __FlashStringHelper * prefix, cpluginID_t cpluginID, uint64_t statisticsTimerStart); #if FEATURE_HTTP_CLIENT diff --git a/src/src/Helpers/_CPlugin_Helper_webform.cpp b/src/src/Helpers/_CPlugin_Helper_webform.cpp index 8e103aeda..3bf3c90c3 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.cpp +++ b/src/src/Helpers/_CPlugin_Helper_webform.cpp @@ -120,7 +120,7 @@ String getControllerParameterInternalName(protocolIndex_t ProtocolIndex, Control return getControllerParameterName(ProtocolIndex, parameterIdx, displayName, isAlternative); } -String getControllerParameterDisplayName(protocolIndex_t ProtocolIndex, ControllerSettingsStruct::VarType parameterIdx, bool& isAlternative) { +static String getControllerParameterDisplayName(protocolIndex_t ProtocolIndex, ControllerSettingsStruct::VarType parameterIdx, bool& isAlternative) { bool displayName = true; return getControllerParameterName(ProtocolIndex, parameterIdx, displayName, isAlternative); @@ -142,7 +142,7 @@ void addControllerEnabledForm(controllerIndex_t controllerindex) { } #if FEATURE_MQTT_TLS || FEATURE_HTTP_TLS -void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description, TLS_types tls_type) { +static void addCertificateFileNote(const ControllerSettingsStruct& ControllerSettings, const String& description, TLS_types tls_type) { const String certFile = ControllerSettings.getCertificateFilename(tls_type); if (!certFile.isEmpty()) diff --git a/src/src/Helpers/_CPlugin_Helper_webform.h b/src/src/Helpers/_CPlugin_Helper_webform.h index 76bb5398a..834e532d3 100644 --- a/src/src/Helpers/_CPlugin_Helper_webform.h +++ b/src/src/Helpers/_CPlugin_Helper_webform.h @@ -17,8 +17,6 @@ String getControllerParameterName(protocolIndex_t ProtocolInde String getControllerParameterInternalName(protocolIndex_t ProtocolIndex, ControllerSettingsStruct::VarType parameterIdx); -String getControllerParameterDisplayName(protocolIndex_t ProtocolIndex, ControllerSettingsStruct::VarType parameterIdx, bool& isAlternative); - void addControllerEnabledForm(controllerIndex_t controllerindex); void addControllerParameterForm(const ControllerSettingsStruct& ControllerSettings, controllerIndex_t controllerindex, ControllerSettingsStruct::VarType varType); diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp index 3ac7b7e7b..aba200829 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp @@ -60,7 +60,7 @@ void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, int pconfigI sensorTypeHelper_webformLoad(event, pconfigIndex, optionCount, optionValues); } -void sensorTypeHelper_Selector(const String& id, int optionCount, const uint8_t options[], Sensor_VType choice) { +static void sensorTypeHelper_Selector(const String& id, int optionCount, const uint8_t options[], Sensor_VType choice) { addSelector_Head(id); for (uint8_t x = 0; x < optionCount; x++) @@ -85,7 +85,7 @@ const char value_type_categories[] PROGMEM = "Other|" // 1031 ; -String toValueTypeCategory(const uint32_t valueType) { +static String toValueTypeCategory(const uint32_t valueType) { char tmp[12]{}; String result; diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.h b/src/src/Helpers/_Plugin_SensorTypeHelper.h index 927495379..b163eb875 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.h +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.h @@ -9,7 +9,6 @@ void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, int pconfi void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, int pconfigIndex); -void sensorTypeHelper_Selector(const String& id, int optionCount, const uint8_t options[], Sensor_VType choice); #if FEATURE_CUSTOM_TASKVAR_VTYPE void sensorTypeCategoriesHelper_Selector(const String& id, int optionCount, From 5961277bf8037f8fe3fd40a160358ee831be1d06 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 17 Oct 2026 18:03:37 +0200 Subject: [PATCH 02/12] [Build] Reduce build size by static only functions in .cpp files --- platformio_core_defs.ini | 1 + src/src/Commands/Tasks.cpp | 24 +- src/src/Commands/Tasks.h | 7 - src/src/ESPEasyCore/ESPEasy_setup.cpp | 39 +- src/src/Helpers/ESPEasy_CrashHandler.cpp | 180 +++ src/src/Helpers/ESPEasy_CrashHandler.h | 13 + src/src/Helpers/ESPEasy_Storage.cpp | 282 ++-- src/src/Helpers/ESPEasy_Storage.h | 49 - src/src/Helpers/ESPEasy_TouchHandler.cpp | 4 +- src/src/Helpers/ESPEasy_TouchHandler.h | 5 - .../ESPEasy_key_value_store_import_export.cpp | 2 +- src/src/Helpers/ESPEasy_math.cpp | 4 +- src/src/Helpers/ESPEasy_time_calc.cpp | 15 +- src/src/Helpers/ESPEasy_time_calc.h | 7 - src/src/Helpers/FS_Helper.h | 2 - src/src/Helpers/HTTPResponseParser.cpp | 367 ++--- src/src/Helpers/HTTPResponseParser.h | 6 - src/src/Helpers/Hardware.cpp | 8 +- src/src/Helpers/Hardware.h | 9 - src/src/Helpers/Hardware_I2C.cpp | 113 +- src/src/Helpers/Hardware_I2C.h | 10 +- src/src/Helpers/Hardware_PWM.cpp | 2 +- .../Helpers/Hardware_temperature_sensor.cpp | 4 +- src/src/Helpers/JSON_helper.cpp | 148 +- src/src/Helpers/Log_Helper.cpp | 2 +- src/src/Helpers/Networking.cpp | 115 +- src/src/Helpers/Networking.h | 40 +- src/src/Helpers/PeriodicalActions.cpp | 4 +- src/src/Helpers/PeriodicalActions.h | 1 - src/src/Helpers/RulesHelper.cpp | 6 +- src/src/Helpers/Rules_calculate.cpp | 472 +++--- src/src/Helpers/Rules_calculate.h | 116 +- src/src/Helpers/StringConverter.cpp | 1294 +++++++++-------- src/src/Helpers/StringConverter.h | 432 +++--- src/src/Helpers/StringConverter_Numerical.cpp | 29 +- src/src/Helpers/StringConverter_Numerical.h | 4 - src/src/NotifierStructs/N001_data_struct.cpp | 211 +-- src/src/NotifierStructs/N001_data_struct.h | 11 - src/src/WebServer/AdvancedConfigPage.cpp | 212 +-- src/src/WebServer/AdvancedConfigPage.h | 15 - src/src/WebServer/Chart_JS.cpp | 102 +- src/src/WebServer/Chart_JS.h | 8 - src/src/WebServer/ControllerPage.cpp | 231 +-- src/src/WebServer/ControllerPage.h | 26 - 44 files changed, 2287 insertions(+), 2345 deletions(-) create mode 100644 src/src/Helpers/ESPEasy_CrashHandler.cpp create mode 100644 src/src/Helpers/ESPEasy_CrashHandler.h diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index f888fceef..23db5e0c3 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -231,6 +231,7 @@ build_flags = -DESP32_STAGE -include "esp32x_fixes.h" -DUPDATE_NOCRYPT -Os + -finline-functions-called-once ; -Wnull-dereference -DNO_GLOBAL_ESP_NOW -DNO_GLOBAL_EEPROM diff --git a/src/src/Commands/Tasks.cpp b/src/src/Commands/Tasks.cpp index c5c67e735..5e711acbc 100644 --- a/src/src/Commands/Tasks.cpp +++ b/src/src/Commands/Tasks.cpp @@ -262,22 +262,11 @@ const __FlashStringHelper * Command_Task_ValueSet(struct EventStruct *event, con } #if FEATURE_STRING_VARIABLES -const __FlashStringHelper * Command_Task_ValueSetDerived(struct EventStruct *event, const char *Line) -{ - return taskValueSetString(event, Line, F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), F(TASK_VALUE_UOM_PREFIX_TEMPLATE), F(TASK_VALUE_VTYPE_PREFIX_TEMPLATE)); -} - -const __FlashStringHelper * Command_Task_ValueSetPresentation(struct EventStruct *event, const char *Line) -{ - return taskValueSetString(event, Line, F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE)); -} - static const __FlashStringHelper * taskValueSetString(struct EventStruct *event, const char *Line, const __FlashStringHelper * storageTemplate, const __FlashStringHelper * uomTemplate, const __FlashStringHelper * vTypeTemplate) { String taskName; - taskIndex_t tmpTaskIndex = INVALID_TASK_INDEX; if ((event->Par1 <= 0 || event->Par1 >= INVALID_TASK_INDEX) && GetArgv(Line, taskName, 2)) { - tmpTaskIndex = findTaskIndexByName(taskName, true); + const taskIndex_t tmpTaskIndex = findTaskIndexByName(taskName, true); if (tmpTaskIndex != INVALID_TASK_INDEX) { event->Par1 = tmpTaskIndex + 1; } @@ -364,6 +353,17 @@ static const __FlashStringHelper * taskValueSetString(struct EventStruct *event, } return return_command_failed_flashstr(); // taskValueSet(event, Line, taskIndex, success); } + +const __FlashStringHelper * Command_Task_ValueSetDerived(struct EventStruct *event, const char *Line) +{ + return taskValueSetString(event, Line, F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), F(TASK_VALUE_UOM_PREFIX_TEMPLATE), F(TASK_VALUE_VTYPE_PREFIX_TEMPLATE)); +} + +const __FlashStringHelper * Command_Task_ValueSetPresentation(struct EventStruct *event, const char *Line) +{ + return taskValueSetString(event, Line, F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE), nullptr, nullptr); +} + #endif const __FlashStringHelper * Command_Task_ValueToggle(struct EventStruct *event, const char *Line) diff --git a/src/src/Commands/Tasks.h b/src/src/Commands/Tasks.h index 11f35aefc..9cc95960e 100644 --- a/src/src/Commands/Tasks.h +++ b/src/src/Commands/Tasks.h @@ -23,13 +23,6 @@ const __FlashStringHelper* Command_Task_ValueSetDerived(struct EventStruct *even const char *Line); const __FlashStringHelper* Command_Task_ValueSetPresentation(struct EventStruct *event, const char *Line); -/* -const __FlashStringHelper* taskValueSetString(struct EventStruct *event, - const char *Line, - const __FlashStringHelper *storageTemplate, - const __FlashStringHelper *uomTemplate = nullptr, - const __FlashStringHelper *vTypeTemplate = nullptr); -*/ #endif // if FEATURE_STRING_VARIABLES const __FlashStringHelper* Command_Task_ValueToggle(struct EventStruct *event, const char *Line); diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index 547d924d0..14461fd2f 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -26,6 +26,7 @@ #include "../Helpers/_Plugin_init.h" #include "../Helpers/DeepSleep.h" #include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/ESPEasy_CrashHandler.h" #include "../Helpers/ESPEasy_FactoryDefault.h" #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/ESPEasy_checks.h" @@ -407,44 +408,8 @@ void ESPEasy_setup() Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. - if ((RTC.bootFailedCount > 10) && (RTC.bootCounter > 10)) { - uint8_t toDisable = RTC.bootFailedCount - 10; - toDisable = disablePlugin(toDisable); + disableFailingComponent(); - if (toDisable != 0) { - toDisable = disableController(toDisable); - } - #if FEATURE_NOTIFIER - - if (toDisable != 0) { - toDisable = disableNotification(toDisable); - } - #endif // if FEATURE_NOTIFIER - if (toDisable != 0) { - toDisable = disableNetwork(toDisable); - } - - if (toDisable != 0) { - toDisable = disableRules(toDisable); - } - - if (toDisable != 0) { - toDisable = disableAllPlugins(toDisable); - } - - if (toDisable != 0) { - toDisable = disableAllControllers(toDisable); - } -#if FEATURE_NOTIFIER - - if (toDisable != 0) { - toDisable = disableAllNotifications(toDisable); - } -#endif // if FEATURE_NOTIFIER - if (toDisable != 0) { - toDisable = disableAllNetworkss(toDisable); - } - } #if FEATURE_ETHERNET // This ensures, that changing WIFI OR ETHERNET MODE happens properly only after reboot. Changing without reboot would not be a good idea. diff --git a/src/src/Helpers/ESPEasy_CrashHandler.cpp b/src/src/Helpers/ESPEasy_CrashHandler.cpp new file mode 100644 index 000000000..bb6aa53c3 --- /dev/null +++ b/src/src/Helpers/ESPEasy_CrashHandler.cpp @@ -0,0 +1,180 @@ +#include "../Helpers/ESPEasy_CrashHandler.h" + +#include "../Globals/Settings.h" +#include "../Globals/RTC.h" + +#define CRASHHANDLER_MIN_BOOTLOOP_COUNT 10 + +/********************************************************************************************\ + Disable Plugin, based on bootFailedCount + \*********************************************************************************************/ +static uint8_t disablePlugin(uint8_t bootFailedCount) { + for (taskIndex_t i = 0; i < TASKS_MAX && bootFailedCount > 0; ++i) { + if (Settings.TaskDeviceEnabled[i]) { + --bootFailedCount; + + if (bootFailedCount == 0) { + // Disable temporarily as unit crashed + // FIXME TD-er: Should this be stored? + Settings.TaskDeviceEnabled[i] = false; + } + } + } + return bootFailedCount; +} + +static uint8_t disableAllPlugins(uint8_t bootFailedCount) { + if (bootFailedCount > 0) { + --bootFailedCount; + + for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { + // Disable temporarily as unit crashed + // FIXME TD-er: Should this be stored? + Settings.TaskDeviceEnabled[i] = false; + } + } + return bootFailedCount; +} + +/********************************************************************************************\ + Disable Controller, based on bootFailedCount + \*********************************************************************************************/ +static uint8_t disableController(uint8_t bootFailedCount) { + for (controllerIndex_t i = 0; i < CONTROLLER_MAX && bootFailedCount > 0; ++i) { + if (Settings.ControllerEnabled[i]) { + --bootFailedCount; + + if (bootFailedCount == 0) { + Settings.ControllerEnabled[i] = false; + } + } + } + return bootFailedCount; +} + +static uint8_t disableAllControllers(uint8_t bootFailedCount) { + if (bootFailedCount > 0) { + --bootFailedCount; + + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + Settings.ControllerEnabled[i] = false; + } + } + return bootFailedCount; +} + +/********************************************************************************************\ + Disable Notification, based on bootFailedCount + \*********************************************************************************************/ +#if FEATURE_NOTIFIER + +static uint8_t disableNotification(uint8_t bootFailedCount) { + for (uint8_t i = 0; i < NOTIFICATION_MAX && bootFailedCount > 0; ++i) { + if (Settings.NotificationEnabled[i]) { + --bootFailedCount; + + if (bootFailedCount == 0) { + Settings.NotificationEnabled[i] = false; + } + } + } + return bootFailedCount; +} + +static uint8_t disableAllNotifications(uint8_t bootFailedCount) { + if (bootFailedCount > 0) { + --bootFailedCount; + + for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { + Settings.NotificationEnabled[i] = false; + } + } + return bootFailedCount; +} + +#endif // if FEATURE_NOTIFIER + +/********************************************************************************************\ + Disable Network Interfaces, based on bootFailedCount + \*********************************************************************************************/ +static uint8_t disableNetwork(uint8_t bootFailedCount) +{ + for (ESPEasy::net::networkIndex_t i = 0; i < NETWORK_MAX && bootFailedCount > 0; ++i) { + if (Settings.getNetworkEnabled(i)) { + --bootFailedCount; + + if (bootFailedCount == 0) { + Settings.setNetworkEnabled(i, false); + } + } + } + return bootFailedCount; +} + +static uint8_t disableAllNetworks(uint8_t bootFailedCount) +{ + if (bootFailedCount > 0) { + --bootFailedCount; + + for (ESPEasy::net::networkIndex_t i = 0; i < NETWORK_MAX; ++i) { + Settings.setNetworkEnabled(i, false); + } + } + return bootFailedCount; +} + +/********************************************************************************************\ + Disable Rules, based on bootFailedCount + \*********************************************************************************************/ +static uint8_t disableRules(uint8_t bootFailedCount) { + if (bootFailedCount > 0) { + --bootFailedCount; + Settings.UseRules = false; + } + return bootFailedCount; +} + +void disableFailingComponent() +{ + if ((RTC.bootFailedCount > CRASHHANDLER_MIN_BOOTLOOP_COUNT) && (RTC.bootCounter > CRASHHANDLER_MIN_BOOTLOOP_COUNT)) { + uint8_t toDisable = RTC.bootFailedCount - CRASHHANDLER_MIN_BOOTLOOP_COUNT; + toDisable = disablePlugin(toDisable); + + if (toDisable != 0) { + toDisable = disableController(toDisable); + } + #if FEATURE_NOTIFIER + + if (toDisable != 0) { + toDisable = disableNotification(toDisable); + } + #endif // if FEATURE_NOTIFIER + + if (toDisable != 0) { + toDisable = disableNetwork(toDisable); + } + + if (toDisable != 0) { + toDisable = disableRules(toDisable); + } + + if (toDisable != 0) { + toDisable = disableAllPlugins(toDisable); + } + + if (toDisable != 0) { + toDisable = disableAllControllers(toDisable); + } +#if FEATURE_NOTIFIER + + if (toDisable != 0) { + toDisable = disableAllNotifications(toDisable); + } +#endif // if FEATURE_NOTIFIER + + if (toDisable != 0) { + toDisable = disableAllNetworks(toDisable); + } + } + +} diff --git a/src/src/Helpers/ESPEasy_CrashHandler.h b/src/src/Helpers/ESPEasy_CrashHandler.h new file mode 100644 index 000000000..e46c748d0 --- /dev/null +++ b/src/src/Helpers/ESPEasy_CrashHandler.h @@ -0,0 +1,13 @@ +#pragma once + +/********************************************************************************************\ + ESPEasy will start disabling components when a bootloop is detected. + These will only be disabled in memory, the disabled state will not be saved. + So whenever an ESPEasy node suddenly doesn't have some task or controller disabled, + the disabled component is likely causing the node to crash. + This can be some programming error, a configuration error + or maybe a bad device returning invalid data. + \*********************************************************************************************/ + + +void disableFailingComponent(); \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index edc455120..101efd182 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -80,7 +80,7 @@ String patch_fname(const String& fname) { file system error handling Look here for error # reference: https://github.com/pellepl/spiffs/blob/master/src/spiffs.h \*********************************************************************************************/ -String FileError(int line, const char *fname) +static String FileError(int line, const char *fname) { String log = strformat(F("FS : Error while reading/writing %s in %d"), fname, line); @@ -88,6 +88,10 @@ String FileError(int line, const char *fname) return log; } +// Macro used to make file system operations a bit more readable. +#define SPIFFS_CHECK(result, fname) if (!(result)) { return(FileError(__LINE__, fname)); } + + /********************************************************************************************\ Keep track of number of flash writes. \*********************************************************************************************/ @@ -100,7 +104,7 @@ void flashCount() saveToRTC(); } -String flashGuard() +static String flashGuard() { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("flashGuard")); @@ -121,11 +125,7 @@ String flashGuard() if (flashErr.length()) return flashErr; } -String appendLineToFile(const String& fname, const String& line) { - return appendToFile(fname, reinterpret_cast(line.c_str()), line.length()); -} - -String appendToFile(const String& fname, const uint8_t *data, unsigned int size) { +static String appendToFile(const String& fname, const uint8_t *data, unsigned int size) { fs::File f = tryOpenFile(fname, "a+"); SPIFFS_CHECK(f, fname.c_str()); @@ -134,6 +134,10 @@ String appendToFile(const String& fname, const uint8_t *data, unsigned int size) return EMPTY_STRING; } +String appendLineToFile(const String& fname, const String& line) { + return appendToFile(fname, reinterpret_cast(line.c_str()), line.length()); +} + bool fileExists(const __FlashStringHelper *fname) { return fileExists(String(fname)); @@ -234,13 +238,14 @@ fs::File tryOpenFile(const String& fname, const String& mode, FileDestination_e return f; } -bool fileMatchesTaskSettingsType(const String& fname) { +static bool fileMatchesTaskSettingsType(const String& fname) { const String config_dat_file = patch_fname(getFileName(FileType::CONFIG_DAT)); return config_dat_file.equalsIgnoreCase(patch_fname(fname)); } -bool tryRenameFile(const String& fname_old, const String& fname_new, FileDestination_e destination) { +#if FEATURE_DOWNLOAD +static bool tryRenameFile(const String& fname_old, const String& fname_new, FileDestination_e destination) { clearFileCaches(); if (fileExists(fname_old) && !fileExists(fname_new)) { @@ -264,6 +269,7 @@ bool tryRenameFile(const String& fname_old, const String& fname_new, FileDestina } return false; } +#endif bool tryDeleteFile(const String& fname, FileDestination_e destination) { if (fname.length() > 0) @@ -321,7 +327,7 @@ bool tryDeleteFile(const String& fname, FileDestination_e destination) { /********************************************************************************************\ Fix stuff to clear out differences between releases \*********************************************************************************************/ -bool BuildFixes() +static bool BuildFixes() { if (Settings.Build == get_build_nr()) { // Not changed @@ -664,7 +670,7 @@ uint32_t getWiFi_CalibrationVersion() return 0; } -bool setWiFi_CalibrationVersion() { +static bool setWiFi_CalibrationVersion() { // Store used IDF version in NVS const uint32_t idfVersion = ESP_IDF_VERSION; ESPEasy_NVS_Helper preferences; @@ -675,7 +681,7 @@ bool setWiFi_CalibrationVersion() { return false; } -String formatIdfVersion(uint32_t idfVer) { +static String formatIdfVersion(uint32_t idfVer) { return strformat( F("%u.%u.%u"), (idfVer >> 16) & 0xFF, @@ -761,7 +767,7 @@ int getPartionCount(uint8_t pType, uint8_t pSubType) { #endif // ifdef ESP32 #ifdef ESP8266 -bool clearPartition(ESP8266_partition_type ptype) { +static bool clearPartition(ESP8266_partition_type ptype) { uint32_t address; int32_t size; int32_t sector = getPartitionInfo(ESP8266_partition_type::rf_cal, address, size); @@ -871,63 +877,7 @@ String SaveSettings(bool forFactoryReset) return err; } -String SaveSecuritySettings(bool forFactoryReset) { - String err; - - SecuritySettings.validate(); - memcpy(SecuritySettings.ProgmemMd5, CRCValues.runTimeMD5, 16); - - if (forFactoryReset) { - SecuritySettings.forceSave(); - } - - if (SecuritySettings.updateChecksum()) { - // Settings have changed, save to file. - err = SaveToFile(SettingsType::getSettingsFileName(SettingsType::Enum::SecuritySettings_Type).c_str(), - 0, - reinterpret_cast(&SecuritySettings), - sizeof(SecuritySettings)); - - // Security settings are saved, may be update of WiFi settings or hostname. - if (!forFactoryReset) { - if (SecuritySettings.hasWiFiCredentials() && (active_network_medium == ESPEasy::net::NetworkMedium_t::WIFI)) { - ESPEasy::net::wifi::WiFi_AP_Candidates.force_reload(); // Force reload of the credentials and found APs from the last scan - if (!ESPEasy::net::NetworkConnected()) { -// WiFiEventData.wifiConnectAttemptNeeded = true; - ESPEasy::net::wifi::initWiFi();// resetWiFi(); - String dummy; - ESPEasy::net::NWPluginCall( - NWPlugin::Function::NWPLUGIN_CREDENTIALS_CHANGED, 0, dummy); - } - } - } - } -#ifndef BUILD_NO_DEBUG - else { - addLog(LOG_LEVEL_INFO, F("Skip saving SecuritySettings, not changed")); - } -#endif // ifndef BUILD_NO_DEBUG - - // FIXME TD-er: How to check if these have changed? - if (forFactoryReset) { - ExtendedControllerCredentials.clear(); -#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE - SecuritySettings_deviceSpecific.clear(); -#endif - } - - ExtendedControllerCredentials.save(); -#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE - SecuritySettings_deviceSpecific.save(); -#endif - - if (!forFactoryReset) { - afterloadSettings(); - } - return err; -} - -void afterloadSettings() { +static void afterloadSettings() { ExtraTaskSettings.clear(); // make sure these will not contain old settings. if ((Settings.Version != VERSION) || (Settings.PID != ESP_PROJECT_PID)) { @@ -997,6 +947,64 @@ void afterloadSettings() { ESPEasy::net::CheckRunningServices(true); // To update changes in hostname. } +String SaveSecuritySettings(bool forFactoryReset) { + String err; + + SecuritySettings.validate(); + memcpy(SecuritySettings.ProgmemMd5, CRCValues.runTimeMD5, 16); + + if (forFactoryReset) { + SecuritySettings.forceSave(); + } + + if (SecuritySettings.updateChecksum()) { + // Settings have changed, save to file. + err = SaveToFile(SettingsType::getSettingsFileName(SettingsType::Enum::SecuritySettings_Type).c_str(), + 0, + reinterpret_cast(&SecuritySettings), + sizeof(SecuritySettings)); + + // Security settings are saved, may be update of WiFi settings or hostname. + if (!forFactoryReset) { + if (SecuritySettings.hasWiFiCredentials() && (active_network_medium == ESPEasy::net::NetworkMedium_t::WIFI)) { + ESPEasy::net::wifi::WiFi_AP_Candidates.force_reload(); // Force reload of the credentials and found APs from the last scan + if (!ESPEasy::net::NetworkConnected()) { +// WiFiEventData.wifiConnectAttemptNeeded = true; + ESPEasy::net::wifi::initWiFi();// resetWiFi(); + String dummy; + ESPEasy::net::NWPluginCall( + NWPlugin::Function::NWPLUGIN_CREDENTIALS_CHANGED, 0, dummy); + } + } + } + } +#ifndef BUILD_NO_DEBUG + else { + addLog(LOG_LEVEL_INFO, F("Skip saving SecuritySettings, not changed")); + } +#endif // ifndef BUILD_NO_DEBUG + + // FIXME TD-er: How to check if these have changed? + if (forFactoryReset) { + ExtendedControllerCredentials.clear(); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + SecuritySettings_deviceSpecific.clear(); +#endif + } + + ExtendedControllerCredentials.save(); +#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + SecuritySettings_deviceSpecific.save(); +#endif + + if (!forFactoryReset) { + afterloadSettings(); + } + return err; +} + + + /********************************************************************************************\ Load settings from file system \*********************************************************************************************/ @@ -1073,134 +1081,6 @@ String LoadSettings() return err; } -/********************************************************************************************\ - Disable Plugin, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disablePlugin(uint8_t bootFailedCount) { - for (taskIndex_t i = 0; i < TASKS_MAX && bootFailedCount > 0; ++i) { - if (Settings.TaskDeviceEnabled[i]) { - --bootFailedCount; - - if (bootFailedCount == 0) { - // Disable temporarily as unit crashed - // FIXME TD-er: Should this be stored? - Settings.TaskDeviceEnabled[i] = false; - } - } - } - return bootFailedCount; -} - -uint8_t disableAllPlugins(uint8_t bootFailedCount) { - if (bootFailedCount > 0) { - --bootFailedCount; - - for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { - // Disable temporarily as unit crashed - // FIXME TD-er: Should this be stored? - Settings.TaskDeviceEnabled[i] = false; - } - } - return bootFailedCount; -} - -/********************************************************************************************\ - Disable Controller, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disableController(uint8_t bootFailedCount) { - for (controllerIndex_t i = 0; i < CONTROLLER_MAX && bootFailedCount > 0; ++i) { - if (Settings.ControllerEnabled[i]) { - --bootFailedCount; - - if (bootFailedCount == 0) { - Settings.ControllerEnabled[i] = false; - } - } - } - return bootFailedCount; -} - -uint8_t disableAllControllers(uint8_t bootFailedCount) { - if (bootFailedCount > 0) { - --bootFailedCount; - - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - Settings.ControllerEnabled[i] = false; - } - } - return bootFailedCount; -} - -/********************************************************************************************\ - Disable Notification, based on bootFailedCount - \*********************************************************************************************/ -#if FEATURE_NOTIFIER -uint8_t disableNotification(uint8_t bootFailedCount) { - for (uint8_t i = 0; i < NOTIFICATION_MAX && bootFailedCount > 0; ++i) { - if (Settings.NotificationEnabled[i]) { - --bootFailedCount; - - if (bootFailedCount == 0) { - Settings.NotificationEnabled[i] = false; - } - } - } - return bootFailedCount; -} - -uint8_t disableAllNotifications(uint8_t bootFailedCount) { - if (bootFailedCount > 0) { - --bootFailedCount; - - for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { - Settings.NotificationEnabled[i] = false; - } - } - return bootFailedCount; -} - -#endif // if FEATURE_NOTIFIER - -/********************************************************************************************\ - Disable Network Interfaces, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disableNetwork(uint8_t bootFailedCount) -{ - for (ESPEasy::net::networkIndex_t i = 0; i < NETWORK_MAX && bootFailedCount > 0; ++i) { - if (Settings.getNetworkEnabled(i)) { - --bootFailedCount; - - if (bootFailedCount == 0) { - Settings.setNetworkEnabled(i, false); - } - } - } - return bootFailedCount; -} - -uint8_t disableAllNetworkss(uint8_t bootFailedCount) -{ - if (bootFailedCount > 0) { - --bootFailedCount; - - for (ESPEasy::net::networkIndex_t i = 0; i < NETWORK_MAX; ++i) { - Settings.setNetworkEnabled(i, false); - } - } - return bootFailedCount; -} - - -/********************************************************************************************\ - Disable Rules, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disableRules(uint8_t bootFailedCount) { - if (bootFailedCount > 0) { - --bootFailedCount; - Settings.UseRules = false; - } - return bootFailedCount; -} bool getAndLogSettingsParameters(bool read, SettingsType::Enum settingsType, int index, int& offset, int& max_size) { #ifndef BUILD_NO_DEBUG @@ -2716,14 +2596,14 @@ String downloadFileType(const String& url, const String& user, const String& pas return error; } - if (fileExists(filename) && !tryRenameFile(filename, filename_bak)) { + if (fileExists(filename) && !tryRenameFile(filename, filename_bak, FileDestination_e::ANY)) { return F("Could not rename to _bak"); } else { // File does not exist (anymore) - if (!tryRenameFile(tmpfile, filename)) { + if (!tryRenameFile(tmpfile, filename, FileDestination_e::ANY)) { error = F("Could not rename tmp file"); - if (tryRenameFile(filename_bak, filename)) { + if (tryRenameFile(filename_bak, filename, FileDestination_e::ANY)) { error += F("... reverted"); } else { error += F(" Not reverted!"); diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index 9d0f07645..2a1eb9f98 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -16,23 +16,14 @@ #include "../Globals/CPlugins.h" -/********************************************************************************************\ - file system error handling - Look here for error # reference: https://github.com/pellepl/spiffs/blob/master/src/spiffs.h - \*********************************************************************************************/ -String FileError(int line, const char *fname); /********************************************************************************************\ Keep track of number of flash writes. \*********************************************************************************************/ void flashCount(); -String flashGuard(); - String appendLineToFile(const String& fname, const String& line); -String appendToFile(const String& fname, const uint8_t *data, unsigned int size); - enum class FileDestination_e : uint8_t { ANY = 0, FLASH = 1, @@ -48,15 +39,8 @@ int fileSize(const String& fname); fs::File tryOpenFile(const String& fname, const String& mode, FileDestination_e destination = FileDestination_e::ANY); -bool tryRenameFile(const String& fname_old, const String& fname_new, FileDestination_e destination = FileDestination_e::ANY); - bool tryDeleteFile(const String& fname, FileDestination_e destination = FileDestination_e::ANY); -/********************************************************************************************\ - Fix stuff to clear out differences between releases - Return true when settings were changed/patched - \*********************************************************************************************/ -bool BuildFixes(); /********************************************************************************************\ Mount FS and check config.dat @@ -121,44 +105,11 @@ String SaveSettings(bool forFactoryReset = false); String SaveSecuritySettings(bool forFactoryReset = false); -void afterloadSettings(); - /********************************************************************************************\ Load settings from file system \*********************************************************************************************/ String LoadSettings(); -/********************************************************************************************\ - Disable Plugin, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disablePlugin(uint8_t bootFailedCount); -uint8_t disableAllPlugins(uint8_t bootFailedCount); - -/********************************************************************************************\ - Disable Controller, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disableController(uint8_t bootFailedCount); -uint8_t disableAllControllers(uint8_t bootFailedCount); - -/********************************************************************************************\ - Disable Notification, based on bootFailedCount - \*********************************************************************************************/ -#if FEATURE_NOTIFIER -uint8_t disableNotification(uint8_t bootFailedCount); -uint8_t disableAllNotifications(uint8_t bootFailedCount); -#endif - -/********************************************************************************************\ - Disable Rules, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disableRules(uint8_t bootFailedCount); - -/********************************************************************************************\ - Disable Network Interfaces, based on bootFailedCount - \*********************************************************************************************/ -uint8_t disableNetwork(uint8_t bootFailedCount); -uint8_t disableAllNetworkss(uint8_t bootFailedCount); - bool getAndLogSettingsParameters(bool read, SettingsType::Enum settingsType, int index, int& offset, int& max_size); diff --git a/src/src/Helpers/ESPEasy_TouchHandler.cpp b/src/src/Helpers/ESPEasy_TouchHandler.cpp index 5fcb0af03..c197c2ecb 100644 --- a/src/src/Helpers/ESPEasy_TouchHandler.cpp +++ b/src/src/Helpers/ESPEasy_TouchHandler.cpp @@ -34,7 +34,7 @@ tTouchObjects::tTouchObjects() : * toString: Display-value for the touch action ***************************************************************************/ # if TOUCH_FEATURE_EXTENDED_TOUCH -const __FlashStringHelper* toString(Touch_action_e action) { +static const __FlashStringHelper* toString(Touch_action_e action) { switch (action) { case Touch_action_e::Default: return F("Default"); case Touch_action_e::ActivateGroup: return F("Activate Group"); @@ -52,7 +52,7 @@ const __FlashStringHelper* toString(Touch_action_e action) { * toString: Display-value for the swipe action ***************************************************************************/ # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE -const __FlashStringHelper* toString(Swipe_action_e action) { +static const __FlashStringHelper* toString(Swipe_action_e action) { switch (action) { case Swipe_action_e::Up: return F("Up"); case Swipe_action_e::UpRight: return F("Up-Right"); diff --git a/src/src/Helpers/ESPEasy_TouchHandler.h b/src/src/Helpers/ESPEasy_TouchHandler.h index dee2e435f..cd6a03da4 100644 --- a/src/src/Helpers/ESPEasy_TouchHandler.h +++ b/src/src/Helpers/ESPEasy_TouchHandler.h @@ -280,11 +280,6 @@ enum class Swipe_action_e : uint8_t { }; # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE -const __FlashStringHelper* toString(Touch_action_e action); - -# if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE -const __FlashStringHelper* toString(Swipe_action_e action); -# endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE class ESPEasy_TouchHandler { public: diff --git a/src/src/Helpers/ESPEasy_key_value_store_import_export.cpp b/src/src/Helpers/ESPEasy_key_value_store_import_export.cpp index 6fdff263f..56466a582 100644 --- a/src/src/Helpers/ESPEasy_key_value_store_import_export.cpp +++ b/src/src/Helpers/ESPEasy_key_value_store_import_export.cpp @@ -82,7 +82,7 @@ bool ESPEasy_key_value_store_import_export::do_export( } -bool getNextKeyValue(String& json, String& key, String& value) +static bool getNextKeyValue(String& json, String& key, String& value) { String keyValueStr; int pos = json.indexOf(','); diff --git a/src/src/Helpers/ESPEasy_math.cpp b/src/src/Helpers/ESPEasy_math.cpp index f2052bc10..272491bb7 100644 --- a/src/src/Helpers/ESPEasy_math.cpp +++ b/src/src/Helpers/ESPEasy_math.cpp @@ -65,12 +65,12 @@ uint64_t computeDecimalFactorForDecimals(int nrDecimals) } #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -double estimateDoubleEpsilon(const double& a, const double& b) +static double estimateDoubleEpsilon(const double& a, const double& b) { return std::max(std::abs(a), std::abs(b)) * ESPEASY_DOUBLE_EPSILON; } #endif -float estimateEpsilon(const float& a, const float& b) +static float estimateEpsilon(const float& a, const float& b) { return std::max(std::abs(a), std::abs(b)) * ESPEASY_FLOAT_EPSILON; } diff --git a/src/src/Helpers/ESPEasy_time_calc.cpp b/src/src/Helpers/ESPEasy_time_calc.cpp index 3c0406f42..2c3879511 100644 --- a/src/src/Helpers/ESPEasy_time_calc.cpp +++ b/src/src/Helpers/ESPEasy_time_calc.cpp @@ -22,13 +22,14 @@ uint32_t unix_time_frac_to_micros(uint32_t unix_time_frac) { return static_cast(unix_time_frac) / 4294.967f; } - -uint32_t millis_to_unix_time_frac(uint32_t millis) +/* +static uint32_t millis_to_unix_time_frac(uint32_t millis) { return static_cast(millis) * 4294967.0f; } + */ -uint32_t micros_to_unix_time_frac(uint32_t micros) +static uint32_t micros_to_unix_time_frac(uint32_t micros) { return static_cast(micros) * 4294.967f; } @@ -58,7 +59,8 @@ uint32_t micros_to_sec_usec(int64_t micros, uint32_t& usec) return static_cast(seconds); } -uint64_t sec_time_frac_to_uptime_offset_usec(const uint32_t& seconds, +/* +static uint64_t sec_time_frac_to_uptime_offset_usec(const uint32_t& seconds, uint32_t time_frac) { const uint64_t unix_time_usec = sec_time_frac_to_Micros(seconds, time_frac); @@ -67,13 +69,14 @@ uint64_t sec_time_frac_to_uptime_offset_usec(const uint32_t& seconds, return unix_time_usec; return unix_time_usec - cur_usec; } +*/ -bool isLeapYear(int year) { +static bool isLeapYear(int year) { return (year > 0) && !(year % 4) && ((year % 100) || !(year % 400)); } -uint8_t getMonthDays(int year, uint8_t month) { +static uint8_t getMonthDays(int year, uint8_t month) { const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if ((month == 1) && isLeapYear(year)) { diff --git a/src/src/Helpers/ESPEasy_time_calc.h b/src/src/Helpers/ESPEasy_time_calc.h index 12a61c2fe..da40748d7 100644 --- a/src/src/Helpers/ESPEasy_time_calc.h +++ b/src/src/Helpers/ESPEasy_time_calc.h @@ -79,25 +79,18 @@ inline bool usecTimeOutReached(const uint64_t& timer) { uint32_t unix_time_frac_to_millis(uint32_t unix_time_frac); uint32_t unix_time_frac_to_micros(uint32_t unix_time_frac); -uint32_t millis_to_unix_time_frac(uint32_t millis); -uint32_t micros_to_unix_time_frac(uint32_t micros); uint32_t micros_to_sec_time_frac(int64_t micros, uint32_t& unix_time_frac); uint64_t sec_time_frac_to_Micros(uint32_t seconds, uint32_t time_frac); uint32_t micros_to_sec_usec(int64_t micros, uint32_t& usec); -uint64_t sec_time_frac_to_uptime_offset_usec(const uint32_t& seconds, - uint32_t time_frac = 0); /********************************************************************************************\ Unix Time computations \*********************************************************************************************/ -bool isLeapYear(int year); - // Get number of days in a month. // Month starts at 0 for January. -uint8_t getMonthDays(int year, uint8_t month); uint8_t getMonthDays(const struct tm& tm); uint32_t makeTime(const struct tm& tm); diff --git a/src/src/Helpers/FS_Helper.h b/src/src/Helpers/FS_Helper.h index ffb8a8927..58653a967 100644 --- a/src/src/Helpers/FS_Helper.h +++ b/src/src/Helpers/FS_Helper.h @@ -4,8 +4,6 @@ #include "../../ESPEasy_common.h" -// Macro used to make file system operations a bit more readable. -#define SPIFFS_CHECK(result, fname) if (!(result)) { return(FileError(__LINE__, fname)); } #ifdef USE_LITTLEFS #ifdef ESP32 diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 30cc93215..d6a5a4d97 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -15,6 +15,192 @@ # include +/** + * @brief Reads and processes keys from a json.keys file and navigates the JSON document. + */ +static void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + int nr_decimals = ESPEASY_DOUBLE_NR_DECIMALS; +# else // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + int nr_decimals = ESPEASY_FLOAT_NR_DECIMALS; +# endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + // Open the `json.keys` file +# ifdef ESP8266 + const String fileName = F("json.keys"); +# else // ifdef ESP8266 + const String fileName = F("/json.keys"); +# endif // ifdef ESP8266 + + if (!fileExists(fileName)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat(F("%s does not exist!"), fileName.c_str())); + } + return; + } + File keyFile = ESPEASY_FS.open(fileName, "r"); + + int keyCount = 0; + int successfullyProcessedCount = 0; // Counter for successfully processed keys + String csvOutput; // Collect values as a CSV string + + while (keyFile.available()) { + String key = keyFile.readStringUntil('\n'); + key.trim(); // Remove extra spaces or newlines + + if (key.isEmpty()) { + continue; + } + int keyNumber = 0; + + // Extract the number prefix (before the first dot) + if (key.indexOf(':') != -1) { + keyNumber = key.substring(0, key.indexOf(':')).toInt(); + key = key.substring(key.indexOf(':') + 1); + } + + // Only process keys that match the filterNumber (or process all if no match) + if ((numJson > 0) && (keyNumber != numJson)) { + continue; // Skip this key if it doesn't match the filter number + } + keyCount++; + + if (keyCount > MAX_KEYS) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat(F("Warning: More than %d keys in %s"), MAX_KEYS, fileName.c_str())); + } + } + + # if FEATURE_JSON_PARSE + const String val = getJsonValue(root, key, false); // Return arrays and objects as csv, _not_ JSON formatted + + if (!val.isEmpty()) { + successfullyProcessedCount++; + csvOutput += val; + csvOutput += ','; + } + # else // if FEATURE_JSON_PARSE + // Process the key and navigate the JSON + JsonVariant value = *root; + size_t start = 0, end; + + while ((end = key.indexOf('.', start)) != (unsigned int)-1) { + String part = key.substring(start, end); + start = end + 1; + + // Look for an array e.g., "result[0]" → object "result", index 0 + int bracketStart = part.indexOf('['); + + if (bracketStart != -1) { + String objectName = part.substring(0, bracketStart); + String indexStr = part.substring(bracketStart + 1, part.indexOf(']', bracketStart)); + + if (objectName.length() > 0) { + value = value[objectName]; // Access the object + } + + if (value.is()) { + int index = indexStr.toInt(); + value = value[index]; + } else { + value = value[indexStr]; // fallback if not actually array + } + } else { + // Normal object access without array + value = value[part]; + } + + if (value.isNull()) { + break; // Key path is invalid + } + } + + if (!value.isNull()) { + successfullyProcessedCount++; + String lastPart = key.substring(start); + int bracketStart = lastPart.indexOf('['); + + if (bracketStart != -1) { + String objectName = lastPart.substring(0, bracketStart); + String indexStr = lastPart.substring(bracketStart + 1, lastPart.indexOf(']', bracketStart)); + + if (objectName.length() > 0) { + value = value[objectName]; + } + + if (value.is()) { + value = value[indexStr.toInt()]; + } else { + value = value[indexStr]; + } + } else { + value = value[lastPart]; + } + } + + // Append the value to the CSV string if it exists + if (!value.isNull()) { + if (value.is()) { + csvOutput += String(value.as()); + } else if (value.is()) { + csvOutput += doubleToString(value.as(), nr_decimals, true); + } else if (value.is()) { + csvOutput += String(value.as()); + } else if (value.is()) { + // If the value is an array, iterate over its elements + JsonArray array = value.as(); + size_t arraySize = array.size(); // Get the total number of elements in the array + size_t currentIndex = 0; // Track the current index + + for (JsonVariant element : array) { + if (element.is()) { + csvOutput += String(element.as()); + } else if (element.is()) { + csvOutput += doubleToString(element.as(), nr_decimals, true); + } else if (element.is()) { + csvOutput += String(element.as()); + } else { + csvOutput += F("unknown"); + } + + // Add a comma unless it's the last element + currentIndex++; + + if (currentIndex < arraySize) { + csvOutput += ','; + } + } + } else { + csvOutput += F("unknown"); + } + } else { + csvOutput += F("null"); // Indicate missing value + csvOutput += val; + csvOutput += ','; + } + csvOutput += ','; + # endif // if FEATURE_JSON_PARSE + } + + keyFile.close(); + + if (!csvOutput.isEmpty()) { // If there is nothing to show don't create an event + if (csvOutput.charAt(csvOutput.length() - 1) == ',') { + // Check if the last character is a comma + csvOutput.remove(csvOutput.length() - 1); // Remove the last character + } + + // Log the results + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("Successfully processed %d out of %d keys"), successfullyProcessedCount, keyCount)); + } + eventQueue.addMove(strformat(F("JsonReply%s%s=%s"), + numJson != 0 ? "#" : "", + toStringNoZero(numJson).c_str(), + csvOutput.c_str())); + } +} + # endif // if FEATURE_JSON_EVENT void eventFromResponse(const String& host, const int& httpCode, const String& uri, ESPEasy_HTTPClient& http, const int& parseJson) { @@ -269,188 +455,7 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // ------------------------------------------------------------------------------------------- JSONevent Key processing # if FEATURE_JSON_EVENT -void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - int nr_decimals = ESPEASY_DOUBLE_NR_DECIMALS; -# else // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - int nr_decimals = ESPEASY_FLOAT_NR_DECIMALS; -# endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - // Open the `json.keys` file -# ifdef ESP8266 - const String fileName = F("json.keys"); -# else // ifdef ESP8266 - const String fileName = F("/json.keys"); -# endif // ifdef ESP8266 - - if (!fileExists(fileName)) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, strformat(F("%s does not exist!"), fileName.c_str())); - } - return; - } - File keyFile = ESPEASY_FS.open(fileName, "r"); - - int keyCount = 0; - int successfullyProcessedCount = 0; // Counter for successfully processed keys - String csvOutput; // Collect values as a CSV string - - while (keyFile.available()) { - String key = keyFile.readStringUntil('\n'); - key.trim(); // Remove extra spaces or newlines - - if (key.isEmpty()) { - continue; - } - int keyNumber = 0; - - // Extract the number prefix (before the first dot) - if (key.indexOf(':') != -1) { - keyNumber = key.substring(0, key.indexOf(':')).toInt(); - key = key.substring(key.indexOf(':') + 1); - } - - // Only process keys that match the filterNumber (or process all if no match) - if ((numJson > 0) && (keyNumber != numJson)) { - continue; // Skip this key if it doesn't match the filter number - } - keyCount++; - - if (keyCount > MAX_KEYS) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, strformat(F("Warning: More than %d keys in %s"), MAX_KEYS, fileName.c_str())); - } - } - - # if FEATURE_JSON_PARSE - const String val = getJsonValue(root, key, false); // Return arrays and objects as csv, _not_ JSON formatted - - if (!val.isEmpty()) { - successfullyProcessedCount++; - csvOutput += val; - csvOutput += ','; - } - # else // if FEATURE_JSON_PARSE - // Process the key and navigate the JSON - JsonVariant value = *root; - size_t start = 0, end; - - while ((end = key.indexOf('.', start)) != (unsigned int)-1) { - String part = key.substring(start, end); - start = end + 1; - - // Look for an array e.g., "result[0]" → object "result", index 0 - int bracketStart = part.indexOf('['); - - if (bracketStart != -1) { - String objectName = part.substring(0, bracketStart); - String indexStr = part.substring(bracketStart + 1, part.indexOf(']', bracketStart)); - - if (objectName.length() > 0) { - value = value[objectName]; // Access the object - } - - if (value.is()) { - int index = indexStr.toInt(); - value = value[index]; - } else { - value = value[indexStr]; // fallback if not actually array - } - } else { - // Normal object access without array - value = value[part]; - } - - if (value.isNull()) { - break; // Key path is invalid - } - } - - if (!value.isNull()) { - successfullyProcessedCount++; - String lastPart = key.substring(start); - int bracketStart = lastPart.indexOf('['); - - if (bracketStart != -1) { - String objectName = lastPart.substring(0, bracketStart); - String indexStr = lastPart.substring(bracketStart + 1, lastPart.indexOf(']', bracketStart)); - - if (objectName.length() > 0) { - value = value[objectName]; - } - - if (value.is()) { - value = value[indexStr.toInt()]; - } else { - value = value[indexStr]; - } - } else { - value = value[lastPart]; - } - } - - // Append the value to the CSV string if it exists - if (!value.isNull()) { - if (value.is()) { - csvOutput += String(value.as()); - } else if (value.is()) { - csvOutput += doubleToString(value.as(), nr_decimals, true); - } else if (value.is()) { - csvOutput += String(value.as()); - } else if (value.is()) { - // If the value is an array, iterate over its elements - JsonArray array = value.as(); - size_t arraySize = array.size(); // Get the total number of elements in the array - size_t currentIndex = 0; // Track the current index - - for (JsonVariant element : array) { - if (element.is()) { - csvOutput += String(element.as()); - } else if (element.is()) { - csvOutput += doubleToString(element.as(), nr_decimals, true); - } else if (element.is()) { - csvOutput += String(element.as()); - } else { - csvOutput += F("unknown"); - } - - // Add a comma unless it's the last element - currentIndex++; - - if (currentIndex < arraySize) { - csvOutput += ','; - } - } - } else { - csvOutput += F("unknown"); - } - } else { - csvOutput += F("null"); // Indicate missing value - csvOutput += val; - csvOutput += ','; - } - csvOutput += ','; - # endif // if FEATURE_JSON_PARSE - } - - keyFile.close(); - - if (!csvOutput.isEmpty()) { // If there is nothing to show don't create an event - if (csvOutput.charAt(csvOutput.length() - 1) == ',') { - // Check if the last character is a comma - csvOutput.remove(csvOutput.length() - 1); // Remove the last character - } - - // Log the results - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("Successfully processed %d out of %d keys"), successfullyProcessedCount, keyCount)); - } - eventQueue.addMove(strformat(F("JsonReply%s%s=%s"), - numJson != 0 ? "#" : "", - toStringNoZero(numJson).c_str(), - csvOutput.c_str())); - } -} // ------------------------------------------------------------------------------------------- JSONevent Helper UriParseResult parseUriPath(const String& path) { diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index 358803ca4..5b49fd374 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -28,12 +28,6 @@ void eventFromResponse(const String& host, # if FEATURE_JSON_EVENT -/** - * @brief Reads and processes keys from a json.keys file and navigates the JSON document. - */ -void readAndProcessJsonKeys(DynamicJsonDocument*root, - int numJson); - /** * @brief Result of parsing a URI for JSON flag. */ diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index 22fa0dfc0..9f3547000 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -709,7 +709,7 @@ void readBootCause() { /********************************************************************************************\ Hardware specific configurations \*********************************************************************************************/ -const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { +static const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { switch (model) { case DeviceModel::DeviceModel_Sonoff_Basic: case DeviceModel::DeviceModel_Sonoff_TH1x: @@ -746,7 +746,7 @@ const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { return F(""); } -const __FlashStringHelper* getDeviceModelTypeString(DeviceModel model) +static const __FlashStringHelper* getDeviceModelTypeString(DeviceModel model) { switch (model) { #if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) @@ -862,7 +862,7 @@ void setFactoryDefault(DeviceModel model) { /********************************************************************************************\ Add pre defined plugins and rules. \*********************************************************************************************/ -void addSwitchPlugin(taskIndex_t taskIndex, int gpio, const String& name, bool activeLow) { +static void addSwitchPlugin(taskIndex_t taskIndex, int gpio, const String& name, bool activeLow) { setTaskDevice_to_TaskIndex(PLUGIN_GPIO, taskIndex); const int pins[] = { gpio, -1, -1 }; @@ -900,7 +900,7 @@ void addPredefinedPlugins(const GpioFactorySettingsStruct& gpio_settings) { } } -void addButtonRelayRule(uint8_t buttonNumber, int relay_gpio) { +static void addButtonRelayRule(uint8_t buttonNumber, int relay_gpio) { Settings.UseRules = true; String fileName; diff --git a/src/src/Helpers/Hardware.h b/src/src/Helpers/Hardware.h index 6eed51fe9..178cc9272 100644 --- a/src/src/Helpers/Hardware.h +++ b/src/src/Helpers/Hardware.h @@ -92,8 +92,6 @@ void readBootCause(); /********************************************************************************************\ Hardware specific configurations \*********************************************************************************************/ -const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model); - String getDeviceModelString(DeviceModel model); bool modelMatchingFlashSize(DeviceModel model); @@ -103,15 +101,8 @@ void setFactoryDefault(DeviceModel model); /********************************************************************************************\ Add pre defined plugins and rules. \*********************************************************************************************/ -void addSwitchPlugin(taskIndex_t taskIndex, - int gpio, - const String& name, - bool activeLow); - void addPredefinedPlugins(const GpioFactorySettingsStruct& gpio_settings); -void addButtonRelayRule(uint8_t buttonNumber, - int relay_gpio); void addPredefinedRules(const GpioFactorySettingsStruct& gpio_settings); diff --git a/src/src/Helpers/Hardware_I2C.cpp b/src/src/Helpers/Hardware_I2C.cpp index 2f53a1857..cab5b99be 100644 --- a/src/src/Helpers/Hardware_I2C.cpp +++ b/src/src/Helpers/Hardware_I2C.cpp @@ -86,59 +86,7 @@ void initI2C() { I2CSelectHighClockSpeed(0); // Select first interface by default } -void I2CSelectHighClockSpeed(uint8_t i2cBus) { - const uint32_t clockSpeed = Settings.getI2CClockSpeed(i2cBus); - - I2CSelectClockSpeed(i2cBus, clockSpeed); -} - -void I2CSelectLowClockSpeed(uint8_t i2cBus) { - const uint32_t clockSpeed = Settings.getI2CClockSpeedSlow(i2cBus); - - I2CSelectClockSpeed(i2cBus, clockSpeed); -} - -void I2CSelect_Max100kHz_ClockSpeed(uint8_t i2cBus) { - const uint32_t clockSpeed = Settings.getI2CClockSpeed(i2cBus); - const uint32_t clockSpeed_Slow = Settings.getI2CClockSpeedSlow(i2cBus); - - if (clockSpeed <= 100000) { - I2CSelectHighClockSpeed(i2cBus); - } else if (clockSpeed_Slow <= 100000) { - I2CSelectLowClockSpeed(i2cBus); - } else { - I2CSelectClockSpeed(i2cBus, 100000); - } -} - -void I2CSelectClockSpeed(uint8_t i2cBus, uint32_t clockFreq) { - const int8_t i2c_sda = Settings.getI2CSdaPin(i2cBus); - const int8_t i2c_scl = Settings.getI2CSclPin(i2cBus); - const uint32_t ClockStretchLimit = Settings.getI2CClockStretch(i2cBus); - - I2CBegin(i2c_sda, i2c_scl, clockFreq, ClockStretchLimit); -} - -void I2CForceResetBus_swap_pins(uint8_t i2cBus, uint8_t address) { -#if FEATURE_CLEAR_I2C_STUCK - - if (!Settings.EnableClearHangingI2Cbus()) { return; } -#endif // if FEATURE_CLEAR_I2C_STUCK - - // As a final work-around, we temporary swap SDA and SCL, perform a scan and return pin order. - const int8_t i2c_sda = Settings.getI2CSdaPin(i2cBus); - const int8_t i2c_scl = Settings.getI2CSclPin(i2cBus); - const uint32_t ClockStretchLimit = Settings.getI2CClockStretch(i2cBus); - - I2CBegin(i2c_scl, i2c_sda, 100000, ClockStretchLimit); - I2C_wakeup(address); - delay(1); - - // Now we switch back to the correct pins - I2CSelectClockSpeed(i2cBus, 100000); -} - -void I2CBegin(int8_t sda, int8_t scl, uint32_t clockFreq, uint32_t clockStretch) { +static void I2CBegin(int8_t sda, int8_t scl, uint32_t clockFreq, uint32_t clockStretch) { #ifdef ESP32 uint32_t lastI2CClockSpeed = Wire.getClock(); #else // ifdef ESP32 @@ -209,6 +157,61 @@ void I2CBegin(int8_t sda, int8_t scl, uint32_t clockFreq, uint32_t clockStretch) } } +static void I2CSelectClockSpeed(uint8_t i2cBus, uint32_t clockFreq) { + const int8_t i2c_sda = Settings.getI2CSdaPin(i2cBus); + const int8_t i2c_scl = Settings.getI2CSclPin(i2cBus); + const uint32_t ClockStretchLimit = Settings.getI2CClockStretch(i2cBus); + + I2CBegin(i2c_sda, i2c_scl, clockFreq, ClockStretchLimit); +} + +void I2CSelectHighClockSpeed(uint8_t i2cBus) { + const uint32_t clockSpeed = Settings.getI2CClockSpeed(i2cBus); + + I2CSelectClockSpeed(i2cBus, clockSpeed); +} + +void I2CSelectLowClockSpeed(uint8_t i2cBus) { + const uint32_t clockSpeed = Settings.getI2CClockSpeedSlow(i2cBus); + + I2CSelectClockSpeed(i2cBus, clockSpeed); +} + +void I2CSelect_Max100kHz_ClockSpeed(uint8_t i2cBus) { + const uint32_t clockSpeed = Settings.getI2CClockSpeed(i2cBus); + const uint32_t clockSpeed_Slow = Settings.getI2CClockSpeedSlow(i2cBus); + + if (clockSpeed <= 100000) { + I2CSelectHighClockSpeed(i2cBus); + } else if (clockSpeed_Slow <= 100000) { + I2CSelectLowClockSpeed(i2cBus); + } else { + I2CSelectClockSpeed(i2cBus, 100000); + } +} + + +void I2CForceResetBus_swap_pins(uint8_t i2cBus, uint8_t address) { +#if FEATURE_CLEAR_I2C_STUCK + + if (!Settings.EnableClearHangingI2Cbus()) { return; } +#endif // if FEATURE_CLEAR_I2C_STUCK + + // As a final work-around, we temporary swap SDA and SCL, perform a scan and return pin order. + const int8_t i2c_sda = Settings.getI2CSdaPin(i2cBus); + const int8_t i2c_scl = Settings.getI2CSclPin(i2cBus); + const uint32_t ClockStretchLimit = Settings.getI2CClockStretch(i2cBus); + + I2CBegin(i2c_scl, i2c_sda, 100000, ClockStretchLimit); + I2C_wakeup(address); + delay(1); + + // Now we switch back to the correct pins + I2CSelectClockSpeed(i2cBus, 100000); +} + + + #if FEATURE_I2CMULTIPLEXER // Check if the I2C Multiplexer is enabled @@ -218,6 +221,7 @@ bool isI2CMultiplexerEnabled(uint8_t i2cBus) { } // Reset the I2C Multiplexer, if a pin is assigned for that. Pulled to low to force a reset. +/* void I2CMultiplexerReset(uint8_t i2cBus) { const uint8_t Multiplexer_ResetPin = Settings.getI2CMultiplexerResetPin(i2cBus); @@ -227,9 +231,10 @@ void I2CMultiplexerReset(uint8_t i2cBus) { digitalWrite(Multiplexer_ResetPin, HIGH); } } + */ // Shift the bit in the right position when selecting a single channel -uint8_t I2CMultiplexerShiftBit(uint8_t i2cBus, uint8_t i) { +static uint8_t I2CMultiplexerShiftBit(uint8_t i2cBus, uint8_t i) { uint8_t toWrite = 0; const int8_t Multiplexer_Type = Settings.getI2CMultiplexerType(i2cBus); diff --git a/src/src/Helpers/Hardware_I2C.h b/src/src/Helpers/Hardware_I2C.h index a6e31a703..4c5774878 100644 --- a/src/src/Helpers/Hardware_I2C.h +++ b/src/src/Helpers/Hardware_I2C.h @@ -12,20 +12,12 @@ void initI2C(); void I2CSelectHighClockSpeed(uint8_t i2cBus); void I2CSelectLowClockSpeed(uint8_t i2cBus); void I2CSelect_Max100kHz_ClockSpeed(uint8_t i2cBus); -void I2CSelectClockSpeed(uint8_t i2cBus, - uint32_t clockFreq); void I2CForceResetBus_swap_pins(uint8_t i2cBus, uint8_t address); -void I2CBegin(int8_t sda, - int8_t scl, - uint32_t clockFreq, - uint32_t clockStretch); #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); @@ -40,7 +32,7 @@ void SetI2CMultiplexer(uint8_t i2cBus, uint8_t I2CMultiplexerMaxChannels(uint8_t i2cBus); -void I2CMultiplexerReset(uint8_t i2cBus); +// void I2CMultiplexerReset(uint8_t i2cBus); bool I2CMultiplexerPortSelectedForTask(taskIndex_t taskIndex); #endif // if FEATURE_I2CMULTIPLEXER diff --git a/src/src/Helpers/Hardware_PWM.cpp b/src/src/Helpers/Hardware_PWM.cpp index a78116484..50065fef4 100644 --- a/src/src/Helpers/Hardware_PWM.cpp +++ b/src/src/Helpers/Hardware_PWM.cpp @@ -51,7 +51,7 @@ void initAnalogWrite() // Match frequency and resolution and optionally adjust duty accordingly // @return adjusted duty cycle -uint32_t adapt_ledc_frequency_resolution_duty(uint32_t& frequency, uint8_t& resolution, uint32_t duty = 0) +static uint32_t adapt_ledc_frequency_resolution_duty(uint32_t& frequency, uint8_t& resolution, uint32_t duty = 0) { while ((40000000u >> resolution) < frequency && resolution > 1) { --resolution; diff --git a/src/src/Helpers/Hardware_temperature_sensor.cpp b/src/src/Helpers/Hardware_temperature_sensor.cpp index cc89d6074..109a1e204 100644 --- a/src/src/Helpers/Hardware_temperature_sensor.cpp +++ b/src/src/Helpers/Hardware_temperature_sensor.cpp @@ -38,7 +38,7 @@ uint8_t temprature_sens_read(); # if defined(ESP32_CLASSIC) -esp_err_t do_read_internal_temperature(float& celsius) { +static esp_err_t do_read_internal_temperature(float& celsius) { esp_err_t result = ESP_FAIL; uint8_t raw = 128u; int8_t retries = 2; @@ -64,7 +64,7 @@ esp_err_t do_read_internal_temperature(float& celsius) { # elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C5) || defined(ESP32C6) || defined(ESP32C61) || defined(ESP32S2) || defined(ESP32S3) || defined(ESP32P4) -esp_err_t do_read_internal_temperature(float& celsius) { +static esp_err_t do_read_internal_temperature(float& celsius) { esp_err_t result = ESP_FAIL; celsius = 0.0f; // Make sure it is initialized and within the default range. diff --git a/src/src/Helpers/JSON_helper.cpp b/src/src/Helpers/JSON_helper.cpp index 3ca188aad..d25b4a788 100644 --- a/src/src/Helpers/JSON_helper.cpp +++ b/src/src/Helpers/JSON_helper.cpp @@ -12,82 +12,7 @@ #if FEATURE_JSON_PARSE # include "../Helpers/StringConverter.h" -// Private for now -String getJsonValue(JsonVariant element, - bool asJson); - -String getJsonValue(DynamicJsonDocument *root, - String key, - bool asJson) { - // Process the key and navigate the JSON - JsonVariant value = *root; - size_t start = 0; - size_t end; - String result; - - while ((end = key.indexOf('.', start)) != -1) { - const String part = key.substring(start, end); - start = end + 1; - - // Look for an array e.g., "result[0]" → object "result", index 0 - const int bracketStart = part.indexOf('['); - - if (bracketStart != -1) { - const String objectName = part.substring(0, bracketStart); - const String indexStr = part.substring(bracketStart + 1, part.indexOf(']', bracketStart)); - - if (objectName.length() > 0) { - value = value[objectName]; // Access the object - } - - if (value.is()) { - value = value[indexStr.toInt()]; - } else { - value = value[indexStr]; // fallback if not actually array - } - } else { - // Normal object access without array - value = value[part]; - } - - if (value.isNull()) { - break; // Key path is invalid - } - } - - if (!value.isNull()) { - const String lastPart = key.substring(start); - const int bracketStart = lastPart.indexOf('['); - - if (bracketStart != -1) { - const String objectName = lastPart.substring(0, bracketStart); - const String indexStr = lastPart.substring(bracketStart + 1, lastPart.indexOf(']', bracketStart)); - - if (objectName.length() > 0) { - value = value[objectName]; - } - - if (value.is()) { - value = value[indexStr.toInt()]; - } else { - value = value[indexStr]; - } - } else { - value = value[lastPart]; - } - } - - // Append the value to the CSV string if it exists - if (!value.isNull()) { - result += getJsonValue(value, asJson); - } else { - result += F("null"); - } - - return result; -} - -String getJsonValue(JsonVariant value, bool asJson) { +static String getJsonValue(JsonVariant value, bool asJson) { # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE const int nr_decimals = ESPEASY_DOUBLE_NR_DECIMALS; # else // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE @@ -170,4 +95,75 @@ String getJsonValue(JsonVariant value, bool asJson) { return result; } +String getJsonValue(DynamicJsonDocument *root, + String key, + bool asJson) { + // Process the key and navigate the JSON + JsonVariant value = *root; + size_t start = 0; + size_t end; + String result; + + while ((end = key.indexOf('.', start)) != -1) { + const String part = key.substring(start, end); + start = end + 1; + + // Look for an array e.g., "result[0]" → object "result", index 0 + const int bracketStart = part.indexOf('['); + + if (bracketStart != -1) { + const String objectName = part.substring(0, bracketStart); + const String indexStr = part.substring(bracketStart + 1, part.indexOf(']', bracketStart)); + + if (objectName.length() > 0) { + value = value[objectName]; // Access the object + } + + if (value.is()) { + value = value[indexStr.toInt()]; + } else { + value = value[indexStr]; // fallback if not actually array + } + } else { + // Normal object access without array + value = value[part]; + } + + if (value.isNull()) { + break; // Key path is invalid + } + } + + if (!value.isNull()) { + const String lastPart = key.substring(start); + const int bracketStart = lastPart.indexOf('['); + + if (bracketStart != -1) { + const String objectName = lastPart.substring(0, bracketStart); + const String indexStr = lastPart.substring(bracketStart + 1, lastPart.indexOf(']', bracketStart)); + + if (objectName.length() > 0) { + value = value[objectName]; + } + + if (value.is()) { + value = value[indexStr.toInt()]; + } else { + value = value[indexStr]; + } + } else { + value = value[lastPart]; + } + } + + // Append the value to the CSV string if it exists + if (!value.isNull()) { + result += getJsonValue(value, asJson); + } else { + result += F("null"); + } + + return result; +} + #endif // if FEATURE_JSON_PARSE diff --git a/src/src/Helpers/Log_Helper.cpp b/src/src/Helpers/Log_Helper.cpp index 1871ce0a2..024b9ec76 100644 --- a/src/src/Helpers/Log_Helper.cpp +++ b/src/src/Helpers/Log_Helper.cpp @@ -15,7 +15,7 @@ #if FEATURE_SD -void addToSDLog(uint8_t logLevel, const String& str) +static void addToSDLog(uint8_t logLevel, const String& str) { if (!str.isEmpty() && loglevelActiveFor(LOG_TO_SDCARD, logLevel)) { String logName = patch_fname(F("log.txt")); diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 609548fdc..4ba867349 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -92,34 +92,10 @@ void etharp_gratuitous_r(struct netif *netif) { tcpip_callback_with_block((t #if FEATURE_ESPEASY_P2P -/*********************************************************************************************\ - Send event using UDP message -\*********************************************************************************************/ -void SendUDPCommand(uint8_t destUnit, const char *data, uint8_t dataLength) -{ - if (!NetworkConnected(10)) { - return; - } - - if (destUnit != 0) - { - sendUDP(destUnit, (const uint8_t *)data, dataLength); - delay(10); - } else { - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { - if (it->first != Settings.Unit) { - sendUDP(it->first, (const uint8_t *)data, dataLength); - delay(10); - } - } - } - delay(50); -} - /*********************************************************************************************\ Send UDP message to specific unit (unit 255=broadcast) \*********************************************************************************************/ -void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) +static void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) { if (!NetworkConnected(10)) { return; @@ -151,6 +127,32 @@ void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) delay(0); } + +/*********************************************************************************************\ + Send event using UDP message +\*********************************************************************************************/ +void SendUDPCommand(uint8_t destUnit, const char *data, uint8_t dataLength) +{ + if (!NetworkConnected(10)) { + return; + } + + if (destUnit != 0) + { + sendUDP(destUnit, (const uint8_t *)data, dataLength); + delay(10); + } else { + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { + if (it->first != Settings.Unit) { + sendUDP(it->first, (const uint8_t *)data, dataLength); + delay(10); + } + } + } + delay(50); +} + + /*********************************************************************************************\ Update UDP port (ESPEasy propiertary protocol) \*********************************************************************************************/ @@ -709,7 +711,7 @@ bool SSDP_begin() { /********************************************************************************************\ Send SSDP messages (notify & responses) \*********************************************************************************************/ -void SSDP_send(uint8_t method) { +static void SSDP_send(uint8_t method) { uint32_t ip = ESPEasy::net::NetworkLocalIP(); // FIXME TD-er: Why create String objects of these flashstrings? @@ -942,12 +944,14 @@ void SSDP_update() { // ******************************************************************************** // Return subnet range of active network interface. // ******************************************************************************** +/* bool getSubnetRange(IPAddress& low, IPAddress& high) { low = ESPEasy::net::NetworkID(); high = ESPEasy::net::NetworkBroadcast(); return true; } + */ // ******************************************************************************** // Functions to test and handle network/client connectivity. @@ -956,7 +960,7 @@ bool getSubnetRange(IPAddress& low, IPAddress& high) #ifdef CORE_POST_2_5_0 # include #endif // ifdef CORE_POST_2_5_0 - +/* bool hasIPaddr() { if (useStaticIP()) { return true; } @@ -965,12 +969,12 @@ bool hasIPaddr() { for (auto addr : addrList) { if ((configured = (!addr.isLocal() && (addr.ifnumber() == STATION_IF)))) { - /* - ESPEASY_SERIAL_CONSOLE_PORT.printf("STA: IF='%s' hostname='%s' addr= %s\n", - addr.ifname().c_str(), - addr.ifhostname(), - addr.toString().c_str()); - */ + + // ESPEASY_SERIAL_CONSOLE_PORT.printf("STA: IF='%s' hostname='%s' addr= %s\n", + // addr.ifname().c_str(), + // addr.ifhostname(), + // addr.toString().c_str()); + break; } } @@ -979,7 +983,7 @@ bool hasIPaddr() { return WiFi.isConnected(); #endif // ifdef CORE_POST_2_5_0 } - +*/ bool useStaticIP() { #if FEATURE_ETHERNET @@ -1120,7 +1124,7 @@ bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t tim #endif // FEATURE_HTTP_CLIENT -void scrubDNS() { +static void scrubDNS() { /* #if FEATURE_ETHERNET @@ -1309,7 +1313,7 @@ void sendGratuitousARP() { #endif // ifdef SUPPORT_ARP } -bool splitHostPortString(const String& hostPortString, String& host, uint16_t& port) { +static bool splitHostPortString(const String& hostPortString, String& host, uint16_t& port) { port = 80; // Some default int index_colon = hostPortString.indexOf(':'); @@ -1383,7 +1387,10 @@ String splitURL(const String& fullURL, String& user, String& pass, String& host, return fullURL.substring(endhost); } -String joinURL(const String& user, const String& pass, const String& host, uint16_t& port, const String& uri, const String& protocol) +# if defined(CORE_POST_2_6_0) || defined(ESP32) +# if FEATURE_TLS + +static String joinURL(const String& user, const String& pass, const String& host, uint16_t& port, const String& uri, const String& protocol) { String fullURL; if (protocol.isEmpty()) { @@ -1410,8 +1417,10 @@ String joinURL(const String& user, const String& pass, const String& host, uint1 return fullURL; } +#endif +#endif -String get_user_agent_string() { +static String get_user_agent_string() { static unsigned int agent_size = 20; String userAgent; @@ -1426,7 +1435,7 @@ String get_user_agent_string() { return userAgent; } -bool splitHeaders(int& strpos, const String& multiHeaders, String& name, String& value) { +static bool splitHeaders(int& strpos, const String& multiHeaders, String& name, String& value) { if (strpos < 0) { return false; } @@ -1450,7 +1459,7 @@ bool splitHeaders(int& strpos, const String& multiHeaders, String& name, String& return true; } -String extractParam(const String& authReq, const String& param, const char delimit) { +static String extractParam(const String& authReq, const String& param, const char delimit) { int _begin = authReq.indexOf(param); if (_begin == -1) { return EMPTY_STRING; } @@ -1459,7 +1468,7 @@ String extractParam(const String& authReq, const String& param, const char delim #if FEATURE_HTTP_CLIENT -String getCNonce(const int len) { +static String getCNonce(const int len) { static const char alphanum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; @@ -1473,12 +1482,12 @@ String getCNonce(const int len) { return s; } -String getDigestAuth(const String& authReq, - const String& username, - const String& password, - const String& method, - const String& uri, - unsigned int counter) { +static String getDigestAuth(const String& authReq, + const String& username, + const String& password, + const String& method, + const String& uri, + unsigned int counter) { // extracting required parameters for RFC 2069 simpler Digest const String realm = extractParam(authReq, F("realm=\""), '"'); const String nonce = extractParam(authReq, F("nonce=\""), '"'); @@ -1524,7 +1533,7 @@ String getDigestAuth(const String& authReq, # if !defined(BUILD_NO_DEBUG) || defined(ESP32) -void log_http_result(const ESPEasy_HTTPClient& http, +static void log_http_result(const ESPEasy_HTTPClient& http, const String & logIdentifier, const String & host, const String & HttpMethod, @@ -1564,8 +1573,10 @@ void log_http_result(const ESPEasy_HTTPClient& http, } # endif // ifndef BUILD_NO_DEBUG - -int http_authenticate(const String& logIdentifier, +// Initiate the HTTP connection. +// Also try to authenticate using either Basic auth or Digest. +// @retval HTTP return code. +static int http_authenticate(const String& logIdentifier, WiFiClient & client, ESPEasy_HTTPClient & http, uint16_t timeout, @@ -1728,7 +1739,7 @@ int http_authenticate(const String& logIdentifier, http.end(); # if defined(CORE_POST_2_6_0) || defined(ESP32) # if FEATURE_TLS - const String fullURL = joinURL(user, pass, host, port, uri); + const String fullURL = joinURL(user, pass, host, port, uri, EMPTY_STRING); if (fullURL.startsWith(F("https"))) http.begin(fullURL, nullptr); // HTTPS else @@ -1916,7 +1927,7 @@ bool downloadFile(const String& url, String file_save) { // User and Pass may be updated if they occur in the hostname part. // Thus have to be copied instead of const reference. -bool start_downloadFile(WiFiClient & client, +static bool start_downloadFile(WiFiClient & client, ESPEasy_HTTPClient & http, const String& url, String & file_save, diff --git a/src/src/Helpers/Networking.h b/src/src/Helpers/Networking.h index 887ef0da1..32b4b0de2 100644 --- a/src/src/Helpers/Networking.h +++ b/src/src/Helpers/Networking.h @@ -94,11 +94,6 @@ uint8_t getTypeForUnit(uint8_t unit); \*********************************************************************************************/ String getTypeStringForUnit(uint8_t unit); -/*********************************************************************************************\ - Send UDP message to specific unit (unit 255=broadcast) -\*********************************************************************************************/ -void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size); - /*********************************************************************************************\ Refresh aging for remote units, drop if too old... \*********************************************************************************************/ @@ -152,10 +147,6 @@ extern uint32_t _notify_time; \*********************************************************************************************/ bool SSDP_begin(); -/********************************************************************************************\ - Send SSDP messages (notify & responses) - \*********************************************************************************************/ -void SSDP_send(uint8_t method); /********************************************************************************************\ SSDP message processing @@ -169,7 +160,7 @@ void SSDP_update(); // ******************************************************************************** // Return subnet range of WiFi. // ******************************************************************************** -bool getSubnetRange(IPAddress& low, IPAddress& high); +//bool getSubnetRange(IPAddress& low, IPAddress& high); // ******************************************************************************** // Functions to test and handle network/client connectivity. @@ -180,7 +171,7 @@ bool getSubnetRange(IPAddress& low, IPAddress& high); #endif // ifdef CORE_POST_2_5_0 -bool hasIPaddr(); +//bool hasIPaddr(); bool useStaticIP(); @@ -195,7 +186,6 @@ bool connectClient(WiFiClient& client, const char *hostname, uint16_t port, uint bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t timeout_ms = 100); #endif // FEATURE_HTTP_CLIENT -void scrubDNS(); bool IPAddressSet(const IPAddress& ip); @@ -214,8 +204,6 @@ bool beginWiFiUDP_randomPort(WiFiUDP& udp); void sendGratuitousARP(); -bool splitHostPortString(const String& hostPortString, String& host, uint16_t& port); - // Split the username and password from a string like this: // username:password@hostname:portnr // @param hostPortString The string to parse @@ -230,32 +218,8 @@ bool splitUserPass_HostPortString(const String& hostPortString, String& user, St // Return value is everything after the hostname:port section (including /) String splitURL(const String& fullURL, String& user, String& pass, String& host, uint16_t& port, String& file, String& protocol); -String joinURL(const String& user, const String& pass, const String& host, uint16_t& port, const String& uri, const String& protocol = EMPTY_STRING ); - #if FEATURE_HTTP_CLIENT -// Initiate the HTTP connection. -// Also try to authenticate using either Basic auth or Digest. -// @retval HTTP return code. -int http_authenticate(const String& logIdentifier, - WiFiClient & client, - ESPEasy_HTTPClient & http, - uint16_t timeout, - const String& user, - const String& pass, - const String& host, - uint16_t port, - const String& protocol, - # if FEATURE_JSON_EVENT - String uri, - # else // if FEATURE_JSON_EVENT - const String& uri, - # endif // if FEATURE_JSON_EVENT - const String& HttpMethod, - const String& header, - const String& postStr, - bool must_check_reply); - String send_via_http(const String& logIdentifier, uint16_t timeout, diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 59b874b63..31e590e82 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -354,7 +354,7 @@ void scheduleNextMQTTdelayQueue() { } } -void schedule_all_MQTTimport_tasks() { +static void schedule_all_MQTTimport_tasks() { constexpr pluginID_t PLUGIN_MQTT_IMPORT(PLUGIN_ID_MQTT_IMPORT); deviceIndex_t DeviceIndex = getDeviceIndex(PLUGIN_MQTT_IMPORT); // Check if P037_MQTTimport is present in the build @@ -566,7 +566,7 @@ void logTimerStatistics() { /********************************************************************************************\ Clean up all before going to sleep or reboot. \*********************************************************************************************/ -void flushAndDisconnectAllClients() { +static void flushAndDisconnectAllClients() { if (anyControllerEnabled()) { #if FEATURE_MQTT bool mqttControllerEnabled = validControllerIndex(firstEnabledMQTT_ControllerIndex()); diff --git a/src/src/Helpers/PeriodicalActions.h b/src/src/Helpers/PeriodicalActions.h index 35ff5f728..0ae3fdae9 100644 --- a/src/src/Helpers/PeriodicalActions.h +++ b/src/src/Helpers/PeriodicalActions.h @@ -31,7 +31,6 @@ void runEach30Seconds(); #if FEATURE_MQTT void scheduleNextMQTTdelayQueue(); -void schedule_all_MQTTimport_tasks(); void processMQTTdelayQueue(); diff --git a/src/src/Helpers/RulesHelper.cpp b/src/src/Helpers/RulesHelper.cpp index 41b2ce958..18d96294c 100644 --- a/src/src/Helpers/RulesHelper.cpp +++ b/src/src/Helpers/RulesHelper.cpp @@ -10,7 +10,7 @@ Test for common mistake Return true if mistake was found (and corrected) \*********************************************************************************************/ -bool rules_replace_common_mistakes(const String& from, const String& to, String& line) +static bool rules_replace_common_mistakes(const String& from, const String& to, String& line) { if (line.indexOf(from) == -1) { return false; // Nothing replaced @@ -37,7 +37,7 @@ bool rules_replace_common_mistakes(const String& from, const String& to, String& Check for common mistakes Return true if nothing strange found \*********************************************************************************************/ -bool check_rules_line_user_errors(String& line) +static bool check_rules_line_user_errors(String& line) { bool res = true; @@ -56,7 +56,7 @@ bool check_rules_line_user_errors(String& line) Strip comment from the line. Return true when comment was stripped. \*********************************************************************************************/ -bool rules_strip_trailing_comments(String& line) +static bool rules_strip_trailing_comments(String& line) { // Strip trailing comments int comment = line.indexOf(F("//")); diff --git a/src/src/Helpers/Rules_calculate.cpp b/src/src/Helpers/Rules_calculate.cpp index b590d5cc5..0deb0950f 100644 --- a/src/src/Helpers/Rules_calculate.cpp +++ b/src/src/Helpers/Rules_calculate.cpp @@ -8,6 +8,76 @@ #include "../Helpers/Numerical.h" #include "../Helpers/StringConverter.h" +static const __FlashStringHelper* toString(UnaryOperator op) +{ + switch (op) + { + case UnaryOperator::Not: + break; // No need to replace + case UnaryOperator::Log: + return F("log"); + case UnaryOperator::Ln: + return F("ln"); + case UnaryOperator::Abs: + return F("abs"); + case UnaryOperator::Exp: + return F("exp"); + case UnaryOperator::Sqrt: + return F("sqrt"); + case UnaryOperator::Sq: + return F("sq"); + case UnaryOperator::Round: + return F("round"); + case UnaryOperator::Sin: + return F("sin"); + case UnaryOperator::Sin_d: + return F("sin_d"); + case UnaryOperator::Cos: + return F("cos"); + case UnaryOperator::Cos_d: + return F("cos_d"); + case UnaryOperator::Tan: + return F("tan"); + case UnaryOperator::Tan_d: + return F("tan_d"); + case UnaryOperator::ArcSin: + return F("asin"); + case UnaryOperator::ArcSin_d: + return F("asin_d"); + case UnaryOperator::ArcCos: + return F("acos"); + case UnaryOperator::ArcCos_d: + return F("acos_d"); + case UnaryOperator::ArcTan: + return F("atan"); + case UnaryOperator::ArcTan_d: + return F("atan_d"); + case UnaryOperator::Map: + return F("map"); + case UnaryOperator::MapC: + return F("mapc"); + } + return F(""); +} + +#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + +static const __FlashStringHelper* toString(BinaryOperator op) +{ + switch (op) + { + case BinaryOperator::ArcTan2: + return F("atan2"); + case BinaryOperator::ArcTan2_d: + return F("atan2_d"); + case BinaryOperator::FMod: + return F("fmod"); + } + return F(""); +} + +#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + RulesCalculate_t::RulesCalculate_t() { for (int i = 0; i < STACK_SIZE; ++i) { @@ -18,9 +88,7 @@ RulesCalculate_t::RulesCalculate_t() { /********************************************************************************************\ Calculate function for simple expressions \*********************************************************************************************/ -bool isError(CalculateReturnCode returnCode) { - return returnCode != CalculateReturnCode::OK; -} +bool isError(CalculateReturnCode returnCode) { return returnCode != CalculateReturnCode::OK; } bool RulesCalculate_t::is_number(char oc, char c, char pc) { @@ -29,57 +97,59 @@ bool RulesCalculate_t::is_number(char oc, char c, char pc) (c == '.') || // A decimal point of a floating point number. ((oc == '0') && ((c == 'x') || (c == 'b'))) || // HEX (0x) or BIN (0b) prefixes. isxdigit(c) || // HEX digit also includes normal decimal numbers - ((is_operator(oc) || ('\0' == oc)) && (c == '-') - && (isdigit(pc) || ('\0' == pc))) // Beginning of a negative number after an operator or 'separator' and before a digit or end-of-digit. + ((is_operator(oc) || ('\0' == oc)) && (c == '-') + && (isdigit(pc) || ('\0' == pc))) // Beginning of a negative number after an operator or 'separator' and before a digit or + // end-of-digit. ; } -bool RulesCalculate_t::is_operator(char c) -{ - return c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == '%'; -} +bool RulesCalculate_t::is_operator(char c) { return c == '+' || c == '-' || c == '*' || c == '/' || c == '^' || c == '%'; } bool RulesCalculate_t::is_unary_operator(char c) { const UnaryOperator op = static_cast(c); - return (op == UnaryOperator::Not || ( - c >= static_cast(UnaryOperator::Log) && - c <= static_cast(UnaryOperator::ArcTan_d))); -/* - switch (op) { - case UnaryOperator::Not: - case UnaryOperator::Log: - case UnaryOperator::Ln: - case UnaryOperator::Abs: - case UnaryOperator::Exp: - case UnaryOperator::Sqrt: - case UnaryOperator::Sq: - case UnaryOperator::Round: - case UnaryOperator::Sin: - case UnaryOperator::Cos: - case UnaryOperator::Tan: - case UnaryOperator::ArcSin: - case UnaryOperator::ArcCos: - case UnaryOperator::ArcTan: - case UnaryOperator::Sin_d: - case UnaryOperator::Cos_d: - case UnaryOperator::Tan_d: - case UnaryOperator::ArcSin_d: - case UnaryOperator::ArcCos_d: - case UnaryOperator::ArcTan_d: - return true; - } - return false; - */ + + return op == UnaryOperator::Not || ( + c >= static_cast(UnaryOperator::Log) && + c <= static_cast(UnaryOperator::ArcTan_d)); + + /* + switch (op) { + case UnaryOperator::Not: + case UnaryOperator::Log: + case UnaryOperator::Ln: + case UnaryOperator::Abs: + case UnaryOperator::Exp: + case UnaryOperator::Sqrt: + case UnaryOperator::Sq: + case UnaryOperator::Round: + case UnaryOperator::Sin: + case UnaryOperator::Cos: + case UnaryOperator::Tan: + case UnaryOperator::ArcSin: + case UnaryOperator::ArcCos: + case UnaryOperator::ArcTan: + case UnaryOperator::Sin_d: + case UnaryOperator::Cos_d: + case UnaryOperator::Tan_d: + case UnaryOperator::ArcSin_d: + case UnaryOperator::ArcCos_d: + case UnaryOperator::ArcTan_d: + return true; + } + return false; + */ } #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + // binary = 2-argument functions bool RulesCalculate_t::is_binary_operator(char c) { - return (c >= static_cast(BinaryOperator::ArcTan2) && - c <= static_cast(BinaryOperator::FMod)); + return c >= static_cast(BinaryOperator::ArcTan2) && + c <= static_cast(BinaryOperator::FMod); } + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES // quinary = 5-argument functions @@ -127,13 +197,13 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_operator(char op, ESPEASY_RULES return static_cast(round(first)) % static_cast(round(second)); #else return static_cast(roundf(first)) % static_cast(roundf(second)); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case '^': #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return pow(first, second); #else return powf(first, second); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE default: return 0; } @@ -144,43 +214,44 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_unary_operator(char op, ESPEASY ESPEASY_RULES_FLOAT_TYPE ret{}; const UnaryOperator un_op = static_cast(op); - switch (un_op) { + switch (un_op) + { case UnaryOperator::Not: #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return essentiallyZero(round(first)) ? 1 : 0; #else return essentiallyZero(roundf(first)) ? 1 : 0; - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Log: #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return log10(first); #else return log10f(first); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Ln: #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return log(first); #else return logf(first); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Abs: #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return fabs(first); #else return fabsf(first); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Exp: #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return exp(first); #else return expf(first); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Sqrt: #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return sqrt(first); #else return sqrtf(first); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Sq: return first * first; case UnaryOperator::Round: @@ -188,7 +259,7 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_unary_operator(char op, ESPEASY return round(first); #else return roundf(first); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE default: break; } @@ -197,30 +268,31 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_unary_operator(char op, ESPEASY const bool useDegree = angleDegree(un_op); // First the trigonometric functions with angle as output - switch (un_op) { + switch (un_op) + { case UnaryOperator::ArcSin: case UnaryOperator::ArcSin_d: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE ret = asin(first); - #else + # else ret = asinf(first); - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return useDegree ? degrees(ret) : ret; case UnaryOperator::ArcCos: case UnaryOperator::ArcCos_d: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE ret = acos(first); - #else + # else ret = acosf(first); - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return useDegree ? degrees(ret) : ret; case UnaryOperator::ArcTan: case UnaryOperator::ArcTan_d: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE ret = atan(first); - #else + # else ret = atanf(first); - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return useDegree ? degrees(ret) : ret; default: break; @@ -231,34 +303,36 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_unary_operator(char op, ESPEASY first = radians(first); } - switch (un_op) { + switch (un_op) + { case UnaryOperator::Sin: case UnaryOperator::Sin_d: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return sin(first); - #else + # else return sinf(first); - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Cos: case UnaryOperator::Cos_d: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return cos(first); - #else + # else return cosf(first); - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE case UnaryOperator::Tan: case UnaryOperator::Tan_d: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return tan(first); - #else + # else return tanf(first); - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE default: break; } #else // if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES - switch (un_op) { + switch (un_op) + { case UnaryOperator::Sin: case UnaryOperator::Sin_d: case UnaryOperator::Cos: @@ -281,33 +355,37 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_unary_operator(char op, ESPEASY } #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_binary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first, ESPEASY_RULES_FLOAT_TYPE second) { ESPEASY_RULES_FLOAT_TYPE ret{}; - const BinaryOperator bin_op = static_cast(op); - + const BinaryOperator bin_op = static_cast(op); + const bool useDegree = angleDegree(bin_op); - switch (bin_op) { + + switch (bin_op) + { case BinaryOperator::ArcTan2: case BinaryOperator::ArcTan2_d: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE ret = atan2(first, second); - #else + # else ret = atan2f(first, second); - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return useDegree ? degrees(ret) : ret; case BinaryOperator::FMod: - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return fmod(first, second); - #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE return fmodf(first, second); - #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } return ret; } + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES -ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_quinary_operator(char op, +ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_quinary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first, ESPEASY_RULES_FLOAT_TYPE second, ESPEASY_RULES_FLOAT_TYPE third, @@ -317,11 +395,11 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_quinary_operator(char op, ESPEASY_RULES_FLOAT_TYPE ret{}; const UnaryOperator qu_op = static_cast(op); - if (UnaryOperator::Map == qu_op || UnaryOperator::MapC == qu_op) { + if ((UnaryOperator::Map == qu_op) || (UnaryOperator::MapC == qu_op)) { // Clamp the result if the operator is MapC if (qu_op == UnaryOperator::MapC) { - first = constrain(first, min(second, third), max(second, third));; + first = constrain(first, min(second, third), max(second, third)); } ret = mapADCtoFloat(first, second, third, fourth, fifth); } @@ -351,28 +429,31 @@ CalculateReturnCode RulesCalculate_t::RPNCalculate(char *token) ESPEASY_RULES_FLOAT_TYPE first = pop(); ret = push(apply_operator(token[0], first, second)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate operator %c: 1: %.4f 2: %.4f"), token[0], first, second)); -// FIXME TD-er: Regardless whether it is an error, all code paths return ret; -// if (isError(ret)) { return ret; } + // FIXME TD-er: Regardless whether it is an error, all code paths return ret; + // if (isError(ret)) { return ret; } } else if (is_unary_operator(token[0]) && (token[1] == 0)) { ESPEASY_RULES_FLOAT_TYPE first = pop(); ret = push(apply_unary_operator(token[0], first)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate unary %d: 1: %.4f"), token[0], first)); -// FIXME TD-er: Regardless whether it is an error, all code paths return ret; -// if (isError(ret)) { return ret; } + // FIXME TD-er: Regardless whether it is an error, all code paths return ret; + // if (isError(ret)) { return ret; } #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES } else if (is_binary_operator(token[0]) && (token[1] == 0)) { const ESPEASY_RULES_FLOAT_TYPE second = pop(); const ESPEASY_RULES_FLOAT_TYPE first = pop(); - + ret = push(apply_binary_operator(token[0], first, second)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate binary %d: 1: %.4f 2: %.4f"), token[0], first, second)); - + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES } else if (is_quinary_operator(token[0]) && (token[1] == 0)) { @@ -383,21 +464,24 @@ CalculateReturnCode RulesCalculate_t::RPNCalculate(char *token) ESPEASY_RULES_FLOAT_TYPE first = pop(); ret = push(apply_quinary_operator(token[0], first, second, third, fourth, fifth)); - // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate quinary %d: 1: %.4f 2: %.4f 3: %.4f 4: %.4f 5: %.4f"), token[0], first, second, third, fourth, fifth)); + + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate quinary %d: 1: %.4f 2: %.4f 3: %.4f 4: %.4f 5: %.4f"), token[0], first, second, + // third, fourth, fifth)); } else { // Fetch next if there is any ESPEASY_RULES_FLOAT_TYPE value{}; + if (validDoubleFromString(token, value)) { - // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate push value: %.4f token: %s"), value, token)); - // } else { - // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate unknown token: %s"), token)); + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate push value: %.4f token: %s"), value, token)); + // } else { + // addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate unknown token: %s"), token)); } ret = push(value); // If it is a value, push to the stack -// FIXME TD-er: Regardless whether it is an error, all code paths return ret; -// if (isError(ret)) { return ret; } + // FIXME TD-er: Regardless whether it is an error, all code paths return ret; + // if (isError(ret)) { return ret; } } return ret; @@ -431,27 +515,28 @@ int RulesCalculate_t::op_preced(const char c) bool RulesCalculate_t::op_left_assoc(const char c) { - if (is_operator(c)) { return true; // left to right + if (is_operator(c)) { return true; // left to right } -/* - // FIXME TD-er: Disabled the check as the return value is false anyway. - if (is_unary_operator(c)) { return false; // right to left - } - */ + + /* + // FIXME TD-er: Disabled the check as the return value is false anyway. + if (is_unary_operator(c)) { return false; // right to left + } + */ return false; } /* unused: -unsigned int RulesCalculate_t::op_arg_count(const char c) -{ - if (is_unary_operator(c)) { return 1; } + unsigned int RulesCalculate_t::op_arg_count(const char c) + { + if (is_unary_operator(c)) { return 1; } - if (is_operator(c)) { return 2; } + if (is_operator(c)) { return 2; } - if (is_quinary_operator(c)) { return 5; } - return 0; -} -*/ + if (is_quinary_operator(c)) { return 5; } + return 0; + } + */ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RULES_FLOAT_TYPE *result) { @@ -492,15 +577,19 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL { // Get peek-ahead char pcpos = strpos + 1; - pc = *pcpos; + pc = *pcpos; + while ((pcpos < strend) && (' ' == pc)) { ++pcpos; pc = *pcpos; } - // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate is_number: oc: %d c: %d/%c pc: %d token: %s input: %s sl: %u"), oc, c, c, pc, token, input, sl)); + + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate is_number: oc: %d c: %d/%c pc: %d token: %s input: %s sl: %u"), oc, c, c, pc, + // token, input, sl)); if ((pcpos >= strend)) { pc = '\0'; } + // If the token is a number (identifier), then add it to the token queue. if (is_number(oc, c, pc)) { @@ -581,13 +670,16 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL { bool pe = false; sc = '\0'; - // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate stack content 0x%s"), formatToHex_array(reinterpret_cast(stack), sl + 1).c_str())); + + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate stack content 0x%s"), formatToHex_array(reinterpret_cast(stack), + // sl + 1).c_str())); // Until the token at the top of the stack is a left parenthesis, // pop operators off the stack onto the token queue while (sl < OPERATOR_STACK_SIZE) { *(TokenPos) = 0; // Mark end of token string + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate popping stack sl: %u token: %s sc: %d"), sl, token, sc)); if (sc == '(') { // const auto first = pop(); // Get last value from stack @@ -595,6 +687,7 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate at ( last value: %.4f sl: %u"), first, sl)); } else { error = RPNCalculate(token); + // const auto first = pop(); // Get last value from stack // push(first); // push back // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate last value on stack: %.4f sl: %u"), first, sl)); @@ -607,6 +700,7 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL if (sl > 0) { sc = stack[sl - 1]; } + if (pe || (sl == 0)) { break; } @@ -614,8 +708,10 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL if (sc == '(') { pe = true; + if (sl > 1) { sc = stack[sl - 2]; + if (is_operator(sc)) { // Not a function call // Don't touch } else if (is_unary_operator(sc) || @@ -625,9 +721,9 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL is_quinary_operator(sc)) { // Function call, so process the function too *TokenPos = sc; ++TokenPos; - stack[sl - 2] = '\0'; // Don't process again on stack wind-down + stack[sl - 2] = '\0'; // Don't process again on stack wind-down } else { - sc = '\0'; // Reset + sc = '\0'; // Reset } } } @@ -635,9 +731,11 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL { *TokenPos = sc; ++TokenPos; + if (sl > 0) { sl--; } + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate next token: %s sl: %u sc: %d"), token, sl, sc)); } } @@ -665,7 +763,8 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL ++strpos; } - // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate final stack content 0x%s"), formatToHex_array(reinterpret_cast(stack), sl + 1).c_str())); + // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate final stack content 0x%s"), formatToHex_array(reinterpret_cast(stack), + // sl + 1).c_str())); // When there are no more tokens to read: // While there are still operator tokens in the stack: while (sl > 0) @@ -678,8 +777,9 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL *(TokenPos) = 0; // Mark end of token string // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate closing up stack sl: %u token: %s sc: %d"), sl, token, sc)); - error = RPNCalculate(token); - TokenPos = token; + error = RPNCalculate(token); + TokenPos = token; + // const auto first = pop(); // Get last value from stack // push(first); // push back // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate closing, last value on stack: %.4f sl: %u"), first, sl)); @@ -693,6 +793,7 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL *(TokenPos) = 0; // Mark end of token string error = RPNCalculate(token); TokenPos = token; + // const auto first = pop(); // Get last value from stack // push(first); // push back // addLog(LOG_LEVEL_INFO, strformat(F("doCalculate final value on stack: %.4f sl: %u"), first, sl)); @@ -721,6 +822,7 @@ void preProcessReplace(String& input, UnaryOperator op) { } #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + void preProcessReplace(String& input, BinaryOperator op) { String find = toString(op); @@ -731,11 +833,13 @@ void preProcessReplace(String& input, BinaryOperator op) { input.replace(find, replace); } + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES bool angleDegree(UnaryOperator op) { - switch (op) { + switch (op) + { case UnaryOperator::Sin_d: case UnaryOperator::Cos_d: case UnaryOperator::Tan_d: @@ -750,9 +854,11 @@ bool angleDegree(UnaryOperator op) } #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES + bool angleDegree(BinaryOperator op) { return BinaryOperator::ArcTan2_d == op; + // switch (op) // Future extension for 4 or more BinaryOperator options and multiple _d options // { // case BinaryOperator::ArcTan2_d: @@ -763,72 +869,7 @@ bool angleDegree(BinaryOperator op) // } // return false; } -#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES -const __FlashStringHelper* toString(UnaryOperator op) -{ - switch (op) { - case UnaryOperator::Not: - break; // No need to replace - case UnaryOperator::Log: - return F("log"); - case UnaryOperator::Ln: - return F("ln"); - case UnaryOperator::Abs: - return F("abs"); - case UnaryOperator::Exp: - return F("exp"); - case UnaryOperator::Sqrt: - return F("sqrt"); - case UnaryOperator::Sq: - return F("sq"); - case UnaryOperator::Round: - return F("round"); - case UnaryOperator::Sin: - return F("sin"); - case UnaryOperator::Sin_d: - return F("sin_d"); - case UnaryOperator::Cos: - return F("cos"); - case UnaryOperator::Cos_d: - return F("cos_d"); - case UnaryOperator::Tan: - return F("tan"); - case UnaryOperator::Tan_d: - return F("tan_d"); - case UnaryOperator::ArcSin: - return F("asin"); - case UnaryOperator::ArcSin_d: - return F("asin_d"); - case UnaryOperator::ArcCos: - return F("acos"); - case UnaryOperator::ArcCos_d: - return F("acos_d"); - case UnaryOperator::ArcTan: - return F("atan"); - case UnaryOperator::ArcTan_d: - return F("atan_d"); - case UnaryOperator::Map: - return F("map"); - case UnaryOperator::MapC: - return F("mapc"); - } - return F(""); -} - -#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES -const __FlashStringHelper* toString(BinaryOperator op) -{ - switch (op) { - case BinaryOperator::ArcTan2: - return F("atan2"); - case BinaryOperator::ArcTan2_d: - return F("atan2_d"); - case BinaryOperator::FMod: - return F("fmod"); - } - return F(""); -} #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES String RulesCalculate_t::preProces(const String& input) @@ -837,33 +878,33 @@ String RulesCalculate_t::preProces(const String& input) const UnaryOperator operators[] = { UnaryOperator::Not - ,UnaryOperator::Log - ,UnaryOperator::Ln - ,UnaryOperator::Abs - ,UnaryOperator::Exp - ,UnaryOperator::Sqrt - ,UnaryOperator::Sq - ,UnaryOperator::Round + , UnaryOperator::Log + , UnaryOperator::Ln + , UnaryOperator::Abs + , UnaryOperator::Exp + , UnaryOperator::Sqrt + , UnaryOperator::Sq + , UnaryOperator::Round #if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES // Try the "arc" functions first or else "sin" is already replaced when "asin" is tried. - ,UnaryOperator::ArcSin - ,UnaryOperator::ArcSin_d - ,UnaryOperator::Sin - ,UnaryOperator::Sin_d + , UnaryOperator::ArcSin + , UnaryOperator::ArcSin_d + , UnaryOperator::Sin + , UnaryOperator::Sin_d - ,UnaryOperator::ArcCos - ,UnaryOperator::ArcCos_d - ,UnaryOperator::Cos - ,UnaryOperator::Cos_d + , UnaryOperator::ArcCos + , UnaryOperator::ArcCos_d + , UnaryOperator::Cos + , UnaryOperator::Cos_d - ,UnaryOperator::ArcTan - ,UnaryOperator::ArcTan_d - ,UnaryOperator::Tan - ,UnaryOperator::Tan_d + , UnaryOperator::ArcTan + , UnaryOperator::ArcTan_d + , UnaryOperator::Tan + , UnaryOperator::Tan_d #endif // if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES - ,UnaryOperator::Map // FIXME ?Move to new QuintaryOperator enum? - ,UnaryOperator::MapC + , UnaryOperator::Map // FIXME ?Move to new QuintaryOperator enum? + , UnaryOperator::MapC }; @@ -872,11 +913,20 @@ String RulesCalculate_t::preProces(const String& input) for (size_t i = 0; i < nrOperators; ++i) { const UnaryOperator op = operators[i]; #if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES - if (op == UnaryOperator::ArcSin && preprocessed.indexOf(F("sin")) == -1) i += 3; - else if (op == UnaryOperator::ArcCos && preprocessed.indexOf(F("cos")) == -1) i += 3; - else if (op == UnaryOperator::ArcTan && preprocessed.indexOf(F("tan")) == -1) i += 3; - else -#endif + + if ((op == UnaryOperator::ArcSin) && + (preprocessed.indexOf(F("sin")) == -1)) { i += 3; + } + else if ((op == UnaryOperator::ArcCos) && + (preprocessed.indexOf(F("cos")) == + -1)) { i += 3; + } + else if ((op == UnaryOperator::ArcTan) && + (preprocessed.indexOf(F("tan")) == + -1)) { i += 3; + } + else +#endif // if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES { preProcessReplace(preprocessed, op); } @@ -891,6 +941,7 @@ String RulesCalculate_t::preProces(const String& input) }; constexpr size_t nrOperators2 = NR_ELEMENTS(operators2); + for (size_t i = 0; i < nrOperators2; ++i) { const BinaryOperator op = operators2[i]; preProcessReplace(preprocessed, op); @@ -899,4 +950,3 @@ String RulesCalculate_t::preProces(const String& input) return preprocessed; } - diff --git a/src/src/Helpers/Rules_calculate.h b/src/src/Helpers/Rules_calculate.h index 7b7249be8..cbce8d1d4 100644 --- a/src/src/Helpers/Rules_calculate.h +++ b/src/src/Helpers/Rules_calculate.h @@ -11,19 +11,20 @@ #define TOKEN_MAX 20 #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -#define TOKEN_LENGTH 40 +# define TOKEN_LENGTH 40 #else -#define TOKEN_LENGTH 25 -#endif +# define TOKEN_LENGTH 25 +#endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE #define OPERATOR_STACK_SIZE 32 -enum class CalculateReturnCode : uint8_t{ +enum class CalculateReturnCode : uint8_t { OK = 0u, ERROR_STACK_OVERFLOW = 1u, ERROR_BAD_OPERATOR = 2u, ERROR_PARENTHESES_MISMATCHED = 3u, ERROR_UNKNOWN_TOKEN = 4u, ERROR_TOKEN_LENGTH_EXCEEDED = 5u + }; bool isError(CalculateReturnCode returnCode); @@ -36,80 +37,82 @@ bool isError(CalculateReturnCode returnCode); enum class UnaryOperator : uint8_t { Not = '!', Log = 192u, // Start at some ASCII code we don't expect in the rules. - Ln, // Natural logarithm - Abs, // Absolute value - Exp, // exponential value, e^x - Sqrt, // Square Root - Sq, // Square, x^2 - Round, // Rounds to the nearest integer, but rounds halfway cases away from zero (instead of to the nearest even integer). - Sin, // Sine (radian) - Sin_d, // Sine (degree) - Cos, // Cosine (radian) - Cos_d, // Cosine (degree) - Tan, // Tangent (radian) - Tan_d, // Tangent (degree) - ArcSin, // Arc Sine (radian) - ArcSin_d, // Arc Sine (degree) - ArcCos, // Arc Cosine (radian) - ArcCos_d, // Arc Cosine (degree) - ArcTan, // Arc Tangent (radian) - ArcTan_d, // Arc Tangent (degree) - Map, // Map (value, lowFrom, highFrom, lowTo, highTo) (not really unary...) - MapC, // Map (value, lowFrom, highFrom, lowTo, highTo) and clamp to lowTo/highTo + Ln, // Natural logarithm + Abs, // Absolute value + Exp, // exponential value, e^x + Sqrt, // Square Root + Sq, // Square, x^2 + Round, // Rounds to the nearest integer, but rounds halfway cases away from zero (instead of to the nearest even integer). + Sin, // Sine (radian) + Sin_d, // Sine (degree) + Cos, // Cosine (radian) + Cos_d, // Cosine (degree) + Tan, // Tangent (radian) + Tan_d, // Tangent (degree) + ArcSin, // Arc Sine (radian) + ArcSin_d, // Arc Sine (degree) + ArcCos, // Arc Cosine (radian) + ArcCos_d, // Arc Cosine (degree) + ArcTan, // Arc Tangent (radian) + ArcTan_d, // Arc Tangent (degree) + Map, // Map (value, lowFrom, highFrom, lowTo, highTo) (not really unary...) + MapC, // Map (value, lowFrom, highFrom, lowTo, highTo) and clamp to lowTo/highTo + }; #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES enum class BinaryOperator : uint8_t { - - ArcTan2 = 220u, // Arc Tangent 2 (radian) - ArcTan2_d, // Arc Tangent 2 (degrees) - FMod, // Float-modulo + + ArcTan2 = 220u, // Arc Tangent 2 (radian) + ArcTan2_d, // Arc Tangent 2 (degrees) + FMod, // Float-modulo + }; + #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES -void preProcessReplace(String & input, - UnaryOperator op); -bool angleDegree(UnaryOperator op); -const __FlashStringHelper* toString(UnaryOperator op); +void preProcessReplace(String & input, + UnaryOperator op); +bool angleDegree(UnaryOperator op); #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES -bool angleDegree(BinaryOperator op); -const __FlashStringHelper* toString(BinaryOperator op); +bool angleDegree(BinaryOperator op); #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES -class RulesCalculate_t { +class RulesCalculate_t +{ private: ESPEASY_RULES_FLOAT_TYPE globalstack[STACK_SIZE]{}; - ESPEASY_RULES_FLOAT_TYPE *sp = globalstack - 1; + ESPEASY_RULES_FLOAT_TYPE *sp = globalstack - 1; const ESPEASY_RULES_FLOAT_TYPE *sp_max = &globalstack[STACK_SIZE - 1]; // Check if it matches part of a number (identifier) // @param oc Previous character // @param c Current character - bool is_number(char oc, - char c, - char pc); + static bool is_number(char oc, + char c, + char pc); - bool is_operator(char c); + static bool is_operator(char c); - bool is_unary_operator(char c); + static bool is_unary_operator(char c); #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES - bool is_binary_operator(char c); + static bool is_binary_operator(char c); #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES - bool is_quinary_operator(char c); + static bool is_quinary_operator(char c); - CalculateReturnCode push(ESPEASY_RULES_FLOAT_TYPE value); + CalculateReturnCode push(ESPEASY_RULES_FLOAT_TYPE value); - ESPEASY_RULES_FLOAT_TYPE pop(); + ESPEASY_RULES_FLOAT_TYPE pop(); - ESPEASY_RULES_FLOAT_TYPE apply_operator(char op, - ESPEASY_RULES_FLOAT_TYPE first, - ESPEASY_RULES_FLOAT_TYPE second); + ESPEASY_RULES_FLOAT_TYPE apply_operator(char op, + ESPEASY_RULES_FLOAT_TYPE first, + ESPEASY_RULES_FLOAT_TYPE second); - ESPEASY_RULES_FLOAT_TYPE apply_unary_operator(char op, - ESPEASY_RULES_FLOAT_TYPE first); + ESPEASY_RULES_FLOAT_TYPE apply_unary_operator(char op, + ESPEASY_RULES_FLOAT_TYPE first); #if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES ESPEASY_RULES_FLOAT_TYPE apply_binary_operator(char op, @@ -117,7 +120,7 @@ private: ESPEASY_RULES_FLOAT_TYPE second); #endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES - ESPEASY_RULES_FLOAT_TYPE apply_quinary_operator(char op, + ESPEASY_RULES_FLOAT_TYPE apply_quinary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first, ESPEASY_RULES_FLOAT_TYPE second, ESPEASY_RULES_FLOAT_TYPE third, @@ -134,9 +137,9 @@ private: // 3 ^ left to right // 2 * / % left to right // 1 + - left to right - int op_preced(const char c); + static int op_preced(const char c); - bool op_left_assoc(const char c); + static bool op_left_assoc(const char c); // unused: unsigned int op_arg_count(const char c); @@ -144,14 +147,13 @@ public: RulesCalculate_t(); - CalculateReturnCode doCalculate(const char *input, - ESPEASY_RULES_FLOAT_TYPE *result); + CalculateReturnCode doCalculate(const char *input, + ESPEASY_RULES_FLOAT_TYPE *result); // Try to replace multi byte operators with single character ones. // For example log, sin, cos, tan. static String preProces(const String& input); -}; - +}; // class RulesCalculate_t #endif // ifndef HELPERS_RULES_CALCULATE_H diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index 33e90a1c1..a8ef03b2a 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -9,123 +9,104 @@ #include "../ESPEasyCore/ESPEasy_Log.h" #include "../Globals/Cache.h" -//#include "../Globals/CRCValues.h" + +// #include "../Globals/CRCValues.h" #include "../Globals/Device.h" #include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" -//#include "../Globals/ESPEasy_time.h" -//#include "../Globals/MQTT.h" + +// #include "../Globals/ESPEasy_time.h" +// #include "../Globals/MQTT.h" #include "../Globals/Plugins.h" #include "../Globals/Settings.h" #include "../Helpers/Convert.h" -//#include "../Helpers/ESPEasy_Storage.h" + +// #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" #include "../Helpers/Numerical.h" #include "../Helpers/StringGenerator_System.h" #include "../Helpers/StringParser.h" #include "../Helpers/SystemVariables.h" -//#include "../Helpers/_Plugin_SensorTypeHelper.h" + +// #include "../Helpers/_Plugin_SensorTypeHelper.h" // -V::569 unsigned int count_newlines(const String& str) { - unsigned int count = 0; + unsigned int count = 0; const size_t strlength = str.length(); - size_t pos = 0; + size_t pos = 0; + while (pos < strlength) { - if (str[pos] == '\n') ++count; + if (str[pos] == '\n') { ++count; } ++pos; } return count; } -String concat(const __FlashStringHelper * str, const String &val) { +String concat(const __FlashStringHelper *str, const String& val) { String res; + reserve_special(res, strlen_P((PGM_P)str) + val.length()); res.concat(str); res.concat(val); return res; } -String concat(const __FlashStringHelper * str, const __FlashStringHelper *val) { - return concat(str, String(val)); -} +String concat(const __FlashStringHelper *str, const __FlashStringHelper *val) { return concat(str, String(val)); } -String concat(const __FlashStringHelper * str, const char* val) { +String concat(const __FlashStringHelper *str, const char*val) { String res; - res = str; + + res = str; res += val; return res; } -String concat(const __FlashStringHelper * str, int val) -{ - return concat(str, String(val)); -} +String concat(const __FlashStringHelper *str, int val) { return concat(str, String(val)); } -String concat(const __FlashStringHelper * str, const char& c) -{ - return concat(String(str), c); -} +String concat(const __FlashStringHelper *str, const char& c) { return concat(String(str), c); } -String concat(const __FlashStringHelper * str, const float& val) -{ - return concat(str, toString(val)); -} +String concat(const __FlashStringHelper *str, const float& val) { return concat(str, toString(val)); } -String concat(const __FlashStringHelper * str, const double& val) -{ - return concat(str, doubleToString(val)); -} +String concat(const __FlashStringHelper *str, const double& val) { return concat(str, doubleToString(val)); } #if defined(ESP32) && !defined(__riscv) -String concat(const __FlashStringHelper * str, const int32_t& val) -{ - return concat(str, String(val)); -} -#endif -String concat(const __FlashStringHelper * str, const uint32_t& val) -{ - return concat(str, String(val)); -} +String concat(const __FlashStringHelper *str, const int32_t& val) { return concat(str, String(val)); } + +#endif // if defined(ESP32) && !defined(__riscv) + +String concat(const __FlashStringHelper *str, const uint32_t& val) { return concat(str, String(val)); } #if defined(ESP32) && !defined(__riscv) -String concat(const __FlashStringHelper * str, const size_t& val) -{ - return concat(str, String(val)); -} -#endif -String concat(const char& c, const __FlashStringHelper *val) -{ - return concat(String(c), val); -} +String concat(const __FlashStringHelper *str, const size_t& val) { return concat(str, String(val)); } -String concat(int i, const __FlashStringHelper *val) -{ - return concat(String(i), val); -} +#endif // if defined(ESP32) && !defined(__riscv) -String concat(uint32_t i, const __FlashStringHelper *val) -{ - return concat(String(i), val); -} +String concat(const char& c, const __FlashStringHelper *val) { return concat(String(c), val); } -String concat(const String & str, const __FlashStringHelper *val) +String concat(int i, const __FlashStringHelper *val) { return concat(String(i), val); } + +String concat(uint32_t i, const __FlashStringHelper *val) { return concat(String(i), val); } + +String concat(const String& str, const __FlashStringHelper *val) { String res; + reserve_special(res, str.length() + strlen_P((PGM_P)val)); res.concat(str); res.concat(val); return res; } -String concat(const String & str, const String & val) +String concat(const String& str, const String& val) { String res; + reserve_special(res, str.length() + val.length()); res.concat(str); res.concat(val); @@ -133,48 +114,54 @@ String concat(const String & str, const String & val) return res; } -String concat(const String & str, const char* val) +String concat(const String& str, const char*val) { String res; + reserve_special(res, str.length() + strlen_P((PGM_P)val)); res.concat(str); res.concat(val); return res; } -String concat(const String &val, const char& c) +String concat(const String& val, const char& c) { String res; - res = val; + + res = val; res += c; return res; } -String concat(String &&val, const char& c) +String concat(String&& val, const char& c) { String res(std::move(val)); + res += c; return res; } -String concat(String &&str, const __FlashStringHelper * val) +String concat(String&& str, const __FlashStringHelper *val) { String res(std::move(str)); + res += val; return res; } -String concat(const __FlashStringHelper *str, String && val) +String concat(const __FlashStringHelper *str, String&& val) { String res; + res += str; res += std::move(val); return res; } -String concat(const char& str, const String &val) +String concat(const char& str, const String& val) { String res(str); + reserve_special(res, res.length() + val.length()); res.concat(val); return res; @@ -183,44 +170,30 @@ String concat(const char& str, const String &val) bool remove(String& str, const String& toRemove) { const size_t origSize = str.length(); + str.replace(toRemove, EMPTY_STRING); return origSize > str.length(); } -bool remove(String& str, const __FlashStringHelper * toRemove) -{ - return remove(str, String(toRemove)); -} +bool remove(String& str, const __FlashStringHelper *toRemove) { return remove(str, String(toRemove)); } -bool remove(String& str, const char& toRemove) -{ - return remove(str, String(toRemove)); -} +bool remove(String& str, const char& toRemove) { return remove(str, String(toRemove)); } -bool removeSpace(String& str) -{ - return remove(str, ' '); -} +bool removeSpace(String& str) { return remove(str, ' '); } -bool equals(const String& str, const __FlashStringHelper * f_str) { - return str.equals(String(f_str)); -} +bool equals(const String& str, const __FlashStringHelper *f_str) { return str.equals(String(f_str)); } -bool equals(const String& str, const char& c) { - return str.length() == 1 && str[0] == c; -} +bool equals(const String& str, const char& c) { return str.length() == 1 && str[0] == c; } -void move_special(String& dest, String&& source) { - move_special(dest, std::move(source), 64); -} +void move_special(String& dest, String&& source) { move_special(dest, std::move(source), 64); } void move_special(String& dest, String&& source, size_t strLengthThreshold) { // Only try to store larger strings here as those tend to be kept for a longer period. - if ((source.length() >= strLengthThreshold) + if ((source.length() >= strLengthThreshold) #ifdef USE_SECOND_HEAP && mmu_is_dram(&(source[0])) #endif - ) { + ) { // The string was not allocated on the 2nd heap, so copy instead of move if (!reserve_special(dest, source.length())) { // Could not allocate on 2nd heap or PSRAM, so just move existing string @@ -232,41 +205,35 @@ void move_special(String& dest, String&& source, size_t strLengthThreshold) { // Try to avoid keeping reserved memory on empty strings // So just copy the data, not move dest = source; - free_string(source); + free_string(source); } -String move_special(String&& source) { - return move_special(std::move(source), 64); -} +String move_special(String&& source) { return move_special(std::move(source), 64); } String move_special(String&& source, size_t strLengthThreshold) { String dest; + move_special(dest, std::move(source), strLengthThreshold); return dest; } +bool reserve_special(String& str, size_t size) { return String_reserve_special(str, size); } -bool reserve_special(String& str, size_t size) { - return String_reserve_special(str, size); -} - -bool reserve_special(String& str, size_t size, size_t strLengthThreshold) -{ - return String_reserve_special(str, size, strLengthThreshold); -} +bool reserve_special(String& str, size_t size, size_t strLengthThreshold) { return String_reserve_special(str, size, strLengthThreshold); } void free_string(String& str) { // This is a call specifically tailored to what is done in: // void String::move(String &rhs) #if defined(ESP32) || defined(CORE_POST_3_0_0) + // Use current implementation of String::copy as this // invalidates and thus deallocates current buffer -// str = (const char*)nullptr; + // str = (const char*)nullptr; str = String(); // No idea why this is needed, without it, some ESP32's may bootloop -#else +#else // if defined(ESP32) || defined(CORE_POST_3_0_0) str = String(); -#endif +#endif // if defined(ESP32) || defined(CORE_POST_3_0_0) } /********************************************************************************************\ @@ -280,72 +247,79 @@ String strformat(const String& format, ...) va_list arg; va_start(arg, format); // variable args start after parameter 'format' static char temp[64]; - char* buffer = temp; - int len = vsnprintf_P(temp, sizeof(temp), format.c_str(), arg); + char*buffer = temp; + int len = vsnprintf_P(temp, sizeof(temp), format.c_str(), arg); va_end(arg); + if (len > static_cast(sizeof(temp) - 1)) { - buffer = new (std::nothrow) char[len + 1]; - if (!buffer) { - return res; - } - va_start(arg, format); - vsnprintf_P(buffer, len + 1, format.c_str(), arg); - va_end(arg); + buffer = new (std::nothrow) char[len + 1]; + + if (!buffer) { + return res; + } + va_start(arg, format); + vsnprintf_P(buffer, len + 1, format.c_str(), arg); + va_end(arg); } + if (len > 64) { reserve_special(res, len + 1); } res = buffer; + if (buffer != temp) { - delete[] buffer; + delete[] buffer; } } + return res; } -String strformat(const __FlashStringHelper * format, ...) +String strformat(const __FlashStringHelper *format, ...) { String res; { va_list arg; va_start(arg, format); // variable args start after parameter 'format' static char temp[64]; - char* buffer = temp; - int len = vsnprintf_P(temp, sizeof(temp), (PGM_P)format, arg); + char*buffer = temp; + int len = vsnprintf_P(temp, sizeof(temp), (PGM_P)format, arg); va_end(arg); + if (len > static_cast(sizeof(temp) - 1)) { - buffer = new (std::nothrow) char[len + 1]; - if (!buffer) { - return res; - } - va_start(arg, format); - vsnprintf_P(buffer, len + 1, (PGM_P)format, arg); - va_end(arg); + buffer = new (std::nothrow) char[len + 1]; + + if (!buffer) { + return res; + } + va_start(arg, format); + vsnprintf_P(buffer, len + 1, (PGM_P)format, arg); + va_end(arg); } + if (len > 64) { reserve_special(res, len + 1); } res = buffer; + if (buffer != temp) { - delete[] buffer; + delete[] buffer; } } + return res; } - - /********************************************************************************************\ Convert a char string to IP uint8_t array \*********************************************************************************************/ bool isIP(const String& string) { IPAddress tmpip; - return (tmpip.fromString(string)); + + return tmpip.fromString(string); } -bool str2ip(const String& string, uint8_t *IP) { - return str2ip(string.c_str(), IP); -} +bool str2ip(const String& string, uint8_t *IP) { return str2ip(string.c_str(), IP); } bool str2ip(const char *string, uint8_t *IP) { @@ -363,69 +337,66 @@ bool str2ip(const char *string, uint8_t *IP) String formatIP(const IPAddress& ip, bool includeZone) { #ifdef ESP8266 -#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) +# if defined(ARDUINO_ESP8266_RELEASE_2_3_0) IPAddress tmp(ip); return tmp.toString(); -#else // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) +# else // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) return ip.toString(); -#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) -#endif +# endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) +#endif // ifdef ESP8266 #ifdef ESP32 -/* - #if LWIP_IPV6 - if (ip.isAny()) { - IPAddress tmp; - tmp.setV4(); - return tmp.toString(); - } - #endif -*/ -#if FEATURE_USE_IPV6 + + /* + #if LWIP_IPV6 + if (ip.isAny()) { + IPAddress tmp; + tmp.setV4(); + return tmp.toString(); + } + #endif + */ +# if FEATURE_USE_IPV6 return ip.toString(includeZone); -#else +# else return ip.toString(); -#endif -#endif +# endif // if FEATURE_USE_IPV6 +#endif // ifdef ESP32 } -const __FlashStringHelper * boolToString(bool value) { - return value ? F("true") : F("false"); -} +const __FlashStringHelper* boolToString(bool value) { return value ? F("true") : F("false"); } /*********************************************************************************************\ Typical string replace functions. \*********************************************************************************************/ -void removeExtraNewLine(String& line) { +void removeExtraNewLine(String& line) { while (line.endsWith(F("\r\n\r\n"))) { line.remove(line.length() - 2); } } -void removeChar(String& line, char character) { - line.replace(String(character), EMPTY_STRING); -} +void removeChar(String& line, char character) { line.replace(String(character), EMPTY_STRING); } -void addNewLine(String& line) { - line += F("\r\n"); -} +void addNewLine(String& line) { line += F("\r\n"); } -size_t UTF8_charLength(uint8_t firstByte) { +static size_t UTF8_charLength(uint8_t firstByte) { if (firstByte <= 0x7f) { return 1; } + // First Byte Second Byte Third Byte Fourth Byte - // [0x00,0x7F] - // [0xC2,0xDF] [0x80,0xBF] - // 0xE0 [0xA0,0xBF] [0x80,0xBF] - // [0xE1,0xEC] [0x80,0xBF] [0x80,0xBF] - // 0xED [0x80,0x9F] [0x80,0xBF] - // [0xEE,0xEF] [0x80,0xBF] [0x80,0xBF] + // [0x00,0x7F] + // [0xC2,0xDF] [0x80,0xBF] + // 0xE0 [0xA0,0xBF] [0x80,0xBF] + // [0xE1,0xEC] [0x80,0xBF] [0x80,0xBF] + // 0xED [0x80,0x9F] [0x80,0xBF] + // [0xEE,0xEF] [0x80,0xBF] [0x80,0xBF] // 0xF0 [0x90,0xBF] [0x80,0xBF] [0x80,0xBF] // [0xF1,0xF3] [0x80,0xBF] [0x80,0xBF] [0x80,0xBF] // 0xF4 [0x80,0x8F] [0x80,0xBF] [0x80,0xBF] // See: https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/ size_t charLength = 2; + if (firstByte > 0xEF) { charLength = 4; } else if (firstByte > 0xDF) { @@ -436,6 +407,7 @@ size_t UTF8_charLength(uint8_t firstByte) { void replaceUnicodeByChar(String& line, char replChar) { size_t pos = 0; + while (pos < line.length()) { const size_t charLength = UTF8_charLength((uint8_t)line[pos]); @@ -443,6 +415,7 @@ void replaceUnicodeByChar(String& line, char replChar) { // Is unicode char in UTF-8 format // Need to find how many characters we need to replace. const size_t charsLeft = line.length() - pos; + if (charsLeft >= charLength) { line.replace(line.substring(pos, pos + charLength), String(replChar)); } @@ -453,15 +426,17 @@ void replaceUnicodeByChar(String& line, char replChar) { void padToMinimumLength(String& line, uint32_t minimumLength, char padChar) { - if (minimumLength == 0 || line.length() >= minimumLength) return; + if ((minimumLength == 0) || (line.length() >= minimumLength)) { return; } line.reserve(minimumLength); - while (line.length() < minimumLength) line += padChar; + + while (line.length() < minimumLength) { line += padChar; + } } void prefixToMinimumLength(String& line, uint32_t minimumLength, char prefixChar) { - if (minimumLength == 0 || line.length() >= minimumLength) return; - + if ((minimumLength == 0) || (line.length() >= minimumLength)) { return; } + String prefix; padToMinimumLength(prefix, minimumLength - line.length(), prefixChar); line = prefix + line; @@ -471,7 +446,7 @@ void prefixToMinimumLength(String& line, uint32_t minimumLength, char prefixChar Format a value to the set number of decimals \*********************************************************************************************/ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCheck, bool& isvalid) { - if (event == nullptr) return EMPTY_STRING; + if (event == nullptr) { return EMPTY_STRING; } START_TIMER; isvalid = true; @@ -489,11 +464,12 @@ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCh tempEvent.deep_copy(event); tempEvent.idx = rel_index; PluginCall(PLUGIN_FORMAT_USERVAR, &tempEvent, result); + if (result.length() > 0) { return result; } } - + // Spent upto 400 usec till here const uint8_t valueCount = getValueCountForTask(event->TaskIndex); const Sensor_VType sensorType = event->getSensorType(); @@ -505,10 +481,10 @@ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCh if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLogMove(LOG_LEVEL_ERROR, strformat( - F("No sensor value for TaskIndex: %d varnumber: %d type: %s"), - event->TaskIndex + 1, - rel_index + 1, - String(getSensorTypeLabel(sensorType)).c_str())); + F("No sensor value for TaskIndex: %d varnumber: %d type: %s"), + event->TaskIndex + 1, + rel_index + 1, + String(getSensorTypeLabel(sensorType)).c_str())); } #endif // ifndef BUILD_NO_DEBUG return EMPTY_STRING; @@ -519,6 +495,7 @@ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCh } uint8_t nrDecimals = 0; + if (Device[DeviceIndex].configurableDecimals()) { nrDecimals = Cache.getTaskDeviceValueDecimals(event->TaskIndex, rel_index); } @@ -530,9 +507,9 @@ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCh if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { addLogMove(LOG_LEVEL_DEBUG, strformat( - F("Invalid float value for TaskIndex: %d varnumber: %d"), - event->TaskIndex + 1, - rel_index + 1)); + F("Invalid float value for TaskIndex: %d varnumber: %d"), + event->TaskIndex + 1, + rel_index + 1)); } #endif // ifndef BUILD_NO_DEBUG const float f{}; @@ -567,12 +544,10 @@ String formatUserVarNoCheck(struct EventStruct *event, uint8_t rel_index) return doFormatUserVar(event, rel_index, false, isvalid); } -String formatUserVar(struct EventStruct *event, uint8_t rel_index, bool& isvalid) -{ - return doFormatUserVar(event, rel_index, true, isvalid); -} +String formatUserVar(struct EventStruct *event, uint8_t rel_index, bool& isvalid) { return doFormatUserVar(event, rel_index, true, isvalid); } #if FEATURE_STRING_VARIABLES + String formatUserVarForPresentation(struct EventStruct *event, taskVarIndex_t varNr, bool & hasPresentation, @@ -580,12 +555,15 @@ String formatUserVarForPresentation(struct EventStruct *event, const deviceIndex_t DeviceIndex, String valueName) { const taskIndex_t taskIndex = event->TaskIndex; - String formula = Cache.getTaskDeviceFormula(taskIndex, varNr); + String formula = Cache.getTaskDeviceFormula(taskIndex, varNr); + hasPresentation = Cache.hasFormula(taskIndex, varNr) - && formula.startsWith(F(TASK_VALUE_PRESENTATION_PREFIX_STRING)); + && formula.startsWith(F(TASK_VALUE_PRESENTATION_PREFIX_STRING)); + if (hasPresentation) { formula.remove(0, 1); formula.replace(F("%value%"), value); + if (Cache.hasFormula_with_prevValue(taskIndex, varNr)) { event->sensorType = Device[DeviceIndex].VType; String dummy; @@ -598,11 +576,13 @@ String formatUserVarForPresentation(struct EventStruct *event, } else { String taskName = getTaskDeviceName(taskIndex); taskName.toLowerCase(); + if (valueName.isEmpty() && validTaskVarIndex(varNr)) { valueName = getTaskValueName(taskIndex, varNr); } valueName.toLowerCase(); String presentation = getCustomStringVar(strformat(F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str())); + if (!presentation.isEmpty()) { stripEscapeCharacters(presentation); presentation.replace(F("%value%"), value); @@ -613,6 +593,7 @@ String formatUserVarForPresentation(struct EventStruct *event, } return EMPTY_STRING; } + #endif // if FEATURE_STRING_VARIABLES String get_formatted_Controller_number(cpluginID_t cpluginID) { @@ -622,14 +603,12 @@ String get_formatted_Controller_number(cpluginID_t cpluginID) { return strformat(F("C%03d"), cpluginID); } -String get_formatted_Plugin_number(pluginID_t pluginID) -{ - return pluginID.toDisplayString(); -} +String get_formatted_Plugin_number(pluginID_t pluginID) { return pluginID.toDisplayString(); } String formatIntLeadingZeroes(int value, int nrDigits) { String res(value); + prefixToMinimumLength(res, nrDigits, '0'); return res; } @@ -637,6 +616,7 @@ String formatIntLeadingZeroes(int value, int nrDigits) String formatIntLeadingZeroes(const String& value, int nrDigits) { String res(value); + prefixToMinimumLength(res, nrDigits, '0'); return res; } @@ -644,18 +624,14 @@ String formatIntLeadingZeroes(const String& value, int nrDigits) /*********************************************************************************************\ Wrap a string with given pre- and postfix string. \*********************************************************************************************/ -String wrap_braces(const String& string) { - return wrap_String(string, '(', ')'); -} +String wrap_braces(const String& string) { return wrap_String(string, '(', ')'); } String wrap_String(const String& string, char wrap) { - if (stringWrappedWithChar(string, wrap)) return string; + if (stringWrappedWithChar(string, wrap)) { return string; } return wrap_String(string, wrap, wrap); } -String wrap_String(const String& string, char char1, char char2) { - return strformat(F("%c%s%c"), char1, string.c_str(), char2); -} +String wrap_String(const String& string, char char1, char char2) { return strformat(F("%c%s%c"), char1, string.c_str(), char2); } String wrapIfContains(const String& value, char contains, char wrap) { if (value.indexOf(contains) != -1) { @@ -668,13 +644,15 @@ String wrapWithQuotes(const String& text) { if (isWrappedWithQuotes(text)) { return text; } + // Try to find unused quote char and wrap char quotechar = '_'; + if (!findUnusedQuoteChar(text, quotechar)) { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLogMove(LOG_LEVEL_ERROR, strformat( - F("No unused quote to wrap: _%s_"), - text.c_str())); + F("No unused quote to wrap: _%s_"), + text.c_str())); } } return wrap_String(text, quotechar); @@ -684,88 +662,17 @@ String wrapWithQuotesIfContainsParameterSeparatorChar(const String& text) { if (isWrappedWithQuotes(text)) { return text; } + if (stringContainsSeparatorChar(text)) { return wrapWithQuotes(text); } return text; } -/*********************************************************************************************\ - Format an object value pair for use in JSON. -\*********************************************************************************************/ -String to_json_value(const String& value, bool wrapInQuotes) { - if (value.isEmpty()) { - // Empty string - return F("\"\""); - } - - if (is_json_formatted(value)) { - if (!wrapInQuotes) return value; - } - - if (wrapInQuotes || mustConsiderAsJSONString(value)) { - // Is not a numerical value, or BIN/HEX notation, thus wrap with quotes - - // First we check for not allowed special characters. - - // Most frequently found in a string - const bool backslash_or_doubleQuote_found = - value.indexOf('\\') != -1 || - value.indexOf('"') != -1; - - const size_t val_length = value.length(); - for (size_t i = 0; i < val_length; ++i) { - const char c = value[i]; - // Special characters not allowed in JSON: - if (backslash_or_doubleQuote_found || - (c >= '\b' && c <= '\r') -/* - c == '\b'|| // \b Backspace (ascii code 08) - c == '\t'|| // \t Tab - c == '\n'|| // \n New line - c == '\f'|| // \f Form feed (ascii code 0C) - c == '\r'|| // \r Carriage return -*/ - ) { - // Must replace characters, so make a deepcopy - String tmpValue = stripWrappingChar(value, '"'); - tmpValue.replace('\b', '^'); // \b Backspace (ascii code 08) - tmpValue.replace('\t', ' '); // \t Tab - tmpValue.replace('\n', '^'); // \n New line - tmpValue.replace('\f', '^'); // \f Form feed (ascii code 0C) - tmpValue.replace('\r', '^'); // \r Carriage return - tmpValue.replace('\\', '^'); // Backslash - tmpValue.replace(F("\""), F("\\\"")); // Double quote - return wrap_String(tmpValue, '"'); - } - } - return wrap_String(value, '"'); - } - // It is a numerical - return value; -} - - -bool is_json_formatted(const String& value) -{ - const size_t val_length = value.length(); - if (val_length > 2) { - // Check for JSON objects or arrays - const char firstchar = value[0]; - const char lastchar = value[val_length - 1]; - if ((firstchar == '[' && lastchar == ']') || - (firstchar == '{' && lastchar == '}')) - { - return true; - } - } - return false; -} - /*********************************************************************************************\ Strip wrapping chars (e.g. quotes) \*********************************************************************************************/ -String stripWrappingChar(const String& text, char wrappingChar) { +static String stripWrappingChar(const String& text, char wrappingChar) { const unsigned int length = text.length(); if ((length >= 2) && stringWrappedWithChar(text, wrappingChar)) { @@ -778,30 +685,25 @@ bool stringWrappedWithChar(const String& text, char wrappingChar) { const unsigned int length = text.length(); if (length < 2) { return false; } - return (text.charAt(0) == wrappingChar) && + return (text.charAt(0) == wrappingChar) && (text.charAt(length - 1) == wrappingChar); } -bool isQuoteChar(char c) { - return c == '\'' || c == '"' || c == '`'; -} +bool isQuoteChar(char c) { return c == '\'' || c == '"' || c == '`'; } bool findUnusedQuoteChar(const String& text, char& quotechar) { quotechar = '_'; - if (text.indexOf('\'') == -1) quotechar = '\''; - else if (text.indexOf('"') == -1) quotechar = '"'; - else if (text.indexOf('`') == -1) quotechar = '`'; - + + if (text.indexOf('\'') == -1) { quotechar = '\''; } + else if (text.indexOf('"') == -1) { quotechar = '"'; } + else if (text.indexOf('`') == -1) { quotechar = '`'; } + return isQuoteChar(quotechar); } -bool isParameterSeparatorChar(char c) { - return c == ',' || c == ' '; -} +bool isParameterSeparatorChar(char c) { return c == ',' || c == ' '; } -bool stringContainsSeparatorChar(const String& text) { - return text.indexOf(',') != -1 || text.indexOf(' ') != -1; -} +bool stringContainsSeparatorChar(const String& text) { return text.indexOf(',') != -1 || text.indexOf(' ') != -1; } bool isWrappedWithQuotes(const String& text) { if (text.length() < 2) { @@ -822,16 +724,11 @@ String stripQuotes(const String& text) { return text; } -bool safe_strncpy(char *dest, - const __FlashStringHelper * source, - size_t max_size) -{ - return safe_strncpy(dest, String(source), max_size); -} +bool safe_strncpy(char *dest, + const __FlashStringHelper *source, + size_t max_size) { return safe_strncpy(dest, String(source), max_size); } -bool safe_strncpy(char *dest, const String& source, size_t max_size) { - return safe_strncpy(dest, source.c_str(), max_size); -} +bool safe_strncpy(char *dest, const String& source, size_t max_size) { return safe_strncpy(dest, source.c_str(), max_size); } bool safe_strncpy(char *dest, const char *source, size_t max_size) { if (max_size < 1) { return false; } @@ -864,12 +761,89 @@ String to_internal_string(const String& input, char replaceSpace) { return result; } +/*********************************************************************************************\ + Format an object value pair for use in JSON. +\*********************************************************************************************/ +static bool is_json_formatted(const String& value) +{ + const size_t val_length = value.length(); + + if (val_length > 2) { + // Check for JSON objects or arrays + const char firstchar = value[0]; + const char lastchar = value[val_length - 1]; + + if (((firstchar == '[') && (lastchar == ']')) || + ((firstchar == '{') && (lastchar == '}'))) + { + return true; + } + } + return false; +} + +String to_json_value(const String& value, bool wrapInQuotes) { + if (value.isEmpty()) { + // Empty string + return F("\"\""); + } + + if (is_json_formatted(value)) { + if (!wrapInQuotes) { return value; } + } + + if (wrapInQuotes || mustConsiderAsJSONString(value)) { + // Is not a numerical value, or BIN/HEX notation, thus wrap with quotes + + // First we check for not allowed special characters. + + // Most frequently found in a string + const bool backslash_or_doubleQuote_found = + value.indexOf('\\') != -1 || + value.indexOf('"') != -1; + + const size_t val_length = value.length(); + + for (size_t i = 0; i < val_length; ++i) { + const char c = value[i]; + + // Special characters not allowed in JSON: + if (backslash_or_doubleQuote_found || + ((c >= '\b') && (c <= '\r')) + + /* + c == '\b'|| // \b Backspace (ascii code 08) + c == '\t'|| // \t Tab + c == '\n'|| // \n New line + c == '\f'|| // \f Form feed (ascii code 0C) + c == '\r'|| // \r Carriage return + */ + ) { + // Must replace characters, so make a deepcopy + String tmpValue = stripWrappingChar(value, '"'); + tmpValue.replace('\b', '^'); // \b Backspace (ascii code 08) + tmpValue.replace('\t', ' '); // \t Tab + tmpValue.replace('\n', '^'); // \n New line + tmpValue.replace('\f', '^'); // \f Form feed (ascii code 0C) + tmpValue.replace('\r', '^'); // \r Carriage return + tmpValue.replace('\\', '^'); // Backslash + tmpValue.replace(F("\""), F("\\\"")); // Double quote + return wrap_String(tmpValue, '"'); + } + } + return wrap_String(value, '"'); + } + + // It is a numerical + return value; +} + /*********************************************************************************************\ Parse a string and get the xth command or parameter IndexFind = 1 => command. // FIXME TD-er: parseString* should use index starting at 0. \*********************************************************************************************/ -String parseString(const char * string, uint8_t indexFind, char separator, bool trimResult) { +String parseString(const char *string, uint8_t indexFind, char separator, bool trimResult) { return parseString(String(string), indexFind, separator, trimResult); } @@ -890,6 +864,7 @@ String parseStringKeepCase(const String& string, uint8_t indexFind, char separat if (!GetArgv(string.c_str(), result, indexFind, separator)) { return String(); } + if (trimResult) { result.trim(); } @@ -908,25 +883,109 @@ String parseStringToEndKeepCaseNoTrim(const String& string, uint8_t indexFind, c return parseStringToEndKeepCase(string, indexFind, separator, false); } +static bool GetArgvBeginEnd(const char *string, + const size_t & string_len, + const unsigned int argc, + int & pos_begin, + int & pos_end, + unsigned int & string_pos, + unsigned int & argc_pos, + char separator) +{ + pos_begin = -1; + pos_end = -1; + + if (string == nullptr) { + return false; + } + bool parenthesis = false; + char matching_parenthesis = '"'; + + while (string_pos < string_len) + { + char c, d, e; // c = current char, d,e = next char (if available) + c = string[string_pos]; + d = 0; + e = 0; + + if ((string_pos + 1) < string_len) { + d = string[string_pos + 1]; + } + + if ((string_pos + 2) < string_len) { + e = string[string_pos + 2]; + } + + if (!parenthesis && (((c == ' ') && (d == ' ')) || + ((c == separator) && (d == ' ')))) { + // Consider multiple consecutive spaces as one. + } + else if (!parenthesis && ((d == ' ') && (e == separator))) { + // Skip the space. + } + else + { + // Found the start of the new argument. + if ((pos_begin == -1) && !parenthesis && !((c == separator) || isParameterSeparatorChar(c))) { + pos_begin = string_pos; + pos_end = string_pos; + } + + if (pos_end != -1) { + ++pos_end; + } + + // Check if we're in a set of parenthesis (any quote char or []) + if (!parenthesis && (isQuoteChar(c) || (c == '['))) { + parenthesis = true; + matching_parenthesis = c; + + if (c == '[') { + matching_parenthesis = ']'; + } + } else if (parenthesis && (c == matching_parenthesis)) { + parenthesis = false; + } + + if (!parenthesis && (isParameterSeparatorChar(d) || (d == separator) || (d == 0))) // end of word + { + argc_pos++; + + if (argc_pos == argc) + { + string_pos++; + return true; + } + + // new Argument separator found + pos_begin = -1; + pos_end = -1; + } + } + string_pos++; + } + return false; +} + String parseStringToEndKeepCase(const String& string, uint8_t indexFind, char separator, bool trimResult) { // Loop over the arguments to find the first and last pos of the arguments. - int pos_begin = string.length(); - int pos_end = pos_begin; - int tmppos_begin, tmppos_end = -1; + int pos_begin = string.length(); + int pos_end = pos_begin; + int tmppos_begin, tmppos_end = -1; uint8_t nextArgument = indexFind; - bool hasArgument = false; + bool hasArgument = false; unsigned int string_pos = 0, argc_pos = 0; const char *string_c_str = string.c_str(); - while (GetArgvBeginEnd(string_c_str, - string.length(), - nextArgument, - tmppos_begin, - tmppos_end, - string_pos, - argc_pos, + while (GetArgvBeginEnd(string_c_str, + string.length(), + nextArgument, + tmppos_begin, + tmppos_end, + string_pos, + argc_pos, separator)) { hasArgument = true; @@ -954,15 +1013,14 @@ String parseStringToEndKeepCase(const String& string, uint8_t indexFind, char se return stripQuotes(result); } -String tolerantParseStringKeepCase(const char * string, - uint8_t indexFind, - char separator, - bool trimResult) +String tolerantParseStringKeepCase(const char *string, + uint8_t indexFind, + char separator, + bool trimResult) { return tolerantParseStringKeepCase(String(string), indexFind, separator, trimResult); } - String tolerantParseStringKeepCase(const String& string, uint8_t indexFind, char separator, bool trimResult) { if (Settings.TolerantLastArgParse()) { @@ -976,12 +1034,13 @@ String tolerantParseStringKeepCase(const String& string, uint8_t indexFind, char ****************************************************************************/ String parseHexTextString(const String& argument, int index) { String result; + result.reserve(argument.length()); // longer than needed, most likely // Ignore these characters when used as hex-byte separators (0x01ab 23-cd:45 -> 0x01,0xab,0x23,0xcd,0x45) const String skipChars = F(" -:,.;"); - int i = index; - String arg = parseStringKeepCase(argument, i, ',', false); + int i = index; + String arg = parseStringKeepCase(argument, i, ',', false); while (!arg.isEmpty()) { if ((arg.startsWith(F("0x")) || arg.startsWith(F("0X")))) { @@ -1014,7 +1073,7 @@ String parseHexTextString(const String& argument, int index) { /***************************************************************************** * handles: 0xXX,text,0xXX," more text ",0xXX starting from index 2 (1-based) ****************************************************************************/ -std::vector parseHexTextData(const String& argument, int index) { +std::vectorparseHexTextData(const String& argument, int index) { std::vector result; // Ignore these characters when used as hex-byte separators (0x01ab 23-cd:45 -> 0x01,0xab,0x23,0xcd,0x45) @@ -1041,7 +1100,8 @@ std::vector parseHexTextData(const String& argument, int index) { if ((j + 1 < arg.length()) && (skipChars.indexOf(arg[j + 1]) != -1)) { int c = -1; - do { + do + { ++j; c = (j < arg.length()) ? skipChars.indexOf(arg[j]) : -1; } while (c > -1); @@ -1065,26 +1125,30 @@ std::vector parseHexTextData(const String& argument, int index) { Code (c) Tasmota: https://github.com/arendst/Tasmota/blob/293ae8064d753e6d38488b46d21cdc52a4a6e637/tasmota/tasmota_support/support.ino#L937 \*********************************************************************************************/ -char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack) +char* GetTextIndexed(char*destination, size_t destination_size, uint32_t index, const char*haystack) { // Returns empty string if not found // Returns text of found - char* write = destination; + char*write = destination; if (haystack != nullptr) { - const char* read = haystack; + const char*read = haystack; index++; + while (index--) { - size_t size = destination_size -1; + size_t size = destination_size - 1; write = destination; char ch = '.'; + while ((ch != '\0') && (ch != '|')) { ch = pgm_read_byte(read++); + if (size && (ch != '|')) { *write++ = ch; size--; } } + if (0 == ch) { if (index) { write = destination; @@ -1103,32 +1167,38 @@ char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, Code (c) Tasmota: https://github.com/arendst/Tasmota/blob/293ae8064d753e6d38488b46d21cdc52a4a6e637/tasmota/tasmota_support/support.ino#L967 \*********************************************************************************************/ -int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack) +int GetCommandCode(char*destination, size_t destination_size, const char*needle, const char*haystack) { // Returns -1 of not found // Returns index and command if found int result = -1; - if (haystack == nullptr) + + if (haystack == nullptr) { return result; - const char* read = haystack; - char* write = destination; + } + const char*read = haystack; + char*write = destination; while (true) { result++; - size_t size = destination_size -1; + size_t size = destination_size - 1; write = destination; char ch = '.'; + while ((ch != '\0') && (ch != '|')) { ch = pgm_read_byte(read++); + if (size && (ch != '|')) { *write++ = ch; size--; } } *write = '\0'; + if (!strcasecmp(needle, destination)) { break; } + if (0 == ch) { result = -1; break; @@ -1137,32 +1207,39 @@ int GetCommandCode(char* destination, size_t destination_size, const char* needl return result; } -int GetCommandCode(const char* needle, const char* haystack) +int GetCommandCode(const char*needle, const char*haystack) { // Likely long enough to parse any command static char temp[32]{}; + temp[0] = '\0'; return GetCommandCode(temp, sizeof(temp), needle, haystack); } - // escapes special characters in strings for use in html-forms bool htmlEscapeChar(char c, String& esc) { - const __FlashStringHelper * escaped = F(""); + const __FlashStringHelper *escaped = F(""); + switch (c) { - case '&': escaped = F("&"); break; - case '\"': escaped = F("""); break; - case '\'': escaped = F("'"); break; - case '<': escaped = F("<"); break; - case '>': escaped = F(">"); break; - case '/': escaped = F("/"); break; + case '&': escaped = F("&"); + break; + case '\"': escaped = F("""); + break; + case '\'': escaped = F("'"); + break; + case '<': escaped = F("<"); + break; + case '>': escaped = F(">"); + break; + case '/': escaped = F("/"); + break; default: return false; } - esc = String(escaped); + esc = String(escaped); return true; } @@ -1222,6 +1299,7 @@ String URLEncode(const String& msg) for (size_t i = 0; i < msg_length; ++i) { const char ch = msg[i]; + if (isAlphaNumeric(ch) || ('-' == ch) || ('_' == ch) || ('.' == ch) || ('~' == ch)) { @@ -1234,44 +1312,50 @@ String URLEncode(const String& msg) return encodedMsg; } -bool repl(const __FlashStringHelper * key, - const String& val, - String & s, - bool useURLencode) +bool repl(const __FlashStringHelper *key, + const String & val, + String & s, + bool useURLencode) { const char c = pgm_read_byte(key); - if (s.indexOf(c) != -1) + + if (s.indexOf(c) != -1) { return repl(String(key), val, s, useURLencode); + } return false; } -bool repl(const __FlashStringHelper * key, - const char* val, - String & s, - bool useURLencode) +bool repl(const __FlashStringHelper *key, + const char *val, + String & s, + bool useURLencode) { const char c = pgm_read_byte(key); - if (s.indexOf(c) != -1) + + if (s.indexOf(c) != -1) { return repl(String(key), String(val), s, useURLencode); + } return false; } -bool repl(const __FlashStringHelper * key1, - const __FlashStringHelper * key2, - const char* val, - String & s, - bool useURLencode) +bool repl(const __FlashStringHelper *key1, + const __FlashStringHelper *key2, + const char *val, + String & s, + bool useURLencode) { bool somethingReplaced = false; - if (repl(key1, val, s, useURLencode)) somethingReplaced = true; - if (repl(key2, val, s, useURLencode)) somethingReplaced = true; + + if (repl(key1, val, s, useURLencode)) { somethingReplaced = true; } + + if (repl(key2, val, s, useURLencode)) { somethingReplaced = true; } return somethingReplaced; } - bool repl(const String& key, const String& val, String& s, bool useURLencode) { if (s.indexOf(key) == -1) { return false; } + if (useURLencode) { s.replace(key, URLEncode(val)); } else { @@ -1293,10 +1377,13 @@ void parseSpecialCharacters(String& s, bool useURLencode) const char degree[3] = { 0xc2, 0xb0, 0 }; // Unicode degree symbol const char degreeC[4] = { 0xe2, 0x84, 0x83, 0 }; // Unicode degreeC symbol const char degree_C[4] = { 0xc2, 0xb0, 'C', 0 }; // Unicode degree symbol + captial C - if (!no_accolades) repl(F("{D}"), degree, s, useURLencode); - if (!no_html_entity) repl(F("°"), degree, s, useURLencode); - repl(degreeC, degree_C, s, useURLencode); + + if (!no_accolades) { repl(F("{D}"), degree, s, useURLencode); } + + if (!no_html_entity) { repl(F("°"), degree, s, useURLencode); } + repl(degreeC, degree_C, s, useURLencode); } + // Degree symbol is often used on displays, so still support that one. #ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER { @@ -1346,7 +1433,6 @@ void parseSpecialCharacters(String& s, bool useURLencode) #endif // ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER } - /********************************************************************************************\ replace other system variables like %sysname%, %systime%, %ip% \*********************************************************************************************/ @@ -1358,47 +1444,43 @@ void parseControllerVariables(String& s, struct EventStruct *event, bool useURLe // FIXME TD-er: These macros really increase build size. // Simple macro to create the replacement string only when needed. #define SMART_REPL(T, S) \ - if (s.indexOf(T) != -1) { repl((T), (S), s, useURLencode); } + if (s.indexOf(T) != -1) { repl((T), (S), s, useURLencode); } -void parseSingleControllerVariable(String & s, - struct EventStruct* event, - uint8_t taskValueIndex, - bool useURLencode) { +void parseSingleControllerVariable(String & s, + struct EventStruct*event, + uint8_t taskValueIndex, + bool useURLencode) { SMART_REPL(F("%valname%"), getTaskValueName(event->TaskIndex, taskValueIndex)); } #if FEATURE_MQTT_DISCOVER -void parseDeviceClassVariable(String & s, - const __FlashStringHelper* devclass, - bool useURLencode) { - SMART_REPL(F("%devclass%"), devclass); -} + +void parseDeviceClassVariable(String & s, + const __FlashStringHelper*devclass, + bool useURLencode) { SMART_REPL(F("%devclass%"), devclass); } void parseUniqueIdVariable(String & s, const String& uniqueId, - bool useURLencode) { - SMART_REPL(F("%unique_id%"), uniqueId); -} + bool useURLencode) { SMART_REPL(F("%unique_id%"), uniqueId); } -void parseElementIdVariable(String & s, - const String& elementId, - bool useURLencode) { - SMART_REPL(F("%element_id%"), elementId); -} -#endif +void parseElementIdVariable(String & s, + const String& elementId, + bool useURLencode) { SMART_REPL(F("%element_id%"), elementId); } + +#endif // if FEATURE_MQTT_DISCOVER #if FEATURE_STRING_VARIABLES + void parseValNameVariable(String & s, const String& valname, - bool useURLencode) { - SMART_REPL(F("%valname%"), valname); -} + bool useURLencode) { SMART_REPL(F("%valname%"), valname); } + #endif // if FEATURE_STRING_VARIABLES void parseSystemVariables(String& s, bool useURLencode) { String MaskEscapedPercent; - bool mustReplaceEscapedPercent = hasEscapedCharacter(s, '%'); + bool mustReplaceEscapedPercent = hasEscapedCharacter(s, '%'); if (mustReplaceEscapedPercent) { MaskEscapedPercent = static_cast(0x04); // ASCII 0x04 = End of transmit @@ -1421,7 +1503,7 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode } repl(F("%id%"), String(event->idx), s, useURLencode); - const bool val_found = s.indexOf(F("%val")) != -1; + const bool val_found = s.indexOf(F("%val")) != -1; const bool vname_found = s.indexOf(F("%vname")) != -1; #if FEATURE_STRING_VARIABLES @@ -1434,15 +1516,18 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode String postfix; const String search = getDerivedValueSearchAndPostfix(taskName, postfix); - auto it = customStringVar.begin(); + auto it = customStringVar.begin(); uint8_t varNr = VARS_PER_TASK; // %val5% and %vname5% and incrementing the number + while (it != customStringVar.end()) { if (it->first.startsWith(search) && it->first.endsWith(postfix)) { - String valueName = it->first.substring(search.length(), it->first.indexOf('-')); + String valueName = it->first.substring(search.length(), it->first.indexOf('-')); const String vname2 = getDerivedValueName(taskName, valueName); + if (!vname2.isEmpty()) { valueName = vname2; } + if (!it->second.isEmpty()) { String value(it->second); value = parseTemplateAndCalculate(value); @@ -1463,6 +1548,7 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode if (validTaskIndex(event->TaskIndex)) { if (val_found) { const uint8_t valueCount = (event->getSensorType() == Sensor_VType::SENSOR_TYPE_ULONG) ? 1 : getValueCountForTask(event->TaskIndex); + for (uint8_t i = 0; i < valueCount; ++i) { String valstr = F("%val"); valstr += (i + 1); @@ -1472,6 +1558,7 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode #if FEATURE_STRING_VARIABLES auto it = strVarValues.begin(); + while (it != strVarValues.end()) { String valstr = F("%val"); valstr += (it->first + 1); @@ -1487,6 +1574,7 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode if (vname_found) { const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + for (uint8_t i = 0; i < valueCount; ++i) { String vname = F("%vname"); vname += (i + 1); @@ -1496,6 +1584,7 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode } #if FEATURE_STRING_VARIABLES auto it = strVarNames.begin(); + while (it != strVarNames.end()) { String vname = F("%vname"); vname += (it->first + 1); @@ -1510,88 +1599,11 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode #undef SMART_REPL -bool getConvertArgument(const __FlashStringHelper * marker, const String& s, float& argument, int& startIndex, int& endIndex) { - String argumentString; - - if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) { - return validFloatFromString(argumentString, argument); - } - return false; -} - -bool getConvertArgument2(const __FlashStringHelper * marker, const String& s, float& arg1, float& arg2, int& startIndex, int& endIndex) { - String argumentString; - - if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) { - const int pos_comma = argumentString.indexOf(','); - - if (pos_comma == -1) { - return validFloatFromString(argumentString, arg1); // Accept single argument, with second argument 0 - } - - if (validFloatFromString(argumentString.substring(0, pos_comma), arg1)) { - return validFloatFromString(argumentString.substring(pos_comma + 1), arg2); - } - } - return false; -} - -#if FEATURE_STRING_VARIABLES -bool getConvertArgumentStr(const __FlashStringHelper * marker, const String& s, String& argument, int& startIndex, int& endIndex) { - return getConvertArgumentString(marker, s, argument, startIndex, endIndex); -} - -#define _MAX_STRFORMAT_ARGUMENTS 2 // Max. 2 numeric arguments for now to avoid strformat() from crashing when reading the 3rd argument that's not there -bool getConvertArgumentStrFormat(const __FlashStringHelper * marker, const String& s, String& argStr, float& arg1, float& arg2, int& startIndex, int& endIndex) { - String argumentString; - - if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) { - const int pos_comma = argumentString.indexOf(','); - - if (pos_comma == -1) { return false; } // At least 2 arguments required - - argStr = getCustomStringVar(argumentString.substring(0, pos_comma)); // Get String variable content - - if (argStr.isEmpty()) { // Not found or invalid variable name: probably provided a formatstring (not quoted, no commas!) - argStr = argumentString.substring(0, pos_comma); - } - - int pos_dollar = argStr.indexOf('$'); - int dollarCount{}; - while (pos_dollar != -1) { - ++dollarCount; - ++pos_dollar; - pos_dollar = argStr.indexOf('$', pos_dollar); - } - - // We support, 0, 1 or 2, $ formatting characters to avoid crashes and confusion with shorthand variables - if (dollarCount > _MAX_STRFORMAT_ARGUMENTS) { return false; } - - argStr.replace('$', '%'); // Change to regular strformat() format specifiers - - const int pos_comma2 = argumentString.indexOf(',', pos_comma + 1); - - if (pos_comma2 == -1) { - return validFloatFromString(argumentString.substring(pos_comma + 1), arg1); - } - - return validFloatFromString(argumentString.substring(pos_comma + 1, pos_comma2), arg1) - && validFloatFromString(argumentString.substring(pos_comma2 + 1), arg2); - } - return false; -} -#undef _MAX_STRFORMAT_ARGUMENTS -#endif // if FEATURE_STRING_VARIABLES - -bool getConvertArgumentString(const __FlashStringHelper * marker, const String& s, String& argumentString, int& startIndex, int& endIndex) { - return getConvertArgumentString(String(marker), s, argumentString, startIndex, endIndex); -} - -bool getConvertArgumentString(const String& marker, - const String& s, - String & argumentString, - int & startIndex, - int & endIndex) { +static bool getConvertArgumentString(const String& marker, + const String& s, + String & argumentString, + int & startIndex, + int & endIndex) { startIndex = s.indexOf(marker); @@ -1615,43 +1627,161 @@ bool getConvertArgumentString(const String& marker, return true; } +static bool getConvertArgumentString(const __FlashStringHelper *marker, + const String & s, + String & argumentString, + int & startIndex, + int & endIndex) { + return getConvertArgumentString(String(marker), s, argumentString, startIndex, endIndex); +} + +static bool getConvertArgument(const __FlashStringHelper *marker, const String& s, float& argument, int& startIndex, int& endIndex) { + String argumentString; + + if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) { + return validFloatFromString(argumentString, argument); + } + return false; +} + +static bool getConvertArgument2(const __FlashStringHelper *marker, const String& s, float& arg1, float& arg2, int& startIndex, + int& endIndex) { + String argumentString; + + if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) { + const int pos_comma = argumentString.indexOf(','); + + if (pos_comma == -1) { + return validFloatFromString(argumentString, arg1); // Accept single argument, with second argument 0 + } + + if (validFloatFromString(argumentString.substring(0, pos_comma), arg1)) { + return validFloatFromString(argumentString.substring(pos_comma + 1), arg2); + } + } + return false; +} + +#if FEATURE_STRING_VARIABLES + +static bool getConvertArgumentStr(const __FlashStringHelper *marker, const String& s, String& argument, int& startIndex, int& endIndex) { + return getConvertArgumentString(marker, s, argument, startIndex, endIndex); +} + + + +# define _MAX_STRFORMAT_ARGUMENTS 2 // Max. 2 numeric arguments for now to avoid strformat() from crashing when reading the 3rd argument + // that's not there + +static bool getConvertArgumentStrFormat(const __FlashStringHelper *marker, + const String & s, + String & argStr, + float & arg1, + float & arg2, + int & startIndex, + int & endIndex) { + String argumentString; + + if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) { + const int pos_comma = argumentString.indexOf(','); + + if (pos_comma == -1) { return false; } // At least 2 arguments required + + argStr = getCustomStringVar(argumentString.substring(0, pos_comma)); // Get String variable content + + if (argStr.isEmpty()) { // Not found or invalid variable name: probably provided a formatstring (not quoted, no commas!) + argStr = argumentString.substring(0, pos_comma); + } + + int pos_dollar = argStr.indexOf('$'); + int dollarCount{}; + + while (pos_dollar != -1) { + ++dollarCount; + ++pos_dollar; + pos_dollar = argStr.indexOf('$', pos_dollar); + } + + // We support, 0, 1 or 2, $ formatting characters to avoid crashes and confusion with shorthand variables + if (dollarCount > _MAX_STRFORMAT_ARGUMENTS) { return false; } + + argStr.replace('$', '%'); // Change to regular strformat() format specifiers + + const int pos_comma2 = argumentString.indexOf(',', pos_comma + 1); + + if (pos_comma2 == -1) { + return validFloatFromString(argumentString.substring(pos_comma + 1), arg1); + } + + return validFloatFromString(argumentString.substring(pos_comma + 1, pos_comma2), arg1) + && validFloatFromString(argumentString.substring(pos_comma2 + 1), arg2); + } + return false; +} + +# undef _MAX_STRFORMAT_ARGUMENTS +#endif // if FEATURE_STRING_VARIABLES + // FIXME TD-er: These macros really increase build size struct ConvertArgumentData { - ConvertArgumentData(String& s, bool useURLencode) + ConvertArgumentData(String& s, bool useURLencode) : str(s), arg1(0.0f), arg2(0.0f), startIndex(0), endIndex(0), - URLencode(useURLencode) {} + URLencode(useURLencode) {} ConvertArgumentData() = delete; String& str; - String str1; - float arg1, arg2; - int startIndex; - int endIndex; - bool URLencode; + String str1; + float arg1, arg2; + int startIndex; + int endIndex; + bool URLencode; + }; bool repl(ConvertArgumentData& data, const String& repl_str) { return repl(data.str.substring(data.startIndex, data.endIndex), repl_str, data.str, data.URLencode); } -bool getConvertArgument(const __FlashStringHelper * marker, ConvertArgumentData& data) { +static bool getConvertArgument(const __FlashStringHelper *marker, ConvertArgumentData& data) { return getConvertArgument(marker, data.str, data.arg1, data.startIndex, data.endIndex); } -bool getConvertArgument2(const __FlashStringHelper * marker, ConvertArgumentData& data) { +static bool getConvertArgument2(const __FlashStringHelper *marker, ConvertArgumentData& data) { return getConvertArgument2(marker, data.str, data.arg1, data.arg2, data.startIndex, data.endIndex); } +#if FEATURE_STRING_VARIABLES -bool getConvertArgumentStr(const __FlashStringHelper * marker, ConvertArgumentData& data) { +static bool getConvertArgumentStr(const __FlashStringHelper *marker, ConvertArgumentData& data) { return getConvertArgumentStr(marker, data.str, data.str1, data.startIndex, data.endIndex); } -#if FEATURE_STRING_VARIABLES -bool getConvertArgumentStrFormat(const __FlashStringHelper * marker, ConvertArgumentData& data) { +static bool getConvertArgumentStrFormat(const __FlashStringHelper *marker, ConvertArgumentData& data) { return getConvertArgumentStrFormat(marker, data.str, data.str1, data.arg1, data.arg2, data.startIndex, data.endIndex); } + +#endif // if FEATURE_STRING_VARIABLES + +#if FEATURE_STRING_VARIABLES + +static String get_date_time_from_timestamp(time_t unix_timestamp, bool am_pm, bool iso_format) { + struct tm ts; + + ts = *localtime(&unix_timestamp); + + return formatDateTimeString(ts, '-', ':', iso_format ? 'T' : ' ', am_pm && !iso_format) + + (iso_format && am_pm ? node_time.getTimeZoneOffsetString() : F("Z")); +} + +static String get_weekday_from_timestamp(time_t unix_timestamp) { + struct tm ts; + + ts = *localtime(&unix_timestamp); + + return String(ts.tm_wday); +} + #endif // if FEATURE_STRING_VARIABLES // Parse conversions marked with "%conv_marker%(float)" @@ -1667,42 +1797,42 @@ void parseStandardConversions(String& s, bool useURLencode) { // since they also replace the numerical parameter. // The marker may occur more than once per string, but with different parameters. #define SMART_CONV(T, FUN) \ - while (getConvertArgument((T), data)) { repl(data, (FUN)); } - SMART_CONV(F("%c_w_dir%"), getBearing(data.arg1)) - SMART_CONV(F("%c_c2f%"), toString(CelsiusToFahrenheit(data.arg1), 2)) - SMART_CONV(F("%c_ms2Bft%"), String(m_secToBeaufort(data.arg1))) - SMART_CONV(F("%c_cm2imp%"), centimeterToImperialLength(data.arg1)) - SMART_CONV(F("%c_mm2imp%"), millimeterToImperialLength(data.arg1)) - SMART_CONV(F("%c_m2day%"), toString(minutesToDay(data.arg1), 2)) - SMART_CONV(F("%c_m2dh%"), minutesToDayHour(data.arg1)) - SMART_CONV(F("%c_m2dhm%"), minutesToDayHourMinute(data.arg1)) - SMART_CONV(F("%c_m2hcm%"), minutesToHourColonMinute(data.arg1)) - SMART_CONV(F("%c_s2dhms%"), secondsToDayHourMinuteSecond(data.arg1)) + while (getConvertArgument((T), data)) { repl(data, (FUN)); } + SMART_CONV(F("%c_w_dir%"), getBearing(data.arg1)) + SMART_CONV(F("%c_c2f%"), toString(CelsiusToFahrenheit(data.arg1), 2)) + SMART_CONV(F("%c_ms2Bft%"), String(m_secToBeaufort(data.arg1))) + SMART_CONV(F("%c_cm2imp%"), centimeterToImperialLength(data.arg1)) + SMART_CONV(F("%c_mm2imp%"), millimeterToImperialLength(data.arg1)) + SMART_CONV(F("%c_m2day%"), toString(minutesToDay(data.arg1), 2)) + SMART_CONV(F("%c_m2dh%"), minutesToDayHour(data.arg1)) + SMART_CONV(F("%c_m2dhm%"), minutesToDayHourMinute(data.arg1)) + SMART_CONV(F("%c_m2hcm%"), minutesToHourColonMinute(data.arg1)) + SMART_CONV(F("%c_s2dhms%"), secondsToDayHourMinuteSecond(data.arg1)) #if FEATURE_ESPEASY_P2P - SMART_CONV(F("%c_uname%"), getNameForUnit(data.arg1)) - SMART_CONV(F("%c_uage%"), String(static_cast(getAgeForUnit(data.arg1) / 1000))) - SMART_CONV(F("%c_ubuild%"), String(getBuildnrForUnit(data.arg1))) + SMART_CONV(F("%c_uname%"), getNameForUnit(data.arg1)) + SMART_CONV(F("%c_uage%"), String(static_cast(getAgeForUnit(data.arg1) / 1000))) + SMART_CONV(F("%c_ubuild%"), String(getBuildnrForUnit(data.arg1))) SMART_CONV(F("%c_ubuildstr%"), formatSystemBuildNr(getBuildnrForUnit(data.arg1))) - SMART_CONV(F("%c_uload%"), toString(getLoadForUnit(data.arg1))) - SMART_CONV(F("%c_utype%"), String(getTypeForUnit(data.arg1))) - SMART_CONV(F("%c_utypestr%"), getTypeStringForUnit(data.arg1)) + SMART_CONV(F("%c_uload%"), toString(getLoadForUnit(data.arg1))) + SMART_CONV(F("%c_utype%"), String(getTypeForUnit(data.arg1))) + SMART_CONV(F("%c_utypestr%"), getTypeStringForUnit(data.arg1)) #endif // if FEATURE_ESPEASY_P2P #if FEATURE_STRING_VARIABLES - SMART_CONV(F("%c_ts2wday%"), get_weekday_from_timestamp(static_cast(data.arg1))) + SMART_CONV(F("%c_ts2wday%"), get_weekday_from_timestamp(static_cast(data.arg1))) #endif // if FEATURE_STRING_VARIABLES #ifndef LIMIT_BUILD_SIZE - SMART_CONV(F("%c_d2r%"), doubleToString(radians(data.arg1))) - SMART_CONV(F("%c_r2d%"), doubleToString(degrees(data.arg1))) + SMART_CONV(F("%c_d2r%"), doubleToString(radians(data.arg1))) + SMART_CONV(F("%c_r2d%"), doubleToString(degrees(data.arg1))) #endif // ifndef LIMIT_BUILD_SIZE #undef SMART_CONV // Conversions with 2 parameters #define SMART_CONV(T, FUN) \ - while (getConvertArgument2((T), data)) { repl(data, (FUN)); } - SMART_CONV(F("%c_dew_th%"), toString(compute_dew_point_temp(data.arg1, data.arg2), 2)) - SMART_CONV(F("%c_2hex%"), formatToHex_no_prefix(data.arg1, data.arg2)) + while (getConvertArgument2((T), data)) { repl(data, (FUN)); } + SMART_CONV(F("%c_dew_th%"), toString(compute_dew_point_temp(data.arg1, data.arg2), 2)) + SMART_CONV(F("%c_2hex%"), formatToHex_no_prefix(data.arg1, data.arg2)) #if FEATURE_ESPEASY_P2P - SMART_CONV(F("%c_u2ip%"), formatUnitToIPAddress(data.arg1, data.arg2)) + SMART_CONV(F("%c_u2ip%"), formatUnitToIPAddress(data.arg1, data.arg2)) #endif SMART_CONV(F("%c_alt_pres_sea%"), toString(altitudeFromPressure(data.arg1, data.arg2), 2)) SMART_CONV(F("%c_sea_pres_alt%"), toString(pressureElevation(data.arg1, data.arg2), 2)) @@ -1715,40 +1845,24 @@ void parseStandardConversions(String& s, bool useURLencode) { SMART_CONV(F("%c_random%"), doubleToString(HwRandom_f(data.arg1, data.arg2), 3, true)) #else SMART_CONV(F("%c_random%"), floatToString(HwRandom_f(data.arg1, data.arg2), 3, true)) - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE #undef SMART_CONV - + #if FEATURE_STRING_VARIABLES ESPEASY_RULES_FLOAT_TYPE tmp{}; - #define SMART_CONV(T, FUN) \ - while (getConvertArgumentStr((T), data)) { repl(data, (FUN)); } - SMART_CONV(F("%c_isnum%"), String(validDoubleFromString(getCustomStringVar(data.str1), tmp) ? 1 : 0)) - #undef SMART_CONV + # define SMART_CONV(T, FUN) \ + while (getConvertArgumentStr((T), data)) { repl(data, (FUN)); } + SMART_CONV(F("%c_isnum%"), String(validDoubleFromString(getCustomStringVar(data.str1), tmp) ? 1 : 0)) + # undef SMART_CONV + // 1 string and 1 or 2 numeric arguments - #define SMART_CONV(T, FUN) \ - while (getConvertArgumentStrFormat((T), data)) { repl(data, (FUN)); } - SMART_CONV(F("%c_strf%"), strformat(data.str1, data.arg1, data.arg2)) - #undef SMART_CONV + # define SMART_CONV(T, FUN) \ + while (getConvertArgumentStrFormat((T), data)) { repl(data, (FUN)); } + SMART_CONV(F("%c_strf%"), strformat(data.str1, data.arg1, data.arg2)) + # undef SMART_CONV #endif // if FEATURE_STRING_VARIABLES } -#if FEATURE_STRING_VARIABLES -String get_date_time_from_timestamp(time_t unix_timestamp, bool am_pm, bool iso_format) { - struct tm ts; - ts = *localtime(&unix_timestamp); - - return formatDateTimeString(ts, '-', ':', iso_format ? 'T' : ' ', am_pm && !iso_format) - + (iso_format && am_pm ? node_time.getTimeZoneOffsetString() : F("Z")); -} - -String get_weekday_from_timestamp(time_t unix_timestamp) { - struct tm ts; - ts = *localtime(&unix_timestamp); - - return String(ts.tm_wday); -} -#endif // if FEATURE_STRING_VARIABLES - /********************************************************************************************\ Find positional parameter in a char string \*********************************************************************************************/ @@ -1765,23 +1879,23 @@ bool GetArgv(const char *string, String& argvString, unsigned int argc, char sep return GetArgv( string, string_len, - argvString, + argvString, argc, - string_pos, - argc_pos, + string_pos, + argc_pos, separator); } -bool GetArgv(const char *string, - const size_t &string_len, - String & argvString, - unsigned int argc, - unsigned int &string_pos, - unsigned int &argc_pos, - char separator) +bool GetArgv(const char *string, + const size_t& string_len, + String & argvString, + unsigned int argc, + unsigned int& string_pos, + unsigned int& argc_pos, + char separator) { // FIXME TD-er: Setting to 0 again, must fix not starting from beginning each time -// string_pos = 0; argc_pos = 0; + // string_pos = 0; argc_pos = 0; int pos_begin, pos_end; bool hasArgument = GetArgvBeginEnd( string, @@ -1806,115 +1920,19 @@ bool GetArgv(const char *string, return true; } -/* -bool GetArgvBeginEnd(const char *string, const unsigned int argc, int& pos_begin, int& pos_end, char separator) +bool ContainsAny(const String& str, const __FlashStringHelper *charsToFind) { - const size_t string_len = strlen(string); - unsigned int string_pos = 0, argc_pos = 0; - - return GetArgvBeginEnd( - string, - string_len, - argc, - pos_begin, - pos_end, - string_pos, - argc_pos, - separator); -} -*/ - -bool GetArgvBeginEnd(const char *string, - const size_t &string_len, - const unsigned int argc, - int & pos_begin, - int & pos_end, - unsigned int &string_pos, - unsigned int &argc_pos, - char separator) -{ - pos_begin = -1; - pos_end = -1; - if (string == nullptr) { - return false; - } - bool parenthesis = false; - char matching_parenthesis = '"'; - - while (string_pos < string_len) - { - char c, d, e; // c = current char, d,e = next char (if available) - c = string[string_pos]; - d = 0; - e = 0; - - if ((string_pos + 1) < string_len) { - d = string[string_pos + 1]; - } - if ((string_pos + 2) < string_len) { - e = string[string_pos + 2]; - } - - if (!parenthesis && (((c == ' ') && (d == ' ')) || - ((c == separator) && (d == ' ')))) { - // Consider multiple consecutive spaces as one. - } - else if (!parenthesis && ((d == ' ') && (e == separator))) { - // Skip the space. - } - else - { - // Found the start of the new argument. - if (pos_begin == -1 && !parenthesis && !((c == separator) || isParameterSeparatorChar(c))) { - pos_begin = string_pos; - pos_end = string_pos; - } - if (pos_end != -1) { - ++pos_end; - } - - // Check if we're in a set of parenthesis (any quote char or []) - if (!parenthesis && (isQuoteChar(c) || (c == '['))) { - parenthesis = true; - matching_parenthesis = c; - - if (c == '[') { - matching_parenthesis = ']'; - } - } else if (parenthesis && (c == matching_parenthesis)) { - parenthesis = false; - } - - if (!parenthesis && (isParameterSeparatorChar(d) || (d == separator) || (d == 0))) // end of word - { - argc_pos++; - if (argc_pos == argc) - { - string_pos++; - return true; - } - // new Argument separator found - pos_begin = -1; - pos_end = -1; - } - } - string_pos++; - } - return false; -} - -bool ContainsAny(const String& str, const __FlashStringHelper * charsToFind) -{ - if (str.isEmpty()) return false; + if (str.isEmpty()) { return false; } return ContainsAny(str, String(charsToFind)); } bool ContainsAny(const String& str, const String& charsToFind) { - if (str.isEmpty()) return false; + if (str.isEmpty()) { return false; } const size_t nrCharsToFind = charsToFind.length(); + for (size_t i = 0; i < nrCharsToFind; ++i) { - if (str.indexOf(charsToFind[i]) != -1) return true; + if (str.indexOf(charsToFind[i]) != -1) { return true; } } return false; -} \ No newline at end of file +} diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h index 5f9e055b5..76d64145b 100644 --- a/src/src/Helpers/StringConverter.h +++ b/src/src/Helpers/StringConverter.h @@ -22,159 +22,198 @@ unsigned int count_newlines(const String& str); Concatenate using code which results in the smallest compiled code \*********************************************************************************************/ -String concat(const __FlashStringHelper * str, const String &val); -String concat(const __FlashStringHelper * str, const __FlashStringHelper *val); +String concat(const __FlashStringHelper *str, + const String & val); +String concat(const __FlashStringHelper *str, + const __FlashStringHelper *val); -String concat(const __FlashStringHelper * str, const char* val); -String concat(const __FlashStringHelper * str, int val); -String concat(const __FlashStringHelper * str, const char& c); +String concat(const __FlashStringHelper *str, + const char *val); +String concat(const __FlashStringHelper *str, + int val); +String concat(const __FlashStringHelper *str, + const char & c); #if defined(ESP32) && !defined(__riscv) -String concat(const __FlashStringHelper * str, const int32_t& val); -#endif -String concat(const __FlashStringHelper * str, const float& val); -String concat(const __FlashStringHelper * str, const double& val); -String concat(const __FlashStringHelper * str, const uint32_t& val); +String concat(const __FlashStringHelper *str, + const int32_t & val); +#endif // if defined(ESP32) && !defined(__riscv) +String concat(const __FlashStringHelper *str, + const float & val); +String concat(const __FlashStringHelper *str, + const double & val); +String concat(const __FlashStringHelper *str, + const uint32_t & val); #if defined(ESP32) && !defined(__riscv) -String concat(const __FlashStringHelper * str, const size_t& val); -#endif -String concat(const char& c, const __FlashStringHelper *val); -String concat(int i, const __FlashStringHelper *val); -String concat(uint32_t i, const __FlashStringHelper *val); -String concat(const String & str, const __FlashStringHelper *val); -String concat(const String & str, const String & val); -String concat(const String & str, const char* val); -String concat(const String & val, const char& c); -String concat(String &&val, const char& c); -String concat(String &&str, const __FlashStringHelper * val); -String concat(const __FlashStringHelper *str, String && val); +String concat(const __FlashStringHelper *str, + const size_t & val); +#endif // if defined(ESP32) && !defined(__riscv) +String concat(const char & c, + const __FlashStringHelper *val); +String concat(int i, + const __FlashStringHelper *val); +String concat(uint32_t i, + const __FlashStringHelper *val); +String concat(const String & str, + const __FlashStringHelper *val); +String concat(const String& str, + const String& val); +String concat(const String& str, + const char *val); +String concat(const String& val, + const char & c); +String concat(String && val, + const char& c); +String concat(String && str, + const __FlashStringHelper *val); +String concat(const __FlashStringHelper *str, + String && val); -String concat(const char& str, const String &val); +String concat(const char & str, + const String& val); + /* -template -String concat(const __FlashStringHelper * str, const T &val) { - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP + template + String concat(const __FlashStringHelper * str, const T &val) { + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP - String res(str); - res.concat(val); - return res; -} -*/ + String res(str); + res.concat(val); + return res; + } + */ /* -template -String concat(const String& str, const T &val) { - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP + template + String concat(const String& str, const T &val) { + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP - String res(str); - res.concat(val); - return res; -} - */ + String res(str); + res.concat(val); + return res; + } + */ -bool remove(String& str, const String& toRemove); -bool remove(String& str, const __FlashStringHelper * toRemove); -bool remove(String& str, const char& toRemove); +bool remove(String & str, + const String& toRemove); +bool remove(String & str, + const __FlashStringHelper *toRemove); +bool remove(String & str, + const char& toRemove); bool removeSpace(String& str); -bool equals(const String& str, const __FlashStringHelper * f_str); -bool equals(const String& str, const char& c); +bool equals(const String & str, + const __FlashStringHelper *f_str); +bool equals(const String& str, + const char & c); // Move the string to 2nd heap if present -void move_special(String& dest, String&& source); +void move_special(String & dest, + String&& source); String move_special(String&& source); -void move_special(String& dest, String&& source, size_t strLengthThreshold); -String move_special(String&& source, size_t strLengthThreshold); +void move_special(String & dest, + String&& source, + size_t strLengthThreshold); +String move_special(String&& source, + size_t strLengthThreshold); // Try to reserve on the heap with the most space available -bool reserve_special(String& str, size_t size); -bool reserve_special(String& str, size_t size, size_t strLengthThreshold); +bool reserve_special(String& str, + size_t size); +bool reserve_special(String& str, + size_t size, + size_t strLengthThreshold); // Arduino String does not have a function to de-allocate its internal buffer // This is a special trick to de-allocate its internal buffer and thus free up memory. void free_string(String& str); /* -template -bool equals(const String& str, const T &val) { - return str.equals(String(val)); -} -*/ + template + bool equals(const String& str, const T &val) { + return str.equals(String(val)); + } + */ /********************************************************************************************\ Format string using vsnprintf See: https://cplusplus.com/reference/cstdio/printf/ \*********************************************************************************************/ -String strformat(const String& format, ...); -String strformat(const __FlashStringHelper * format, ...); - +String strformat(const String& format, + ...); +String strformat(const __FlashStringHelper *format, + ...); /********************************************************************************************\ Convert a char string to IP uint8_t array \*********************************************************************************************/ -bool isIP(const String& string); +bool isIP(const String& string); -bool str2ip(const String& string, - uint8_t *IP); +bool str2ip(const String& string, + uint8_t *IP); -bool str2ip(const char *string, - uint8_t *IP); +bool str2ip(const char *string, + uint8_t *IP); -String formatIP(const IPAddress& ip, bool includeZone = false); +String formatIP(const IPAddress& ip, + bool includeZone = false); - -const __FlashStringHelper * boolToString(bool value); +const __FlashStringHelper* boolToString(bool value); /*********************************************************************************************\ Typical string replace functions. \*********************************************************************************************/ -void removeExtraNewLine(String& line); +void removeExtraNewLine(String& line); // Remove all occurences of given character from the string -void removeChar(String& line, char character); +void removeChar(String& line, + char character); -void addNewLine(String& line); +void addNewLine(String& line); -size_t UTF8_charLength(uint8_t firstByte); - -void replaceUnicodeByChar(String& line, char replChar); +void replaceUnicodeByChar(String& line, + char replChar); // Pad string with given char to minimum length -void padToMinimumLength(String& line, uint32_t minimumLength, char padChar = ' '); +void padToMinimumLength(String & line, + uint32_t minimumLength, + char padChar = ' '); // Prefix string with given char to minimum length -void prefixToMinimumLength(String& line, uint32_t minimumLength, char prefixChar = ' '); +void prefixToMinimumLength(String & line, + uint32_t minimumLength, + char prefixChar = ' '); /*********************************************************************************************\ Format a value to the set number of decimals \*********************************************************************************************/ String doFormatUserVar(struct EventStruct *event, - uint8_t rel_index, + uint8_t rel_index, bool mustCheck, bool & isvalid); String formatUserVarNoCheck(taskIndex_t TaskIndex, - uint8_t rel_index); + uint8_t rel_index); String formatUserVar(taskIndex_t TaskIndex, - uint8_t rel_index, + uint8_t rel_index, bool & isvalid); String formatUserVarNoCheck(struct EventStruct *event, - uint8_t rel_index); + uint8_t rel_index); String formatUserVar(struct EventStruct *event, - uint8_t rel_index, + uint8_t rel_index, bool & isvalid); #if FEATURE_STRING_VARIABLES @@ -190,9 +229,11 @@ String get_formatted_Controller_number(cpluginID_t cpluginID); String get_formatted_Plugin_number(pluginID_t pluginID); -// Prepend zeroes till the string value length is nrDigits -String formatIntLeadingZeroes(int value, int nrDigits); -String formatIntLeadingZeroes(const String& value, int nrDigits); +// Prepend zeroes till the string value length is nrDigits +String formatIntLeadingZeroes(int value, + int nrDigits); +String formatIntLeadingZeroes(const String& value, + int nrDigits); /*********************************************************************************************\ Wrap a string with given pre- and postfix string. @@ -200,10 +241,11 @@ String formatIntLeadingZeroes(const String& value, int nrDigits); String wrap_braces(const String& string); String wrap_String(const String& string, - char wrap); + char wrap); String wrap_String(const String& string, - char char1, char char2); + char char1, + char char2); String wrapIfContains(const String& value, char contains, @@ -217,22 +259,18 @@ String wrapWithQuotesIfContainsParameterSeparatorChar(const String& text); Format an object value pair for use in JSON. \*********************************************************************************************/ String to_json_value(const String& value, - bool wrapInQuotes = false); - -bool is_json_formatted(const String& value); + bool wrapInQuotes = false); /*********************************************************************************************\ Strip wrapping chars (e.g. quotes) \*********************************************************************************************/ -String stripWrappingChar(const String& text, - char wrappingChar); - bool stringWrappedWithChar(const String& text, char wrappingChar); bool isQuoteChar(char c); -bool findUnusedQuoteChar(const String& text, char& quotechar) ; +bool findUnusedQuoteChar(const String& text, + char & quotechar); bool isParameterSeparatorChar(char c); @@ -242,13 +280,13 @@ bool isWrappedWithQuotes(const String& text); String stripQuotes(const String& text); -bool safe_strncpy(char *dest, - const __FlashStringHelper * source, - size_t max_size); +bool safe_strncpy(char *dest, + const __FlashStringHelper *source, + size_t max_size); -bool safe_strncpy(char *dest, - const String& source, - size_t max_size); +bool safe_strncpy(char *dest, + const String& source, + size_t max_size); bool safe_strncpy(char *dest, const char *source, @@ -263,19 +301,19 @@ String to_internal_string(const String& input, IndexFind = 1 => command. // FIXME TD-er: parseString* should use index starting at 0. \*********************************************************************************************/ -String parseString(const char * string, - uint8_t indexFind, - char separator = ',', - bool trimResult = true); +String parseString(const char *string, + uint8_t indexFind, + char separator = ',', + bool trimResult = true); String parseString(const String& string, uint8_t indexFind, - char separator = ',', + char separator = ',', bool trimResult = true); String parseStringKeepCase(const String& string, uint8_t indexFind, - char separator = ',', + char separator = ',', bool trimResult = true); String parseStringKeepCaseNoTrim(const String& string, @@ -284,32 +322,32 @@ String parseStringKeepCaseNoTrim(const String& string, String parseStringToEnd(const String& string, uint8_t indexFind, - char separator = ',', + char separator = ',', bool trimResult = true); String parseStringToEndKeepCase(const String& string, uint8_t indexFind, - char separator = ',', + char separator = ',', bool trimResult = true); String parseStringToEndKeepCaseNoTrim(const String& string, uint8_t indexFind, char separator = ','); -String tolerantParseStringKeepCase(const char * string, - uint8_t indexFind, - char separator = ',', - bool trimResult = true); +String tolerantParseStringKeepCase(const char *string, + uint8_t indexFind, + char separator = ',', + bool trimResult = true); String tolerantParseStringKeepCase(const String& string, uint8_t indexFind, - char separator = ',', + char separator = ',', bool trimResult = true); -String parseHexTextString(const String& argument, - int index = 2); -std::vector parseHexTextData(const String& argument, - int index = 2); +String parseHexTextString(const String& argument, + int index = 2); +std::vectorparseHexTextData(const String& argument, + int index = 2); /*********************************************************************************************\ @@ -318,7 +356,10 @@ std::vector parseHexTextData(const String& argument, Code (c) Tasmota: https://github.com/arendst/Tasmota/blob/293ae8064d753e6d38488b46d21cdc52a4a6e637/tasmota/tasmota_support/support.ino#L937 \*********************************************************************************************/ -char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack); +char* GetTextIndexed(char *destination, + size_t destination_size, + uint32_t index, + const char*haystack); /*********************************************************************************************\ GetCommandCode: Find string in large PROGMEM stored string @@ -326,10 +367,13 @@ char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, Code (c) Tasmota: https://github.com/arendst/Tasmota/blob/293ae8064d753e6d38488b46d21cdc52a4a6e637/tasmota/tasmota_support/support.ino#L967 \*********************************************************************************************/ -int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack); - -int GetCommandCode(const char* needle, const char* haystack); +int GetCommandCode(char *destination, + size_t destination_size, + const char*needle, + const char*haystack); +int GetCommandCode(const char*needle, + const char*haystack); // escapes special characters in strings for use in html-forms @@ -345,55 +389,55 @@ void htmlStrongEscape(String& html); String URLEncode(const String& msg); -bool repl(const __FlashStringHelper * key, - const String& val, - String & s, - bool useURLencode); +bool repl(const __FlashStringHelper *key, + const String & val, + String & s, + bool useURLencode); -bool repl(const __FlashStringHelper * key, - const char* val, - String & s, - bool useURLencode); +bool repl(const __FlashStringHelper *key, + const char *val, + String & s, + bool useURLencode); -bool repl(const __FlashStringHelper * key1, - const __FlashStringHelper * key2, - const char* val, - String & s, - bool useURLencode); +bool repl(const __FlashStringHelper *key1, + const __FlashStringHelper *key2, + const char *val, + String & s, + bool useURLencode); -bool repl(const String& key, - const String& val, - String & s, - bool useURLencode); +bool repl(const String& key, + const String& val, + String & s, + bool useURLencode); void parseSpecialCharacters(String& s, - bool useURLencode); + bool useURLencode); /********************************************************************************************\ replace other system variables like %sysname%, %systime%, %ip% \*********************************************************************************************/ void parseControllerVariables(String & s, struct EventStruct *event, - bool useURLencode); + bool useURLencode); -void parseSingleControllerVariable(String & s, - struct EventStruct* event, - uint8_t taskValueIndex, - bool useURLencode); +void parseSingleControllerVariable(String & s, + struct EventStruct*event, + uint8_t taskValueIndex, + bool useURLencode); #if FEATURE_MQTT_DISCOVER -void parseDeviceClassVariable(String & s, - const __FlashStringHelper* devclass, - bool useURLencode); +void parseDeviceClassVariable(String & s, + const __FlashStringHelper*devclass, + bool useURLencode); void parseUniqueIdVariable(String & s, const String& uniqueId, bool useURLencode); -void parseElementIdVariable(String & s, - const String& elementId, - bool useURLencode); -#endif +void parseElementIdVariable(String & s, + const String& elementId, + bool useURLencode); +#endif // if FEATURE_MQTT_DISCOVER #if FEATURE_STRING_VARIABLES void parseValNameVariable(String & s, @@ -402,62 +446,18 @@ void parseValNameVariable(String & s, #endif // if FEATURE_STRING_VARIABLES void parseSystemVariables(String& s, - bool useURLencode); + bool useURLencode); void parseEventVariables(String & s, struct EventStruct *event, - bool useURLencode); + bool useURLencode); -bool getConvertArgument(const __FlashStringHelper * marker, - const String& s, - float & argument, - int & startIndex, - int & endIndex); - -bool getConvertArgument2(const __FlashStringHelper * marker, - const String& s, - float & arg1, - float & arg2, - int & startIndex, - int & endIndex); - -#if FEATURE_STRING_VARIABLES -bool getConvertArgumentStrFormat(const __FlashStringHelper *marker, - const String & s, - String & argStr, - float & arg1, - float & arg2, - int & startIndex, - int & endIndex); -#endif // if FEATURE_STRING_VARIABLES - -bool getConvertArgumentStr(const __FlashStringHelper *marker, - const String & s, - String & argument, - int & startIndex, - int & endIndex); - -bool getConvertArgumentString(const __FlashStringHelper * marker, - const String& s, - String & argumentString, - int & startIndex, - int & endIndex); - -bool getConvertArgumentString(const String& marker, - const String& s, - String & argumentString, - int & startIndex, - int & endIndex); // Parse conversions marked with "%conv_marker%(float)" // Must be called last, since all sensor values must be converted, processed, etc. void parseStandardConversions(String& s, - bool useURLencode); + bool useURLencode); -#if FEATURE_STRING_VARIABLES -String get_date_time_from_timestamp(time_t unix_timestamp, bool am_pm, bool iso_format); -String get_weekday_from_timestamp(time_t unix_timestamp); -#endif // if FEATURE_STRING_VARIABLES bool HasArgv(const char *string, unsigned int argc); @@ -467,30 +467,18 @@ bool GetArgv(const char *string, unsigned int argc, char separator = ','); -bool GetArgv(const char *string, - const size_t & string_len, - String & argvString, - unsigned int argc, - unsigned int & string_pos, - unsigned int & argc_pos, - char separator = ','); -/* -bool GetArgvBeginEnd(const char *string, - const unsigned int argc, - int & pos_begin, - int & pos_end, - char separator = ','); -*/ -bool GetArgvBeginEnd(const char *string, - const size_t & string_len, - const unsigned int argc, - int & pos_begin, - int & pos_end, - unsigned int & string_pos, - unsigned int & argc_pos, - char separator = ','); +bool GetArgv(const char *string, + const size_t& string_len, + String & argvString, + unsigned int argc, + unsigned int& string_pos, + unsigned int& argc_pos, + char separator = ','); -bool ContainsAny(const String& str, const char * charsToFind, size_t nrCharsToFind); -bool ContainsAny(const String& str, const String& charsToFind); +bool ContainsAny(const String& str, + const char *charsToFind, + size_t nrCharsToFind); +bool ContainsAny(const String& str, + const String& charsToFind); #endif // HELPERS_STRINGCONVERTER_H diff --git a/src/src/Helpers/StringConverter_Numerical.cpp b/src/src/Helpers/StringConverter_Numerical.cpp index 465853986..f07068f84 100644 --- a/src/src/Helpers/StringConverter_Numerical.cpp +++ b/src/src/Helpers/StringConverter_Numerical.cpp @@ -353,6 +353,20 @@ String formatToHex_array(const uint8_t* data, size_t size) return res; } +static String formatULLtoHex_no_prefix(const uint64_t& value, unsigned int minimal_hex_digits) +{ + const uint32_t msb_val = static_cast(value >> 32); + const uint32_t lsb_val = static_cast(value & 0xFFFFFFFF); + String res; + res.reserve(16); + res += formatToHex_no_prefix(msb_val, 8); + res += formatToHex_no_prefix(lsb_val, 8); + while (minimal_hex_digits < res.length() && res.startsWith(F("0"))) { + res = res.substring(1); + } + return res; +} + String formatULLtoHex(const uint64_t& value, const __FlashStringHelper * prefix, unsigned int minimal_hex_digits) @@ -371,19 +385,6 @@ String formatULLtoHex(const uint64_t& value, unsigned int minimal_hex_digits) return formatULLtoHex(value, F("0x"), minimal_hex_digits); } -String formatULLtoHex_no_prefix(const uint64_t& value, unsigned int minimal_hex_digits) -{ - const uint32_t msb_val = static_cast(value >> 32); - const uint32_t lsb_val = static_cast(value & 0xFFFFFFFF); - String res; - res.reserve(16); - res += formatToHex_no_prefix(msb_val, 8); - res += formatToHex_no_prefix(lsb_val, 8); - while (minimal_hex_digits < res.length() && res.startsWith(F("0"))) { - res = res.substring(1); - } - return res; -} String formatULLtoHex_decimal(const uint64_t& value) { @@ -481,7 +482,7 @@ String formatToHex_decimal(uint32_t value, uint32_t factor) { } -int intFromHexChar(char a) +static int intFromHexChar(char a) { if ((a >= 'A') && (a <= 'F')) { return a - 'A' + 10; } else if ((a >= 'a') && (a <= 'f')) { return a - 'a' + 10; } diff --git a/src/src/Helpers/StringConverter_Numerical.h b/src/src/Helpers/StringConverter_Numerical.h index 5c091fa8e..4bdf69493 100644 --- a/src/src/Helpers/StringConverter_Numerical.h +++ b/src/src/Helpers/StringConverter_Numerical.h @@ -105,8 +105,6 @@ String formatULLtoHex(const uint64_t& value, String formatULLtoHex(const uint64_t& value, unsigned int minimal_hex_digits = 0); -String formatULLtoHex_no_prefix(const uint64_t& value, unsigned int minimal_hex_digits = 0); - String formatULLtoHex_decimal(const uint64_t& value); @@ -133,8 +131,6 @@ String formatToHex_decimal(uint32_t value); String formatToHex_decimal(uint32_t value, uint32_t factor); -int intFromHexChar(char a); - String stringFromHexArray(const String& arr); #endif // ifndef HELPERS_STRINGCONVERTER_NUMERICAL_H diff --git a/src/src/NotifierStructs/N001_data_struct.cpp b/src/src/NotifierStructs/N001_data_struct.cpp index 1e2d4196c..9e980124e 100644 --- a/src/src/NotifierStructs/N001_data_struct.cpp +++ b/src/src/NotifierStructs/N001_data_struct.cpp @@ -20,6 +20,115 @@ # endif // if FEATURE_EMAIL_TLS + + + +static bool getNextMailAddress(const String& data, String& address, int index) +{ + int found = 0; + int strIndex[] = { 0, -1 }; + const int maxIndex = data.length() - 1; + + for (int i = 0; i <= maxIndex && found <= index; i++) { + if ((data.charAt(i) == ',') || (i == maxIndex)) { + found++; + strIndex[0] = strIndex[1] + 1; + strIndex[1] = (i == maxIndex) ? i + 1 : i; + } + } + + if (found > index) { + address = data.substring(strIndex[0], strIndex[1]); + return true; + } + return false; +} + + +static bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, uint16_t aWaitForPattern, uint16_t timeout) +{ + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat( + F("NPlugin_001_MTA: Waitfor: %u Timeout: %u ms Send: '%s'"), + aWaitForPattern, + timeout, + aStr.c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + + if (aStr.length()) { + client.PR_9453_FLUSH_TO_CLEAR(); // have to send msg to server so flush data first + client.println(aStr); + } + + // Wait For Response + const uint32_t timer = millis() + timeout; + backgroundtasks(); + + do { // FIXME TD-er: Why this while loop??? makes no sense as it will only be run once + delay(0); + + String line; + safeReadStringUntil(client, line, '\n', 1024); // , timeout); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("NPlugin_001_MTA: read line: "), line)); + } + # endif // ifndef BUILD_NO_DEBUG + + + // response could be like: '220 domain', '220-domain','220+domain' + const String pattern_str_space = strformat(F("%d "), aWaitForPattern); + const String pattern_str_minus = strformat(F("%d-"), aWaitForPattern); + const String pattern_str_plus = strformat(F("%d+"), aWaitForPattern); + + const bool patternFound = line.indexOf(pattern_str_space) >= 0 + || line.indexOf(pattern_str_minus) >= 0 + || line.indexOf(pattern_str_plus) >= 0; + + if (patternFound) { + return true; + } + } while (!timeOutReached(timer)); + + if (timeOutReached(timer)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, + concat(F("NPlugin_001_MTA: timeout. "), aStr)); + } + } + + return false; +} + +static bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass, uint16_t timeout) +{ + if (user.isEmpty() || pass.isEmpty()) { + // No user/password given. + return true; + } + base64 encoder; + + bool success = true; + + if (!NPlugin_001_MTA(client, F("AUTH LOGIN"), 334, timeout)) { success = false; } + + if (!NPlugin_001_MTA(client, encoder.encode(user), 334, timeout)) { success = false; } + + if (!NPlugin_001_MTA(client, encoder.encode(pass), 235, timeout)) { success = false; } +#ifndef BUILD_NO_DEBUG + if (success) { + addLog(LOG_LEVEL_INFO, F("Email: Credentials Accepted")); + } +#endif + return success; +} + + bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, String& subject, String& body) { bool myStatus = false; @@ -383,108 +492,6 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, St return myStatus; } -bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass, uint16_t timeout) -{ - if (user.isEmpty() || pass.isEmpty()) { - // No user/password given. - return true; - } - base64 encoder; - bool success = true; - - if (!NPlugin_001_MTA(client, F("AUTH LOGIN"), 334, timeout)) { success = false; } - - if (!NPlugin_001_MTA(client, encoder.encode(user), 334, timeout)) { success = false; } - - if (!NPlugin_001_MTA(client, encoder.encode(pass), 235, timeout)) { success = false; } -#ifndef BUILD_NO_DEBUG - if (success) { - addLog(LOG_LEVEL_INFO, F("Email: Credentials Accepted")); - } -#endif - return success; -} - -bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, uint16_t aWaitForPattern, uint16_t timeout) -{ - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, strformat( - F("NPlugin_001_MTA: Waitfor: %u Timeout: %u ms Send: '%s'"), - aWaitForPattern, - timeout, - aStr.c_str())); - } - # endif // ifndef BUILD_NO_DEBUG - - if (aStr.length()) { - client.PR_9453_FLUSH_TO_CLEAR(); // have to send msg to server so flush data first - client.println(aStr); - } - - // Wait For Response - const uint32_t timer = millis() + timeout; - backgroundtasks(); - - do { // FIXME TD-er: Why this while loop??? makes no sense as it will only be run once - delay(0); - - String line; - safeReadStringUntil(client, line, '\n', 1024); // , timeout); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, concat(F("NPlugin_001_MTA: read line: "), line)); - } - # endif // ifndef BUILD_NO_DEBUG - - - // response could be like: '220 domain', '220-domain','220+domain' - const String pattern_str_space = strformat(F("%d "), aWaitForPattern); - const String pattern_str_minus = strformat(F("%d-"), aWaitForPattern); - const String pattern_str_plus = strformat(F("%d+"), aWaitForPattern); - - const bool patternFound = line.indexOf(pattern_str_space) >= 0 - || line.indexOf(pattern_str_minus) >= 0 - || line.indexOf(pattern_str_plus) >= 0; - - if (patternFound) { - return true; - } - } while (!timeOutReached(timer)); - - if (timeOutReached(timer)) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, - concat(F("NPlugin_001_MTA: timeout. "), aStr)); - } - } - - return false; -} - -bool getNextMailAddress(const String& data, String& address, int index) -{ - int found = 0; - int strIndex[] = { 0, -1 }; - const int maxIndex = data.length() - 1; - - for (int i = 0; i <= maxIndex && found <= index; i++) { - if ((data.charAt(i) == ',') || (i == maxIndex)) { - found++; - strIndex[0] = strIndex[1] + 1; - strIndex[1] = (i == maxIndex) ? i + 1 : i; - } - } - - if (found > index) { - address = data.substring(strIndex[0], strIndex[1]); - return true; - } - return false; -} #endif // ifdef USES_N001 diff --git a/src/src/NotifierStructs/N001_data_struct.h b/src/src/NotifierStructs/N001_data_struct.h index 9ab778e7c..d4223fc88 100644 --- a/src/src/NotifierStructs/N001_data_struct.h +++ b/src/src/NotifierStructs/N001_data_struct.h @@ -20,17 +20,6 @@ bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, String & subject, String & body); -bool NPlugin_001_Auth(WiFiClient & client, - const String& user, - const String& pass, - uint16_t timeout); -bool NPlugin_001_MTA(WiFiClient & client, - const String& aStr, - uint16_t aWaitForPattern, - uint16_t timeout); -bool getNextMailAddress(const String& data, - String & address, - int index); #endif // ifdef USES_N001 diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index e541fc26f..0e6c06cec 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -32,6 +32,114 @@ void setLogLevelFor(LogDestination destination, LabelType::Enum label) { setLogLevelFor(destination, getFormItemInt(getInternalLabel(label))); } +static void addFormDstSelect(bool isStart, uint16_t choice) { + uint16_t tmpstart(choice); + uint16_t tmpend(choice); + + if (!TimeChangeRule(choice, 0).isValid()) { + time_zone.getDefaultDst_flash_values(tmpstart, tmpend); + } + TimeChangeRule rule(isStart ? tmpstart : tmpend, 0); + { + const __FlashStringHelper * week[] = { F("Last"), F("1st"), F("2nd"), F("3rd"), F("4th") }; + constexpr int weekValues[] = { 0, 1, 2, 3, 4 }; + addRowLabel(concat( + isStart ? F("Start") : F("End"), + F(" (week, dow, month)"))); + + const FormSelectorOptions selector(NR_ELEMENTS(weekValues), week, weekValues); + selector.addSelector( + isStart ? F("dststartweek") : F("dstendweek"), + rule.week); + } + html_BR(); + { + const __FlashStringHelper * dow[] = { F("Sun"), F("Mon"), F("Tue"), F("Wed"), F("Thu"), F("Fri"), F("Sat") }; + constexpr int dowValues[] = { 1, 2, 3, 4, 5, 6, 7 }; + + const FormSelectorOptions selector(NR_ELEMENTS(dowValues), dow, dowValues); + selector.addSelector( + isStart ? F("dststartdow") : F("dstenddow"), + rule.dow); + } + html_BR(); + { + const __FlashStringHelper * month[] = { F("Jan"), F("Feb"), F("Mar"), F("Apr"), F("May"), F("Jun"), F("Jul"), F("Aug"), F("Sep"), F("Oct"), F("Nov"), F( + "Dec") }; + constexpr int monthValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + + const FormSelectorOptions selector(NR_ELEMENTS(monthValues), month, monthValues); + selector.addSelector( + isStart ? F("dststartmonth") : F("dstendmonth"), + rule.month); + } + + addFormNumericBox( + isStart ? F("Start (localtime, e.g. 2h→3h)") : F("End (localtime, e.g. 3h→2h)"), + isStart ? F("dststarthour") : F("dstendhour"), + rule.hour, 0, 23); + addUnit(isStart ? F("hour ↷") : F("hour ↶")); +} + + + +static void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) +{ + addRowLabel(label); + 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), + #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); + selector.addSelector(id, static_cast(choice)); +} + + +static void addFormLogLevelSelect(LabelType::Enum label, int choice) +{ + #ifdef BUILD_NO_DEBUG + if (choice > LOG_LEVEL_INFO) choice = LOG_LEVEL_INFO; + #endif + + addRowLabel(getLabel(label)); + addLogLevelFormSelectorOptions(getInternalLabel(label), choice); +} + +#if FEATURE_SYSLOG +static void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) +{ + addRowLabel(label); + const __FlashStringHelper * options[] = + { F("Kernel"), F("User"), F("Daemon"), F("Message"), F("Local0"), F("Local1"), + F("Local2"), F("Local3"), F("Local4"), F("Local5"), F("Local6"), F("Local7") }; + const int optionValues[] = { 0, 1, 3, 5, 16, 17, 18, 19, 20, 21, 22, 23 }; + + const FormSelectorOptions selector(NR_ELEMENTS(options), options, optionValues); + selector.addSelector(id, choice); +} +#endif + // ******************************************************************************** // Web Interface config page // ******************************************************************************** @@ -419,110 +527,6 @@ void handle_advanced() { sendTail_stdtemplate(); } -void addFormDstSelect(bool isStart, uint16_t choice) { - uint16_t tmpstart(choice); - uint16_t tmpend(choice); - if (!TimeChangeRule(choice, 0).isValid()) { - time_zone.getDefaultDst_flash_values(tmpstart, tmpend); - } - TimeChangeRule rule(isStart ? tmpstart : tmpend, 0); - { - const __FlashStringHelper * week[] = { F("Last"), F("1st"), F("2nd"), F("3rd"), F("4th") }; - constexpr int weekValues[] = { 0, 1, 2, 3, 4 }; - addRowLabel(concat( - isStart ? F("Start") : F("End"), - F(" (week, dow, month)"))); - - const FormSelectorOptions selector(NR_ELEMENTS(weekValues), week, weekValues); - selector.addSelector( - isStart ? F("dststartweek") : F("dstendweek"), - rule.week); - } - html_BR(); - { - const __FlashStringHelper * dow[] = { F("Sun"), F("Mon"), F("Tue"), F("Wed"), F("Thu"), F("Fri"), F("Sat") }; - constexpr int dowValues[] = { 1, 2, 3, 4, 5, 6, 7 }; - - const FormSelectorOptions selector(NR_ELEMENTS(dowValues), dow, dowValues); - selector.addSelector( - isStart ? F("dststartdow") : F("dstenddow"), - rule.dow); - } - html_BR(); - { - const __FlashStringHelper * month[] = { F("Jan"), F("Feb"), F("Mar"), F("Apr"), F("May"), F("Jun"), F("Jul"), F("Aug"), F("Sep"), F("Oct"), F("Nov"), F( - "Dec") }; - constexpr int monthValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; - - const FormSelectorOptions selector(NR_ELEMENTS(monthValues), month, monthValues); - selector.addSelector( - isStart ? F("dststartmonth") : F("dstendmonth"), - rule.month); - } - - addFormNumericBox( - isStart ? F("Start (localtime, e.g. 2h→3h)") : F("End (localtime, e.g. 3h→2h)"), - isStart ? F("dststarthour") : F("dstendhour"), - rule.hour, 0, 23); - addUnit(isStart ? F("hour ↷") : F("hour ↶")); -} - -void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) -{ - addRowLabel(label); - 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), - #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); - selector.addSelector(id, static_cast(choice)); -} - - -void addFormLogLevelSelect(LabelType::Enum label, int choice) -{ - #ifdef BUILD_NO_DEBUG - if (choice > LOG_LEVEL_INFO) choice = LOG_LEVEL_INFO; - #endif - - addRowLabel(getLabel(label)); - addLogLevelFormSelectorOptions(getInternalLabel(label), choice); -} - -#if FEATURE_SYSLOG -void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) -{ - addRowLabel(label); - const __FlashStringHelper * options[] = - { F("Kernel"), F("User"), F("Daemon"), F("Message"), F("Local0"), F("Local1"), - F("Local2"), F("Local3"), F("Local4"), F("Local5"), F("Local6"), F("Local7") }; - const int optionValues[] = { 0, 1, 3, 5, 16, 17, 18, 19, 20, 21, 22, 23 }; - - const FormSelectorOptions selector(NR_ELEMENTS(options), options, optionValues); - selector.addSelector(id, choice); -} -#endif #endif // ifdef WEBSERVER_ADVANCED diff --git a/src/src/WebServer/AdvancedConfigPage.h b/src/src/WebServer/AdvancedConfigPage.h index b5988c882..3bc8c861d 100644 --- a/src/src/WebServer/AdvancedConfigPage.h +++ b/src/src/WebServer/AdvancedConfigPage.h @@ -13,20 +13,5 @@ // ******************************************************************************** void handle_advanced(); -void addFormDstSelect(bool isStart, uint16_t choice); - -void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice); - -void addFormLogLevelSelect(LabelType::Enum label, int choice); - -#if FEATURE_SYSLOG -void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); -#endif - - #endif // ifdef WEBSERVER_ADVANCED - - - - #endif \ No newline at end of file diff --git a/src/src/WebServer/Chart_JS.cpp b/src/src/WebServer/Chart_JS.cpp index a3bd845fc..dbbc7dc7d 100644 --- a/src/src/WebServer/Chart_JS.cpp +++ b/src/src/WebServer/Chart_JS.cpp @@ -7,7 +7,7 @@ # include "../WebServer/HTML_wrappers.h" # include "../WebServer/JSON.h" -void add_ChartJS_array(KeyValueWriter& parent, +static void add_ChartJS_array(KeyValueWriter& parent, int valueCount, const String array[]) { @@ -16,7 +16,7 @@ void add_ChartJS_array(KeyValueWriter& parent, } } -void add_ChartJS_array(KeyValueWriter& parent, +static void add_ChartJS_array(KeyValueWriter& parent, int valueCount, const float array[], uint8_t nrDecimals) @@ -26,7 +26,7 @@ void add_ChartJS_array(KeyValueWriter& parent, } } -void add_ChartJS_array(KeyValueWriter& parent, +static void add_ChartJS_array(KeyValueWriter& parent, int valueCount, const int array[]) { @@ -54,53 +54,7 @@ UP_KeyValueWriter add_ChartJS_chart_header( onlyJSON); } -UP_KeyValueWriter add_ChartJS_chart_header( - const __FlashStringHelper *chartType, - const String & id, - const ChartJS_title & chartTitle, - ChartJS_options_scales & options, - bool enableZoom, - size_t nrSamples, - bool onlyJSON) -{ - if (!onlyJSON) { - addHtml(F("
")); - addHtml(F("")); - addHtml(F("
")); - const char *id_c_str = id.c_str(); - addHtml(strformat( - F("")); - } - - add_ChartJS_chart_JSON_header( - *chartJSON, - chartType, - chartTitle, - options, - nrSamples, - enableZoom); - } - - return std::move(chartJSON); -} - -void add_ChartJS_chart_JSON_header( +static void add_ChartJS_chart_JSON_header( KeyValueWriter_JSON & parent, const __FlashStringHelper *chartType, const ChartJS_title & chartTitle, @@ -161,6 +115,54 @@ void add_ChartJS_chart_JSON_header( } } +UP_KeyValueWriter add_ChartJS_chart_header( + const __FlashStringHelper *chartType, + const String & id, + const ChartJS_title & chartTitle, + ChartJS_options_scales & options, + bool enableZoom, + size_t nrSamples, + bool onlyJSON) +{ + if (!onlyJSON) { + addHtml(F("
")); + addHtml(F("")); + addHtml(F("
")); + const char *id_c_str = id.c_str(); + addHtml(strformat( + F("")); + } + + add_ChartJS_chart_JSON_header( + *chartJSON, + chartType, + chartTitle, + options, + nrSamples, + enableZoom); + } + + return std::move(chartJSON); +} + + + void add_ChartJS_chart_labels( KeyValueWriter& parent, int valueCount, diff --git a/src/src/WebServer/Chart_JS.h b/src/src/WebServer/Chart_JS.h index 3cdf2902e..6579b4da7 100644 --- a/src/src/WebServer/Chart_JS.h +++ b/src/src/WebServer/Chart_JS.h @@ -67,14 +67,6 @@ UP_KeyValueWriter add_ChartJS_chart_header( size_t nrSamples = 0, bool onlyJSON = false); -void add_ChartJS_chart_JSON_header( - KeyValueWriter_JSON & parent, - const __FlashStringHelper *chartType, - const ChartJS_title & chartTitle, - ChartJS_options_scales & options, - size_t nrSamples, - bool enableZoom - ); void add_ChartJS_chart_labels( KeyValueWriter& parent, diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 10ec07745..d338a23cf 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -28,119 +28,11 @@ # include "../Helpers/_CPlugin_Helper_webform.h" # include "../Helpers/ESPEasy_Storage.h" -// ******************************************************************************** -// Web Interface controller page -// ******************************************************************************** -void handle_controllers() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_controllers")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!startStream_send_stdTemplate(MENU_INDEX_CONTROLLERS)) { return; } - - // 'index' value in the URL - uint8_t controllerindex = getFormItemInt(F("index"), 0); - const bool controllerIndexSet = controllerindex != 0 && validControllerIndex(controllerindex - 1); - --controllerindex; // Index in URL is starting from 1, but starting from 0 in the array. - - const int protocol_webarg_value = getFormItemInt(F("protocol"), -1); - - // submitted data - if ((protocol_webarg_value != -1) && controllerIndexSet) - { - const protocolIndex_t protocolIndex = protocol_webarg_value; - bool mustInit = false; - bool mustCallCpluginSave = false; - { - // Place in a scope to free ControllerSettings memory ASAP - MakeControllerSettings(ControllerSettings); // -V522 - - if (!AllocatedControllerSettings()) { - addHtmlError(F("Not enough free memory to save settings")); - } else { - // Need to make sure every byte between the members is also zero - // Otherwise the checksum will fail and settings will be saved too often. - ControllerSettings->reset(); - - if (Settings.Protocol[controllerindex] != protocolIndex) - { - // Protocol has changed. - Settings.Protocol[controllerindex] = protocolIndex; - - // there is a protocolIndex selected? - if (protocolIndex != 0) - { - mustInit = true; - handle_controllers_clearLoadDefaults(controllerindex, *ControllerSettings); - } - } - - // subitted same protocolIndex - else - { - // there is a protocolIndex selected - if (protocolIndex != 0) - { - mustInit = true; - handle_controllers_CopySubmittedSettings(controllerindex, *ControllerSettings); - mustCallCpluginSave = true; - } - } - addHtmlError(SaveControllerSettings(controllerindex, *ControllerSettings)); - # if FEATURE_MQTT_DISCOVER - - // AutoDiscovery enabled? - if (ControllerSettings->mqtt_autoDiscovery() - && Settings.ControllerEnabled[controllerindex] - - // && (ControllerSettings->MqttAutoDiscoveryTrigger[0] != 0) - && (ControllerSettings->MqttAutoDiscoveryTopic[0] != 0) - ) { - // Generate random time-offset in 0.1 sec, range 1..30 seconds - 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 - } - } - - if (mustCallCpluginSave) { - // Call CPLUGIN_WEBFORM_SAVE after destructing ControllerSettings object to reduce RAM usage. - // Controller plugin almost only deals with custom controller settings. - // Even if they need to save things to the ControllerSettings, then the changes must - // already be saved first as the CPluginCall does not have the ControllerSettings as argument. - handle_controllers_CopySubmittedSettings_CPluginCall(controllerindex); - } - addHtmlError(SaveSettings()); - - if (mustInit) { - // Init controller plugin using the new settings. - CPlugin_Exit_Init(controllerindex); - } - } - - if (controllerIndexSet) - { - handle_controllers_ControllerSettingsPage(controllerindex); - } else { - handle_controllers_ShowAllControllersTable(); - } - - sendTail_stdtemplate(); -} - // ******************************************************************************** // Selected controller has changed. // Clear all Controller settings and load some defaults // ******************************************************************************** -void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) +static void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) { // Protocol has changed and it was not an empty one. // reset (some) default-settings @@ -185,10 +77,12 @@ void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSet ClearCustomControllerSettings(controllerindex); } + + // ******************************************************************************** // Collect all submitted form data and store in the ControllerSettings // ******************************************************************************** -void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) +static void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) { // copy all settings to controller settings struct for (int parameterIdx = 0; parameterIdx <= ControllerSettingsStruct::CONTROLLER_ENABLED; ++parameterIdx) { @@ -197,7 +91,7 @@ void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, Controlle } } -void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex) { +static void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex) { protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); if (validProtocolIndex(ProtocolIndex)) { @@ -213,7 +107,7 @@ void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerinde // ******************************************************************************** // Show table with all selected controllers // ******************************************************************************** -void handle_controllers_ShowAllControllersTable() +static void handle_controllers_ShowAllControllersTable() { html_add_form(); html_table_class_multirow(); @@ -278,7 +172,7 @@ void handle_controllers_ShowAllControllersTable() // ******************************************************************************** // Show the controller settings page // ******************************************************************************** -void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex) +static void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex) { if (!validControllerIndex(controllerindex)) { return; @@ -726,4 +620,115 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex html_end_form(); } + + + +// ******************************************************************************** +// Web Interface controller page +// ******************************************************************************** +void handle_controllers() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_controllers")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (!startStream_send_stdTemplate(MENU_INDEX_CONTROLLERS)) { return; } + + // 'index' value in the URL + uint8_t controllerindex = getFormItemInt(F("index"), 0); + const bool controllerIndexSet = controllerindex != 0 && validControllerIndex(controllerindex - 1); + --controllerindex; // Index in URL is starting from 1, but starting from 0 in the array. + + const int protocol_webarg_value = getFormItemInt(F("protocol"), -1); + + // submitted data + if ((protocol_webarg_value != -1) && controllerIndexSet) + { + const protocolIndex_t protocolIndex = protocol_webarg_value; + bool mustInit = false; + bool mustCallCpluginSave = false; + { + // Place in a scope to free ControllerSettings memory ASAP + MakeControllerSettings(ControllerSettings); // -V522 + + if (!AllocatedControllerSettings()) { + addHtmlError(F("Not enough free memory to save settings")); + } else { + // Need to make sure every byte between the members is also zero + // Otherwise the checksum will fail and settings will be saved too often. + ControllerSettings->reset(); + + if (Settings.Protocol[controllerindex] != protocolIndex) + { + // Protocol has changed. + Settings.Protocol[controllerindex] = protocolIndex; + + // there is a protocolIndex selected? + if (protocolIndex != 0) + { + mustInit = true; + handle_controllers_clearLoadDefaults(controllerindex, *ControllerSettings); + } + } + + // subitted same protocolIndex + else + { + // there is a protocolIndex selected + if (protocolIndex != 0) + { + mustInit = true; + handle_controllers_CopySubmittedSettings(controllerindex, *ControllerSettings); + mustCallCpluginSave = true; + } + } + addHtmlError(SaveControllerSettings(controllerindex, *ControllerSettings)); + # if FEATURE_MQTT_DISCOVER + + // AutoDiscovery enabled? + if (ControllerSettings->mqtt_autoDiscovery() + && Settings.ControllerEnabled[controllerindex] + + // && (ControllerSettings->MqttAutoDiscoveryTrigger[0] != 0) + && (ControllerSettings->MqttAutoDiscoveryTopic[0] != 0) + ) { + // Generate random time-offset in 0.1 sec, range 1..30 seconds + 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 + } + } + + if (mustCallCpluginSave) { + // Call CPLUGIN_WEBFORM_SAVE after destructing ControllerSettings object to reduce RAM usage. + // Controller plugin almost only deals with custom controller settings. + // Even if they need to save things to the ControllerSettings, then the changes must + // already be saved first as the CPluginCall does not have the ControllerSettings as argument. + handle_controllers_CopySubmittedSettings_CPluginCall(controllerindex); + } + addHtmlError(SaveSettings()); + + if (mustInit) { + // Init controller plugin using the new settings. + CPlugin_Exit_Init(controllerindex); + } + } + + if (controllerIndexSet) + { + handle_controllers_ControllerSettingsPage(controllerindex); + } else { + handle_controllers_ShowAllControllersTable(); + } + + sendTail_stdtemplate(); +} + #endif // ifdef WEBSERVER_CONTROLLERS diff --git a/src/src/WebServer/ControllerPage.h b/src/src/WebServer/ControllerPage.h index bba1b881d..8aa1e41f2 100644 --- a/src/src/WebServer/ControllerPage.h +++ b/src/src/WebServer/ControllerPage.h @@ -14,32 +14,6 @@ // ******************************************************************************** void handle_controllers(); -// ******************************************************************************** -// Selected controller has changed. -// Clear all Controller settings and load some defaults -// ******************************************************************************** -void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); - -// ******************************************************************************** -// Collect all submitted form data and store in the ControllerSettings -// ******************************************************************************** -void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); - -void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex); - -// ******************************************************************************** -// Show table with all selected controllers -// ******************************************************************************** -void handle_controllers_ShowAllControllersTable(); - -// ******************************************************************************** -// Show the controller settings page -// ******************************************************************************** -void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex); #endif // ifdef WEBSERVER_CONTROLLERS - - - - #endif \ No newline at end of file From 396504655e3a51500bc3da8d81836a3d59f184eb Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 17 Oct 2026 23:55:19 +0200 Subject: [PATCH 03/12] [Build] Reduce build size using static functions in src/WebServer --- src/src/WebServer/AdvancedConfigPage.cpp | 4 +- src/src/WebServer/DevicesPage.cpp | 2008 +++++++++++----------- src/src/WebServer/DevicesPage.h | 91 +- src/src/WebServer/DownloadPage.cpp | 66 +- src/src/WebServer/DownloadPage.h | 9 +- src/src/WebServer/ESPEasy_WebServer.cpp | 592 ++++--- src/src/WebServer/ESPEasy_WebServer.h | 144 +- src/src/WebServer/FactoryResetPage.cpp | 42 +- src/src/WebServer/FactoryResetPage.h | 4 - src/src/WebServer/FileList.cpp | 131 +- src/src/WebServer/FileList.h | 4 - src/src/WebServer/I2C_Scanner.cpp | 313 ++-- src/src/WebServer/I2C_Scanner.h | 13 - src/src/WebServer/InterfacesPage.cpp | 540 +++--- src/src/WebServer/InterfacesPage.h | 66 +- src/src/WebServer/JSON.cpp | 24 +- src/src/WebServer/JSON.h | 61 +- src/src/WebServer/Metrics.cpp | 108 +- src/src/WebServer/Metrics.h | 1 - src/src/WebServer/NetworkPage.cpp | 216 +-- src/src/WebServer/NetworkPage.h | 29 +- src/src/WebServer/Rules.cpp | 223 +-- src/src/WebServer/Rules.h | 13 +- 23 files changed, 2284 insertions(+), 2418 deletions(-) diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 0e6c06cec..bfed58db2 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -82,7 +82,7 @@ static void addFormDstSelect(bool isStart, uint16_t choice) { } - +#if FEATURE_EXT_RTC static void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) { addRowLabel(label); @@ -114,7 +114,7 @@ static void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const const FormSelectorOptions selector(NR_ELEMENTS(optionValues), options, optionValues); selector.addSelector(id, static_cast(choice)); } - +#endif static void addFormLogLevelSelect(LabelType::Enum label, int choice) { diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 9925d1b23..5ab48a25d 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -40,190 +40,16 @@ # include -#if FEATURE_TASKVALUE_UNIT_OF_MEASURE -#include "../Helpers/ESPEasy_UnitOfMeasure.h" -#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - -void handle_devices() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_devices")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!startStream_send_stdTemplate(MENU_INDEX_DEVICES)) { return; } - - // char tmpString[41]; - - - // String taskindex = webArg(F("index")); - - pluginID_t taskdevicenumber; - - if (hasArg(F("del"))) { - taskdevicenumber.setInvalid(); - } - else { - taskdevicenumber = pluginID_t::toPluginID(getFormItemInt(F("TDNUM"), 0)); - } - - - // String taskdeviceid[CONTROLLER_MAX]; - // String taskdevicepin1 = webArg(F("taskdevicepin1")); // "taskdevicepin*" should not be changed because it is uses by plugins - // and expected to be saved by this code - // String taskdevicepin2 = webArg(F("taskdevicepin2")); - // String taskdevicepin3 = webArg(F("taskdevicepin3")); - // String taskdevicepin1pullup = webArg(F("TDPPU")); - // String taskdevicepin1inversed = webArg(F("TDPI")); - // String taskdevicename = webArg(F("TDN")); - // String taskdeviceport = webArg(F("TDP")); - // String taskdeviceformula[VARS_PER_TASK]; - // String taskdevicevaluename[VARS_PER_TASK]; - // String taskdevicevaluedecimals[VARS_PER_TASK]; - // String taskdevicesenddata[CONTROLLER_MAX]; - // String taskdeviceglobalsync = webArg(F("TDGS")); - // String taskdeviceenabled = webArg(F("TDE")); - - // for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) - // { - // char argc[25]; - // String arg = F("TDF"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdeviceformula[varNr] = webArg(argc); - // - // arg = F("TDVN"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicevaluename[varNr] = webArg(argc); - // - // arg = F("TDVD"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicevaluedecimals[varNr] = webArg(argc); - // } - - // for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - // { - // char argc[25]; - // String arg = F("TDID"); - // arg += controllerNr + 1; - // arg.toCharArray(argc, 25); - // taskdeviceid[controllerNr] = webArg(argc); - // - // arg = F("TDSD"); - // arg += controllerNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicesenddata[controllerNr] = webArg(argc); - // } - - uint8_t page = getFormItemInt(F("page"), 0); - - if (page == 0) { - page = 1; - } - uint8_t setpage = getFormItemInt(F("setpage"), 0); - - if (setpage > 0) - { - if (setpage <= (TASKS_MAX / TASKS_PER_PAGE)) { - page = setpage; - } - else { - page = TASKS_MAX / TASKS_PER_PAGE; - } - } - const int edit = getFormItemInt(F("edit"), 0); - - // taskIndex in the URL is 1 ... TASKS_MAX - // For use in other functions, set it to 0 ... (TASKS_MAX - 1) - taskIndex_t taskIndex = getFormItemInt(F("index"), 0); - boolean taskIndexNotSet = taskIndex == 0; - - const bool nosave = isFormItemChecked(F("nosave")); - - if (!taskIndexNotSet) { - --taskIndex; - - // LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date - } - - // FIXME TD-er: Might have to clear any caches here. - if ((edit != 0) && !taskIndexNotSet) // when form submitted - { - if (Settings.getPluginID_for_task(taskIndex) != taskdevicenumber) - { - // change of device: cleanup old device and reset default settings - setTaskDevice_to_TaskIndex(taskdevicenumber, taskIndex); -#if FEATURE_I2C - const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); - - if (validDeviceIndex(DeviceIndex)) { - const DeviceStruct& device = Device[DeviceIndex]; - - if ((device.Type == DEVICE_TYPE_I2C) && device.I2CMax100kHz) { // 100 kHz-only I2C device? - bitWrite(Settings.I2C_SPI_bus_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED, 1); // Then: Enable Force Slow I2C speed checkbox by default - } - } -#endif - } - else if (taskdevicenumber != INVALID_PLUGIN_ID) // save settings - { - handle_devices_CopySubmittedSettings(taskIndex, taskdevicenumber); - } - - if (taskdevicenumber != INVALID_PLUGIN_ID) { - // Task index has a task device number, so it makes sense to save. - // N.B. When calling delete, the settings were already saved. - if (nosave) { - Cache.updateExtraTaskSettingsCache(); - } else { - addHtmlError(SaveTaskSettings(taskIndex)); - addHtmlError(SaveSettings()); - } - - struct EventStruct TempEvent(taskIndex); - String dummy; - - // May need to call PLUGIN_INIT, however we must make sure it is exited first - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - - if (Settings.TaskDeviceEnabled[taskIndex]) { - if (PluginCall(PLUGIN_INIT, &TempEvent, dummy)) { - PluginCall(PLUGIN_READ, &TempEvent, dummy); - } - } - } - } - - // show all tasks as table - if (taskIndexNotSet) - { - handle_devicess_ShowAllTasksTable(page); - } - - // Show edit form if a specific entry is chosen with the edit button - else - { - handle_devices_TaskSettingsPage(taskIndex, page); - } - - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_devices")); - # endif // ifndef BUILD_NO_RAM_TRACKER -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - addLogMove(LOG_LEVEL_DEBUG_DEV, concat(F("DEBUG: String size:"), static_cast(TXBuffer.sentBytes))); - } -# endif // ifndef BUILD_NO_DEBUG - sendTail_stdtemplate(); -} +# if FEATURE_TASKVALUE_UNIT_OF_MEASURE +# include "../Helpers/ESPEasy_UnitOfMeasure.h" +# endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE // ******************************************************************************** // Add a device select dropdown list // TODO TD-er: Add JavaScript filter: // https://www.w3schools.com/howto/howto_js_filter_dropdown.asp // ******************************************************************************** -void addDeviceSelect(const __FlashStringHelper *name, pluginID_t choice) +static void addDeviceSelect(const __FlashStringHelper *name, pluginID_t choice) { String deviceName; @@ -263,7 +89,7 @@ void addDeviceSelect(const __FlashStringHelper *name, pluginID_t choice) // ******************************************************************************** // Collect all submitted form data and store the task settings // ******************************************************************************** -void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t taskdevicenumber) +static void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t taskdevicenumber) { if (!validTaskIndex(taskIndex)) { return; } const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); @@ -280,47 +106,49 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; - #ifdef ESP32 - #if FEATURE_SPI + # ifdef ESP32 + # if FEATURE_SPI + if (device.isSPI()) { Settings.setSPIBusForTask(taskIndex, getFormItemInt(F("pspibus"), 0)); } - #endif - #endif // ifdef ESP32 -#if FEATURE_I2C + # endif // if FEATURE_SPI + # endif // ifdef ESP32 +# if FEATURE_I2C + if (device.Type == DEVICE_TYPE_I2C) { uint8_t flags = 0; bitWrite(flags, I2C_FLAGS_SLOW_SPEED, isFormItemChecked(F("taskdeviceflags0"))); -# if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER +# if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER uint8_t i2cBus = 0; -# endif +# endif -# if FEATURE_I2C_MULTIPLE +# if FEATURE_I2C_MULTIPLE if ((getI2CBusCount() > 1) && (Settings.isI2CEnabled(1) - # if FEATURE_I2C_INTERFACE_3 + # if FEATURE_I2C_INTERFACE_3 || Settings.isI2CEnabled(2) - # endif + # endif ) && !Device[DeviceIndex].I2CNoBusSelection ) { i2cBus = getFormItemInt(F("pi2cbus")); set3BitToUL(flags, I2C_FLAGS_BUS_NUMBER, i2cBus); } -# endif // if FEATURE_I2C_MULTIPLE +# endif // if FEATURE_I2C_MULTIPLE -# if FEATURE_I2CMULTIPLEXER +# if FEATURE_I2CMULTIPLEXER bool muxPortsOption{}; - int selectedPorts{}; + int selectedPorts{}; GetI2CMultiplexerFromPage(i2cBus, muxPortsOption, selectedPorts); bitWrite(flags, I2C_FLAGS_MUX_MULTICHANNEL, muxPortsOption); Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; -# endif // if FEATURE_I2CMULTIPLEXER +# endif // if FEATURE_I2CMULTIPLEXER Settings.I2C_SPI_bus_Flags[taskIndex] = flags; } -#endif +# endif // if FEATURE_I2C // Must load from file system to make sure all caches and checksums match. ExtraTaskSettings.clear(); @@ -548,17 +376,830 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task UserVar.clear_computed(taskIndex); } -void html_add_setPage(uint8_t page, bool isLinkToPrev) { +static void html_add_setPage(uint8_t page, bool isLinkToPrev) { addHtml(strformat( F("devices?setpage=%u'>&%ct;"), static_cast(page), isLinkToPrev ? 'l' : 'g')); } +# if FEATURE_ESPEASY_P2P + +static void format_originating_node(uint8_t remoteUnit) { + addHtml(F("Unit ")); + addHtmlInt(remoteUnit); + + if (remoteUnit != 255) { + const NodeStruct *node = Nodes.getNode(remoteUnit); + + if (node != nullptr) { + addHtml(F(" - ")); + addHtml(node->getNodeName()); + } else { + addHtml(F(" - Not Seen recently")); + } + } +} + +# endif // if FEATURE_ESPEASY_P2P + + +# if FEATURE_SPI + +static void format_SPI_port_description(int8_t spi_gpios[3], uint8_t spi_bus) +{ + if (!Settings.getSPI_pins(spi_gpios, spi_bus)) { + addHtml(F("SPI (Not enabled)")); + return; + } + # ifdef ESP32 + addHtml(getSPI_optionToShortString(Settings.getSPISelection(spi_bus), spi_bus)); + # endif // ifdef ESP32 + # ifdef ESP8266 + addHtml(F("SPI")); + # endif // ifdef ESP8266 +} + +# endif // if FEATURE_SPI + +# if FEATURE_I2C + +static void format_I2C_pin_description(taskIndex_t x) +{ + # if FEATURE_I2C_MULTIPLE + const uint8_t i2cBus = Settings.getI2CInterface(x); + # else + const uint8_t i2cBus = 0; + # endif // if FEATURE_I2C_MULTIPLE + + if (checkI2CConfigValid_toHtml(x)) { + Label_Gpio_toHtml(F("SDA"), formatGpioLabel(Settings.getI2CSdaPin(i2cBus), false)); + html_BR(); + Label_Gpio_toHtml(F("SCL"), formatGpioLabel(Settings.getI2CSclPin(i2cBus), false)); + } +} + +# endif // if FEATURE_I2C + +# if FEATURE_SPI + +static void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSpin) +{ + const uint8_t spi_bus = Settings.getSPIBusForTask(x); + + if (Settings.getSPISelection(spi_bus) != SPI_Options_e::None) { + const __FlashStringHelper*labels[] = { F("CLK"), F("MISO"), F("MOSI") }; + + for (size_t i = 0; i < NR_ELEMENTS(labels); ++i) { + if (i != 0) { + html_BR(); + } + + Label_Gpio_toHtml(labels[i], formatGpioLabel(spi_gpios[i], false)); + } + + if (showCSpin) { + html_BR(); + Label_Gpio_toHtml(F("CS"), formatGpioLabel(Settings.TaskDevicePin1[x], false)); + } + } +} + +# endif // if FEATURE_SPI + +# if FEATURE_SPI + +static void devicePage_show_SPI_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (Device[DeviceIndex].isSPI() + && (Settings.getNrConfiguredSPI_buses() == 0)) { + addFormNote(F("SPI Bus not configured yet (Hardware page).")); + } + # ifdef ESP32 + + if (Device[DeviceIndex].SpiBusSelect && (getSPIBusCount() > 1) && (Settings.getNrConfiguredSPI_buses() != 0)) { + uint8_t spiBus = Settings.getSPIBusForTask(taskIndex); + SPIInterfaceSelector(F("SPI Bus"), + F("pspibus"), + spiBus); + } + # endif // ifdef ESP32 +} + +# endif // if FEATURE_SPI + +static void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.PullUpOption) + { + addFormCheckBox(F("Internal PullUp"), F("TDPPU"), Settings.TaskDevicePin1PullUp[taskIndex]); // ="taskdevicepin1pullup" + addFormNote(F("Best to (also) configure pull-up on Hardware tab under \"GPIO boot states\"")); + # if defined(ESP8266) + + if ((Settings.TaskDevicePin1[taskIndex] == 16) || (Settings.TaskDevicePin2[taskIndex] == 16) || + (Settings.TaskDevicePin3[taskIndex] == 16)) { + addFormNote(F("PullDown for GPIO-16 (D0)")); + } + # endif // if defined(ESP8266) + } + + if (device.InverseLogicOption) + { + addFormCheckBox(F("Inversed Logic"), F("TDPI"), Settings.TaskDevicePin1Inversed[taskIndex]); // ="taskdevicepin1inversed" + addFormNote(F("Will go into effect on next input change.")); + } + +# if FEATURE_SPI + + if (device.isSPI()) { + devicePage_show_SPI_config(taskIndex, DeviceIndex); + } +# endif // if FEATURE_SPI + + if (device.connectedToGPIOpins()) { + // get descriptive GPIO-names from plugin + struct EventStruct TempEvent(taskIndex); + + TempEvent.String1 = F("1st GPIO"); + TempEvent.String2 = F("2nd GPIO"); + TempEvent.String3 = F("3rd GPIO"); + String dummy; + PluginCall(PLUGIN_GET_DEVICEGPIONAMES, &TempEvent, dummy); + + if (device.usesTaskDevicePin(1)) { + PinSelectPurpose purpose = device.getPinSelectPurpose(1); // PinSelectPurpose::Generic; + + if (device.isSerial()) + { + // Pin1 = GPIO <--- TX + purpose = PinSelectPurpose::Serial_input; + } +# if FEATURE_SPI + else if (device.isSPI()) + { + // All selectable SPI pins are output only + purpose = PinSelectPurpose::Generic_output; + } +# endif // if FEATURE_SPI + + addFormPinSelect(purpose, TempEvent.String1, F("taskdevicepin1"), Settings.TaskDevicePin1[taskIndex]); + } + + if (device.usesTaskDevicePin(2)) { + PinSelectPurpose purpose = device.getPinSelectPurpose(2); // PinSelectPurpose::Generic; + + if (device.isSerial()) + { + // Serial Pin2 = GPIO ---> RX + purpose = PinSelectPurpose::Serial_output; + } +# if FEATURE_SPI + + if (device.isSPI()) + { + // SPI only needs output pins + purpose = PinSelectPurpose::Generic_output; + } +# endif // if FEATURE_SPI + addFormPinSelect(purpose, TempEvent.String2, F("taskdevicepin2"), Settings.TaskDevicePin2[taskIndex]); + } + + if (device.usesTaskDevicePin(3)) { + PinSelectPurpose purpose = device.getPinSelectPurpose(3); // PinSelectPurpose::Generic; +# if FEATURE_SPI + + if (device.isSPI()) + { + // SPI only needs output pins + purpose = PinSelectPurpose::Generic_output; + } +# endif // if FEATURE_SPI + addFormPinSelect(purpose, TempEvent.String3, F("taskdevicepin3"), Settings.TaskDevicePin3[taskIndex]); + } + } +} + +# ifdef PLUGIN_USES_SERIAL + +static void devicePage_show_serial_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + struct EventStruct TempEvent(taskIndex); + + String webformLoadString; + + PluginCall(PLUGIN_WEBFORM_PRE_SERIAL_PARAMS, &TempEvent, webformLoadString); + + serialHelper_webformLoad(&TempEvent, Device[DeviceIndex].SerialPortsAllowed); + + PluginCall(PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, &TempEvent, webformLoadString); +} + +# endif // ifdef PLUGIN_USES_SERIAL + + +# if FEATURE_I2C + +# 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)) { + { + const __FlashStringHelper *i2c_mux_channels[] = { + F("Single channel"), + F("Multiple channels") }; + constexpr int i2c_mux_channelOptions[] = { 0, 1 }; + int i2c_mux_channelCount = 1; + + if (Settings.getI2CMultiplexerType(i2cBus) == I2C_MULTIPLEXER_PCA9540) { + muxPortsOption = false; // force off + } else { + i2c_mux_channelCount++; + } + FormSelectorOptions selector( + i2c_mux_channelCount, + i2c_mux_channels, + i2c_mux_channelOptions); + selector.reloadonchange = true; + selector.addFormSelector( + F("Multiplexer channels"), + F("taskdeviceflags1"), + muxPortsOption ? 1 : 0); + } + + if (muxPortsOption) { + addRowLabel(F("Select connections"), EMPTY_STRING); + html_table(EMPTY_STRING, false); // Sub-table + html_table_header(F("Channel"), 100); + html_table_header(F("Enable"), 80); + html_table_header(F("Channel"), 100); + html_table_header(F("Enable"), 80); + + for (int x = 0; x < I2CMultiplexerMaxChannels(i2cBus); x++) { + if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels + html_TD(); + addHtml(concat(F("Channel "), x)); + html_TD(); + addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(taskDeviceI2CMuxPort, x), false); + } + html_end_table(); + } else { + const uint32_t mux_max = I2CMultiplexerMaxChannels(i2cBus); + String i2c_mux_portoptions[mux_max + 1]; + int i2c_mux_portchoices[mux_max + 1]; + i2c_mux_portoptions[0] = F("(Not connected via multiplexer)"); + i2c_mux_portchoices[0] = -1; + + for (uint32_t x = 0; x < mux_max; x++) { + const uint32_t mux_opt = x + 1; + i2c_mux_portoptions[mux_opt] = concat(F("Channel "), x); + i2c_mux_portchoices[mux_opt] = x; + } + + if (taskDeviceI2CMuxPort >= static_cast(mux_max)) { taskDeviceI2CMuxPort = -1; } // Reset if out of range + const FormSelectorOptions selector( + mux_max + 1, + i2c_mux_portoptions, + i2c_mux_portchoices); + selector.addFormSelector( + F("Connected to"), + F("taskdevicei2cmuxport"), + taskDeviceI2CMuxPort); + } + } +} + +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 + +static void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + struct EventStruct TempEvent(taskIndex); + + addFormSubHeader(F("I2C options")); + + if (Settings.getNrConfiguredI2C_buses() == 0) { + addFormNote(F("I2C Bus is not configured yet (Hardware page).")); + } + + String dummy; + + PluginCall(PLUGIN_WEBFORM_SHOW_I2C_PARAMS, &TempEvent, dummy); + addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_SPI_bus_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); + + if (Device[DeviceIndex].I2CMax100kHz) { + addFormNote(F("This device is specified for max. 100 kHz operation!")); + } +# if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER + uint8_t i2cBus = 0; +# endif + + # if FEATURE_I2C_MULTIPLE + + if (!Device[DeviceIndex].I2CNoBusSelection) { // If the device doesn't disallow bus selection + i2cBus = Settings.getI2CInterface(taskIndex); + I2CInterfaceSelector(F("I2C Bus"), + F("pi2cbus"), + i2cBus, + true); + } + # endif // if FEATURE_I2C_MULTIPLE + # if FEATURE_I2CMULTIPLEXER + ShowI2CMultiplexerUI(i2cBus, + bitRead(Settings.I2C_SPI_bus_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL), + Settings.I2C_Multiplexer_Channel[taskIndex]); + # endif // if FEATURE_I2CMULTIPLEXER +} + +# endif // if FEATURE_I2C + +static void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + struct EventStruct TempEvent(taskIndex); + int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); + + switch (Device[DeviceIndex].OutputDataType) + { + case Output_Data_type_t::Default: + return; + case Output_Data_type_t::Simple: + + if (pconfigIndex >= 0) { + sensorTypeHelper_webformLoad_simple(&TempEvent, pconfigIndex); + return; + } + break; + case Output_Data_type_t::All: + { + if (pconfigIndex >= 0) { + sensorTypeHelper_webformLoad_allTypes(&TempEvent, pconfigIndex); + return; + } + break; + } + } + addFormSubHeader(F("Output Configuration")); + String dummy; + PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, &TempEvent, dummy); +} + +# if FEATURE_PLUGIN_STATS + +static void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (Device[DeviceIndex].PluginStats) + { + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); + + if (taskData != nullptr) { + if (taskData->hasPluginStats()) { + addFormSubHeader(F("Statistics")); + } + # if FEATURE_CHART_JS + + if (taskData->nrSamplesPresent() > 0) { + addRowColspan(2); + + // addRowLabel(F("Historic data")); + taskData->plot_ChartJS(); + addHtml(F("")); + } + # endif // if FEATURE_CHART_JS + + struct EventStruct TempEvent(taskIndex); + String dummy; + bool somethingAdded = false; + + if (!PluginCall(PLUGIN_WEBFORM_LOAD_SHOW_STATS, &TempEvent, dummy)) { + somethingAdded = taskData->webformLoad_show_stats(&TempEvent); + } else { somethingAdded = true; } + + if (somethingAdded) { + if (taskData->hasPeaks()) { + addFormNote(strformat( + F("Peak values recorded since last \"%s.resetpeaks\"."), + getTaskDeviceName(taskIndex).c_str())); + } + } + } + } +} + +# endif // if FEATURE_PLUGIN_STATS + +static void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) { return; } + + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.SendDataOption) + { + addFormSubHeader(F("Data Acquisition")); + + if (device.ErrorStateValues) { + struct EventStruct TempEvent(taskIndex); + String dummy; + + PluginCall(PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, &TempEvent, dummy); // Show extra settings for Error State Value options + } + + addFormCheckBox(F("Single event with all values"), F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex)); + addFormNote(strformat( + F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"), + getTaskDeviceName(taskIndex).c_str())); + + bool sendDerived{}; + # if FEATURE_STRING_VARIABLES + + if (!device.HideDerivedValues) { + sendDerived = Settings.EventAndLogDerivedTaskValues(taskIndex); + addFormCheckBox(F("Show derived values"), F("TSDV"), Settings.ShowDerivedTaskValues(taskIndex)); + addFormCheckBox(F("Event & Log derived values"), F("TELD"), sendDerived); + } + # endif // if FEATURE_STRING_VARIABLES + + const uint8_t valueCount = getValueCountForTask(taskIndex); + bool separatorAdded = false; + + for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX && (valueCount > 0 || sendDerived); controllerNr++) + { + if (Settings.Protocol[controllerNr] != 0) + { + if (!separatorAdded) { + addFormSeparator(2); + } + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); + const bool showControllerIDX = validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).usesID && + (Settings.Protocol[controllerNr] != 0); + # if FEATURE_MQTT_DISCOVER + const bool showMqttGroup = (validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).mqttAutoDiscover); + # endif // if FEATURE_MQTT_DISCOVER + separatorAdded = true; + html_TR_TD(); + addHtml(F("Send to Controller ")); + addHtml(getControllerSymbol(controllerNr)); + addHtmlDiv(F("note"), wrap_braces(getCPluginNameFromCPluginID(Settings.Protocol[controllerNr]) + F(", ") + // Most compact code... + (Settings.ControllerEnabled[controllerNr] ? F("enabled") : F("disabled")) + # if FEATURE_MQTT_DISCOVER + + (showMqttGroup ? F(", Auto Discovery") : F("")) + # endif // if FEATURE_MQTT_DISCOVER + )); + html_TD(); + + addHtml(F("")); // remove left padding 2x to align vertically with other inputs + html_TD(F("width:50px;padding-left:0")); + addCheckBox( + getPluginCustomArgName(F("TDSD"), controllerNr), // ="taskdevicesenddata" + Settings.TaskDeviceSendData[controllerNr][taskIndex]); + + # if FEATURE_STRING_VARIABLES + const bool allowSendDerived = !device.HideDerivedValues && + (validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).allowSendDerived); + # endif // if FEATURE_STRING_VARIABLES + + if (showControllerIDX + # if FEATURE_MQTT_DISCOVER + || showMqttGroup + # endif // if FEATURE_MQTT_DISCOVER + ) { + html_TD(); + addHtml( + # if FEATURE_MQTT_DISCOVER + showMqttGroup&& !showControllerIDX ? F("Group:") : + # endif // if FEATURE_MQTT_DISCOVER + F("IDX:")); + html_TD(); + addNumericBox( + getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" + Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); + } + # if FEATURE_MQTT && FEATURE_MQTT_DISCOVER + + if (showMqttGroup) { + html_TD(); + addHtml(F("Retained:")); + html_TD(); + addCheckBox(getPluginCustomArgName(F("TSRT"), controllerNr), Settings.SendRetainedTaskValues(taskIndex, controllerNr), false + # if FEATURE_TOOLTIPS + , F("Send values with Retain flag") + # endif // if FEATURE_TOOLTIPS + ); + } + # endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER + # if FEATURE_STRING_VARIABLES + + if (allowSendDerived) { + html_TD(); + addHtml(F("Send derived:")); + html_TD(); + addCheckBox(getPluginCustomArgName(F("TSND"), controllerNr), Settings.SendDerivedTaskValues(taskIndex, controllerNr), false + # if FEATURE_TOOLTIPS + , F("Send derived values") + # endif // if FEATURE_TOOLTIPS + ); + } + # endif // if FEATURE_STRING_VARIABLES + + # if FEATURE_MQTT_DISCOVER + + if (showMqttGroup && + (mqttDiscoveryController == controllerNr)) { + html_TD(); + addHtml(F("Resend MQTT Discovery:")); + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDDSC"), controllerNr), // ="taskdevicediscover" + false); + } + # endif // if FEATURE_MQTT_DISCOVER + html_end_table(); + } + } + } +} + +static void devicePage_show_interval_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) { return; } + + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.TimerOption) + { + // FIXME: shoudn't the max be ULONG_MAX because Settings.TaskDeviceTimer is an uint32_t? addFormNumericBox only supports ints + // for min and max specification + addFormNumericBox(F("Interval"), F("TDT"), Settings.TaskDeviceTimer[taskIndex], 0, 65535); // ="taskdevicetimer" + addUnit(F("sec")); + + if (device.TimerOptional) { + addHtml(F(" (Optional for this Device)")); + } + } +} + +static void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) { return; } + + // section: Values + const uint8_t valueCount = getValueCountForTask(taskIndex); + + const DeviceStruct& device = Device[DeviceIndex]; + + if (!device.Custom && (valueCount > 0)) + { + int colCount = 2; + addFormSubHeader(F("Values")); + html_end_table(); + html_table_class_normal(); + + // table header + addHtml(F("")); - } - # endif // if FEATURE_CHART_JS struct EventStruct TempEvent(taskIndex); String dummy; - bool somethingAdded = false; - if (!PluginCall(PLUGIN_WEBFORM_LOAD_SHOW_STATS, &TempEvent, dummy)) { - somethingAdded = taskData->webformLoad_show_stats(&TempEvent); - } else { somethingAdded = true; } + // May need to call PLUGIN_INIT, however we must make sure it is exited first + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - if (somethingAdded) { - if (taskData->hasPeaks()) { - addFormNote(strformat( - F("Peak values recorded since last \"%s.resetpeaks\"."), - getTaskDeviceName(taskIndex).c_str())); + if (Settings.TaskDeviceEnabled[taskIndex]) { + if (PluginCall(PLUGIN_INIT, &TempEvent, dummy)) { + PluginCall(PLUGIN_READ, &TempEvent, dummy); } } } } -} -# endif // if FEATURE_PLUGIN_STATS - -void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) { return; } - - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.SendDataOption) + // show all tasks as table + if (taskIndexNotSet) { - addFormSubHeader(F("Data Acquisition")); - - if (device.ErrorStateValues) { - struct EventStruct TempEvent(taskIndex); - String dummy; - - PluginCall(PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, &TempEvent, dummy); // Show extra settings for Error State Value options - } - - addFormCheckBox(F("Single event with all values"), F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex)); - addFormNote(strformat( - F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"), - getTaskDeviceName(taskIndex).c_str())); - - bool sendDerived{}; - # if FEATURE_STRING_VARIABLES - - if (!device.HideDerivedValues) { - sendDerived = Settings.EventAndLogDerivedTaskValues(taskIndex); - addFormCheckBox(F("Show derived values"), F("TSDV"), Settings.ShowDerivedTaskValues(taskIndex)); - addFormCheckBox(F("Event & Log derived values"), F("TELD"), sendDerived); - } - # endif // if FEATURE_STRING_VARIABLES - - const uint8_t valueCount = getValueCountForTask(taskIndex); - bool separatorAdded = false; - - for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX && (valueCount > 0 || sendDerived); controllerNr++) - { - if (Settings.Protocol[controllerNr] != 0) - { - if (!separatorAdded) { - addFormSeparator(2); - } - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); - const bool showControllerIDX = validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).usesID && - (Settings.Protocol[controllerNr] != 0); - # if FEATURE_MQTT_DISCOVER - const bool showMqttGroup = (validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).mqttAutoDiscover); - # endif // if FEATURE_MQTT_DISCOVER - separatorAdded = true; - html_TR_TD(); - addHtml(F("Send to Controller ")); - addHtml(getControllerSymbol(controllerNr)); - addHtmlDiv(F("note"), wrap_braces(getCPluginNameFromCPluginID(Settings.Protocol[controllerNr]) + F(", ") + // Most compact code... - (Settings.ControllerEnabled[controllerNr] ? F("enabled") : F("disabled")) - #if FEATURE_MQTT_DISCOVER - + (showMqttGroup ? F(", Auto Discovery") : F("")) - #endif // if FEATURE_MQTT_DISCOVER - )); - html_TD(); - - addHtml(F("
#")); + html_table_header(F("Name"), 500); + + if (device.FormulaOption) + { + html_table_header(F("Formula"), F("RTDPlugin/_Plugin.html#formula"), 500); + ++colCount; + } + + if (device.configurableDecimals()) + { + html_table_header(F("Decimals"), 30); + ++colCount; + } + +# if FEATURE_PLUGIN_STATS + + if (device.PluginStats) + { + html_table_header(F("Stats"), 30); + ++colCount; + html_table_header(F("Hide"), 30); + ++colCount; + html_table_header(F("Axis"), 30); + ++colCount; + } +# endif // if FEATURE_PLUGIN_STATS + + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE + html_table_header(F("Unit of Measure"), 300); + ++colCount; + EventStruct uomEvent(taskIndex); + String uomDummy; + bool limitedUom = PluginCall(PLUGIN_GET_UOM_GROUPS, &uomEvent, uomDummy); + + if (!limitedUom) { + PluginCall(PLUGIN_GET_DEVICEVTYPE, &uomEvent, uomDummy); // Get Sensor_VType + limitedUom = getDefaultUoMforSensorVType(&uomEvent); // Populate UoM groups for known Sensor_VTypes + # if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE + + if (!limitedUom) { + // Fill in standard Unit of measurement and Value Type, if possible + std::vector discoveryItems; + MQTT_DiscoveryGetDeviceVType(taskIndex, discoveryItems, getValueCountForTask(taskIndex), uomDummy); + + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + // Match varNr with the DiscoveryItems to find the Sensor_VType for the value + for (uint8_t j = 0; j < discoveryItems.size(); ++j) { + for (uint8_t k = 0; k < discoveryItems[j].valueCount; ++k) { // Can have multiple values for 1 VType + if (varNr == discoveryItems[j].varIndex + k) { + const String uom = getValueType2DefaultHAUoM(discoveryItems[j].VType); + const uint16_t uomGroup = getUoMGroupForUoM(uom); + const uint16_t uomGroup2 = getUoMGroupForUoM(Cache.getTaskVarUnitOfMeasure(taskIndex, varNr)); + + if (uomGroup > 0) { + uomEvent.Par64N[varNr] = (uint64_t)(1ULL << (uomGroup - 1024)) | (uomGroup2 > + 0 ? (uint64_t)(1ULL << (uomGroup2 - 1024)) : 0ULL); + limitedUom = true; + } else { + uomEvent.Par64N[varNr] = UOM_GROUP_ALL; + } + } + } + } + } + } + # endif // if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE + } + # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + # if FEATURE_CUSTOM_TASKVAR_VTYPE + + if (device.CustomVTypeVar) { + html_table_header(F("Value Type"), 100); + ++colCount; + } + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + # if FEATURE_MQTT_STATE_CLASS + + if (device.MqttStateClass) { + html_table_header(F("MQTT State Class"), 350); + ++colCount; + } + # endif // if FEATURE_MQTT_STATE_CLASS + + + // placeholder header + html_table_header(F("")); + ++colCount; + + # if FEATURE_CUSTOM_TASKVAR_VTYPE + std::vector singleOptions; + + if (device.CustomVTypeVar) { + // Build a list of all single-value available value VTypes from PR #5199 + constexpr uint8_t maxVType = static_cast(Sensor_VType::SENSOR_TYPE_NOT_SET); + singleOptions.push_back(0); // Empty/None value + + for (uint8_t i = 0; i < maxVType; ++i) { + if (getValueCountFromSensorType(static_cast(i), false) == 1) { + singleOptions.push_back(i); + } + } + } + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + # if FEATURE_PLUGIN_STATS + const __FlashStringHelper *chartAxis[] = { + F("L1"), + F("L2"), + F("L3"), + F("L4"), + F("R1"), + F("R2"), + F("R3"), + F("R4") + }; + constexpr size_t chartAxisCount = NR_ELEMENTS(chartAxis); + # endif // if FEATURE_PLUGIN_STATS + + # if FEATURE_MQTT_STATE_CLASS + const __FlashStringHelper *stateClasses[] = { + MQTT_sensor_StateClass(0), + MQTT_sensor_StateClass(1), + MQTT_sensor_StateClass(2), + MQTT_sensor_StateClass(3), + MQTT_sensor_StateClass(4), + }; + constexpr size_t stateCount = NR_ELEMENTS(stateClasses); + # endif // if FEATURE_MQTT_STATE_CLASS + + // table body + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + html_TR_TD(); + addHtmlInt(varNr + 1); + html_TD(); + { + const String id = getPluginCustomArgName(F("TDVN"), varNr); // ="taskdevicevaluename" + addTextBox(id, Cache.getTaskDeviceValueName(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); + } + + if (device.FormulaOption) + { + html_TD(); + const String id = getPluginCustomArgName(F("TDF"), varNr); // ="taskdeviceformula" + addTextBox(id, Cache.getTaskDeviceFormula(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); + } + + if (device.configurableDecimals()) + { + html_TD(); + const String id = getPluginCustomArgName(F("TDVD"), varNr); // ="taskdevicevaluedecimals" + addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); + } + +# if FEATURE_PLUGIN_STATS + + if (device.PluginStats) + { + PluginStats_Config_t cachedConfig = Cache.getPluginStatsConfig(taskIndex, varNr); + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDS"), varNr), // ="taskdevicestats" + cachedConfig.isEnabled()); + + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDSH"), varNr), // ="taskdevicestats Hidden" + cachedConfig.showHidden()); + + html_TD(); + + int selected = cachedConfig.getAxisIndex(); + + if (!cachedConfig.isLeft()) { + selected += 4; + } + + const FormSelectorOptions selector(chartAxisCount, chartAxis); + selector.addSelector( + getPluginCustomArgName(F("TDSA"), varNr), + selected); + } +# endif // if FEATURE_PLUGIN_STATS + + # if FEATURE_TASKVALUE_UNIT_OF_MEASURE + html_TD(); + const uint64_t uomGroups = limitedUom ? uomEvent.Par64N[varNr] : UOM_GROUP_ALL; + addUnitOfMeasureSelector(getPluginCustomArgName(F("TUOM"), varNr), Cache.getTaskVarUnitOfMeasure(taskIndex, varNr), uomGroups); + # endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE + + # if FEATURE_CUSTOM_TASKVAR_VTYPE + + if (device.CustomVTypeVar) { + html_TD(); + sensorTypeCategoriesHelper_Selector( + getPluginCustomArgName(F("TDTV"), varNr), + singleOptions.size(), + &singleOptions[0], + static_cast(Cache.getTaskVarCustomVType(taskIndex, varNr))); + } + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + # if FEATURE_MQTT_STATE_CLASS + + if (device.MqttStateClass) { + html_TD(); + + const FormSelectorOptions selectorSC(stateCount, stateClasses); + selectorSC.addSelector( + getPluginCustomArgName(F("TDSC"), varNr), + Cache.getTaskVarStateClass(taskIndex, varNr)); + } + # endif // if FEATURE_MQTT_STATE_CLASS + } + addFormSeparator(colCount); + } +} + // ******************************************************************************** // Show table with all selected Tasks/Devices // ******************************************************************************** -void handle_devicess_ShowAllTasksTable(uint8_t page) +static void handle_devicess_ShowAllTasksTable(uint8_t page) { serve_JS(JSfiles_e::UpdateSensorValuesDevicePage); html_table_class_multirow(); @@ -588,7 +1229,7 @@ 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 pluginID_t pid = 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(); @@ -616,9 +1257,9 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) if (pluginID_set) { // LoadTaskSettings(x); -#if FEATURE_SPI +# if FEATURE_SPI int8_t spi_gpios[3] { -1, -1, -1 }; -#endif +# endif struct EventStruct TempEvent(x); addEnabled(Settings.TaskDeviceEnabled[x] && validDeviceIndex(DeviceIndex)); @@ -643,16 +1284,19 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) addHtml(portDescr); } else { const DeviceStruct& device = Device[DeviceIndex]; -#if FEATURE_I2C +# if FEATURE_I2C + if (device.Type == DEVICE_TYPE_I2C) { format_I2C_port_description(x); - } else -#endif -#if FEATURE_SPI + } else +# endif // if FEATURE_I2C +# if FEATURE_SPI + if (device.isSPI()) { format_SPI_port_description(spi_gpios, Settings.getSPIBusForTask(x)); - } else -#endif + } else +# endif // if FEATURE_SPI + if (device.isSerial()) { # ifdef PLUGIN_USES_SERIAL addHtml(serialHelper_getSerialTypeLabel(&TempEvent)); @@ -722,15 +1366,15 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) switch (device.Type) { -#if FEATURE_I2C +# if FEATURE_I2C case DEVICE_TYPE_I2C: { format_I2C_pin_description(x); html_BR(); break; } -#endif -#if FEATURE_SPI +# endif // if FEATURE_I2C +# if FEATURE_SPI case DEVICE_TYPE_SPI3: showpin3 = !pluginHasGPIODescription; @@ -742,7 +1386,7 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) case DEVICE_TYPE_SPI: format_SPI_pin_description(spi_gpios, x, !pluginHasGPIODescription); break; -#endif +# endif // if FEATURE_SPI case DEVICE_TYPE_ANALOG: { # ifdef ESP8266 @@ -857,40 +1501,21 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) html_end_form(); } -# if FEATURE_ESPEASY_P2P +# if FEATURE_I2C -void format_originating_node(uint8_t remoteUnit) { - addHtml(F("Unit ")); - addHtmlInt(remoteUnit); - - if (remoteUnit != 255) { - const NodeStruct *node = Nodes.getNode(remoteUnit); - - if (node != nullptr) { - addHtml(F(" - ")); - addHtml(node->getNodeName()); - } else { - addHtml(F(" - Not Seen recently")); - } - } -} - -# endif // if FEATURE_ESPEASY_P2P - -#if FEATURE_I2C void format_I2C_port_description(taskIndex_t x) { addHtml(F("I2C")); - # if FEATURE_I2C_GET_ADDRESS + # if FEATURE_I2C_GET_ADDRESS const uint8_t i2cAddr = getTaskI2CAddress(x); if (i2cAddr > 0) { addHtml(' '); addHtml(formatToHex(i2cAddr, 2)); } - # endif // if FEATURE_I2C_GET_ADDRESS - # if FEATURE_I2CMULTIPLEXER - # if FEATURE_I2C_MULTIPLE + # endif // if FEATURE_I2C_GET_ADDRESS + # if FEATURE_I2CMULTIPLEXER + # if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = Settings.getI2CInterface(x); if ((i2cBus > 0) || Settings.isI2CEnabled(1) || Settings.isI2CEnabled(2)) { @@ -899,16 +1524,16 @@ void format_I2C_port_description(taskIndex_t x) addHtml(' '); addHtmlInt(i2cBus); } - # else // if FEATURE_I2C_MULTIPLE + # else // if FEATURE_I2C_MULTIPLE const uint8_t i2cBus = 0; - # endif // if FEATURE_I2C_MULTIPLE + # endif // if FEATURE_I2C_MULTIPLE if (isI2CMultiplexerEnabled(i2cBus) && I2CMultiplexerPortSelectedForTask(x)) { String mux; if (bitRead(Settings.I2C_SPI_bus_Flags[x], I2C_FLAGS_MUX_MULTICHANNEL)) { // Multi-channel mux = F("
Multiplexer channel(s)"); - uint8_t b = 0; // For adding lineBreaks + uint8_t b = 0; // For adding lineBreaks for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(i2cBus); ++c) { if (bitRead(Settings.I2C_Multiplexer_Channel[x], c)) { @@ -922,70 +1547,15 @@ void format_I2C_port_description(taskIndex_t x) } addHtml(mux); } - # endif // if FEATURE_I2CMULTIPLEXER + # endif // if FEATURE_I2CMULTIPLEXER } -#endif -#if FEATURE_SPI -void format_SPI_port_description(int8_t spi_gpios[3], uint8_t spi_bus) -{ - if (!Settings.getSPI_pins(spi_gpios, spi_bus)) { - addHtml(F("SPI (Not enabled)")); - return; - } - # ifdef ESP32 - addHtml(getSPI_optionToShortString(Settings.getSPISelection(spi_bus), spi_bus)); - # endif // ifdef ESP32 - # ifdef ESP8266 - addHtml(F("SPI")); - # endif // ifdef ESP8266 -} -#endif - -#if FEATURE_I2C -void format_I2C_pin_description(taskIndex_t x) -{ - # if FEATURE_I2C_MULTIPLE - const uint8_t i2cBus = Settings.getI2CInterface(x); - # else - const uint8_t i2cBus = 0; - # endif // if FEATURE_I2C_MULTIPLE - - if (checkI2CConfigValid_toHtml(x)) { - Label_Gpio_toHtml(F("SDA"), formatGpioLabel(Settings.getI2CSdaPin(i2cBus), false)); - html_BR(); - Label_Gpio_toHtml(F("SCL"), formatGpioLabel(Settings.getI2CSclPin(i2cBus), false)); - } -} -#endif - -#if FEATURE_SPI -void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSpin) -{ - const uint8_t spi_bus = Settings.getSPIBusForTask(x); - if (Settings.getSPISelection(spi_bus) != SPI_Options_e::None) { - const __FlashStringHelper*labels[] = { F("CLK"), F("MISO"), F("MOSI") }; - - for (size_t i = 0; i < NR_ELEMENTS(labels); ++i) { - if (i != 0) { - html_BR(); - } - - Label_Gpio_toHtml(labels[i], formatGpioLabel(spi_gpios[i], false)); - } - - if (showCSpin) { - html_BR(); - Label_Gpio_toHtml(F("CS"), formatGpioLabel(Settings.TaskDevicePin1[x], false)); - } - } -} -#endif +# endif // if FEATURE_I2C // ******************************************************************************** // Show the task settings page // ******************************************************************************** -void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) +static void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) { if (!validTaskIndex(taskIndex)) { return; } @@ -1110,11 +1680,11 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) addPinConfig = true; } - if (addPinConfig -#if FEATURE_I2C - || (device.Type == DEVICE_TYPE_I2C) -#endif - ) { + if (addPinConfig +# if FEATURE_I2C + || (device.Type == DEVICE_TYPE_I2C) +# endif + ) { if (device.isSerial()) { # ifdef PLUGIN_USES_SERIAL devicePage_show_serial_config(taskIndex, DeviceIndex); @@ -1126,18 +1696,19 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) addPinConfig = false; html_add_script(F("document.getElementById('serPort').onchange();"), false); - } else -#if FEATURE_I2C + } else +# if FEATURE_I2C if (device.Type == DEVICE_TYPE_I2C) { devicePage_show_pin_config(taskIndex, DeviceIndex); addPinConfig = false; -#if FEATURE_I2C +# if FEATURE_I2C + if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { devicePage_show_I2C_config(taskIndex, DeviceIndex); } -#endif +# endif // if FEATURE_I2C } -#endif +# endif // if FEATURE_I2C if (addPinConfig) { devicePage_show_pin_config(taskIndex, DeviceIndex); } @@ -1220,719 +1791,178 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) serve_JS(JSfiles_e::SplitPasteInput); } -void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - const DeviceStruct& device = Device[DeviceIndex]; +void handle_devices() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_devices")); + # endif // ifndef BUILD_NO_RAM_TRACKER - if (device.PullUpOption) + if (!startStream_send_stdTemplate(MENU_INDEX_DEVICES)) { return; } + + // char tmpString[41]; + + + // String taskindex = webArg(F("index")); + + pluginID_t taskdevicenumber; + + if (hasArg(F("del"))) { + taskdevicenumber.setInvalid(); + } + else { + taskdevicenumber = pluginID_t::toPluginID(getFormItemInt(F("TDNUM"), 0)); + } + + + // String taskdeviceid[CONTROLLER_MAX]; + // String taskdevicepin1 = webArg(F("taskdevicepin1")); // "taskdevicepin*" should not be changed because it is uses by plugins + // and expected to be saved by this code + // String taskdevicepin2 = webArg(F("taskdevicepin2")); + // String taskdevicepin3 = webArg(F("taskdevicepin3")); + // String taskdevicepin1pullup = webArg(F("TDPPU")); + // String taskdevicepin1inversed = webArg(F("TDPI")); + // String taskdevicename = webArg(F("TDN")); + // String taskdeviceport = webArg(F("TDP")); + // String taskdeviceformula[VARS_PER_TASK]; + // String taskdevicevaluename[VARS_PER_TASK]; + // String taskdevicevaluedecimals[VARS_PER_TASK]; + // String taskdevicesenddata[CONTROLLER_MAX]; + // String taskdeviceglobalsync = webArg(F("TDGS")); + // String taskdeviceenabled = webArg(F("TDE")); + + // for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) + // { + // char argc[25]; + // String arg = F("TDF"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdeviceformula[varNr] = webArg(argc); + // + // arg = F("TDVN"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicevaluename[varNr] = webArg(argc); + // + // arg = F("TDVD"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicevaluedecimals[varNr] = webArg(argc); + // } + + // for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + // { + // char argc[25]; + // String arg = F("TDID"); + // arg += controllerNr + 1; + // arg.toCharArray(argc, 25); + // taskdeviceid[controllerNr] = webArg(argc); + // + // arg = F("TDSD"); + // arg += controllerNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicesenddata[controllerNr] = webArg(argc); + // } + + uint8_t page = getFormItemInt(F("page"), 0); + + if (page == 0) { + page = 1; + } + uint8_t setpage = getFormItemInt(F("setpage"), 0); + + if (setpage > 0) { - addFormCheckBox(F("Internal PullUp"), F("TDPPU"), Settings.TaskDevicePin1PullUp[taskIndex]); // ="taskdevicepin1pullup" - addFormNote(F("Best to (also) configure pull-up on Hardware tab under \"GPIO boot states\"")); - # if defined(ESP8266) - - if ((Settings.TaskDevicePin1[taskIndex] == 16) || (Settings.TaskDevicePin2[taskIndex] == 16) || - (Settings.TaskDevicePin3[taskIndex] == 16)) { - addFormNote(F("PullDown for GPIO-16 (D0)")); + if (setpage <= (TASKS_MAX / TASKS_PER_PAGE)) { + page = setpage; } - # endif // if defined(ESP8266) + else { + page = TASKS_MAX / TASKS_PER_PAGE; + } + } + const int edit = getFormItemInt(F("edit"), 0); + + // taskIndex in the URL is 1 ... TASKS_MAX + // For use in other functions, set it to 0 ... (TASKS_MAX - 1) + taskIndex_t taskIndex = getFormItemInt(F("index"), 0); + boolean taskIndexNotSet = taskIndex == 0; + + const bool nosave = isFormItemChecked(F("nosave")); + + if (!taskIndexNotSet) { + --taskIndex; + + // LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date } - if (device.InverseLogicOption) + // FIXME TD-er: Might have to clear any caches here. + if ((edit != 0) && !taskIndexNotSet) // when form submitted { - addFormCheckBox(F("Inversed Logic"), F("TDPI"), Settings.TaskDevicePin1Inversed[taskIndex]); // ="taskdevicepin1inversed" - addFormNote(F("Will go into effect on next input change.")); - } - -#if FEATURE_SPI - if (device.isSPI()) { - devicePage_show_SPI_config(taskIndex, DeviceIndex); - } -#endif - - if (device.connectedToGPIOpins()) { - // get descriptive GPIO-names from plugin - struct EventStruct TempEvent(taskIndex); - - TempEvent.String1 = F("1st GPIO"); - TempEvent.String2 = F("2nd GPIO"); - TempEvent.String3 = F("3rd GPIO"); - String dummy; - PluginCall(PLUGIN_GET_DEVICEGPIONAMES, &TempEvent, dummy); - - if (device.usesTaskDevicePin(1)) { - PinSelectPurpose purpose = device.getPinSelectPurpose(1); // PinSelectPurpose::Generic; - - if (device.isSerial()) - { - // Pin1 = GPIO <--- TX - purpose = PinSelectPurpose::Serial_input; - } -#if FEATURE_SPI - else if (device.isSPI()) - { - // All selectable SPI pins are output only - purpose = PinSelectPurpose::Generic_output; - } -#endif - - addFormPinSelect(purpose, TempEvent.String1, F("taskdevicepin1"), Settings.TaskDevicePin1[taskIndex]); - } - - if (device.usesTaskDevicePin(2)) { - PinSelectPurpose purpose = device.getPinSelectPurpose(2); // PinSelectPurpose::Generic; - - if (device.isSerial()) - { - // Serial Pin2 = GPIO ---> RX - purpose = PinSelectPurpose::Serial_output; - } -#if FEATURE_SPI - if (device.isSPI()) - { - // SPI only needs output pins - purpose = PinSelectPurpose::Generic_output; - } -#endif - addFormPinSelect(purpose, TempEvent.String2, F("taskdevicepin2"), Settings.TaskDevicePin2[taskIndex]); - } - - if (device.usesTaskDevicePin(3)) { - PinSelectPurpose purpose = device.getPinSelectPurpose(3); // PinSelectPurpose::Generic; -#if FEATURE_SPI - if (device.isSPI()) - { - // SPI only needs output pins - purpose = PinSelectPurpose::Generic_output; - } -#endif - addFormPinSelect(purpose, TempEvent.String3, F("taskdevicepin3"), Settings.TaskDevicePin3[taskIndex]); - } - } -} - -# ifdef PLUGIN_USES_SERIAL - -void devicePage_show_serial_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - struct EventStruct TempEvent(taskIndex); - - String webformLoadString; - - PluginCall(PLUGIN_WEBFORM_PRE_SERIAL_PARAMS, &TempEvent, webformLoadString); - - serialHelper_webformLoad(&TempEvent, Device[DeviceIndex].SerialPortsAllowed); - - PluginCall(PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, &TempEvent, webformLoadString); -} - -# endif // ifdef PLUGIN_USES_SERIAL - -#if FEATURE_SPI -void devicePage_show_SPI_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (Device[DeviceIndex].isSPI() - && Settings.getNrConfiguredSPI_buses() == 0) { - addFormNote(F("SPI Bus not configured yet (Hardware page).")); - } - #ifdef ESP32 - if (Device[DeviceIndex].SpiBusSelect && getSPIBusCount() > 1 && (Settings.getNrConfiguredSPI_buses() != 0)) { - uint8_t spiBus = Settings.getSPIBusForTask(taskIndex); - SPIInterfaceSelector(F("SPI Bus"), - F("pspibus"), - spiBus); - } - #endif // ifdef ESP32 -} -#endif - -#if FEATURE_I2C -void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - struct EventStruct TempEvent(taskIndex); - - addFormSubHeader(F("I2C options")); - - if (Settings.getNrConfiguredI2C_buses() == 0) { - addFormNote(F("I2C Bus is not configured yet (Hardware page).")); - } - - String dummy; - - PluginCall(PLUGIN_WEBFORM_SHOW_I2C_PARAMS, &TempEvent, dummy); - addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_SPI_bus_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); - - if (Device[DeviceIndex].I2CMax100kHz) { - addFormNote(F("This device is specified for max. 100 kHz operation!")); - } -# if FEATURE_I2C_MULTIPLE || FEATURE_I2CMULTIPLEXER - uint8_t i2cBus = 0; -# endif - - # if FEATURE_I2C_MULTIPLE - - if (!Device[DeviceIndex].I2CNoBusSelection) { // If the device doesn't disallow bus selection - i2cBus = Settings.getI2CInterface(taskIndex); - I2CInterfaceSelector(F("I2C Bus"), - F("pi2cbus"), - i2cBus, - true); - } - # endif // if FEATURE_I2C_MULTIPLE - # if FEATURE_I2CMULTIPLEXER - ShowI2CMultiplexerUI(i2cBus, - bitRead(Settings.I2C_SPI_bus_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)) { + if (Settings.getPluginID_for_task(taskIndex) != taskdevicenumber) { - const __FlashStringHelper *i2c_mux_channels[] = { - F("Single channel"), - F("Multiple channels") }; - constexpr int i2c_mux_channelOptions[] = { 0, 1 }; - int i2c_mux_channelCount = 1; + // change of device: cleanup old device and reset default settings + setTaskDevice_to_TaskIndex(taskdevicenumber, taskIndex); +# if FEATURE_I2C + const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); - if (Settings.getI2CMultiplexerType(i2cBus) == I2C_MULTIPLEXER_PCA9540) { - muxPortsOption = false; // force off + if (validDeviceIndex(DeviceIndex)) { + const DeviceStruct& device = Device[DeviceIndex]; + + if ((device.Type == DEVICE_TYPE_I2C) && device.I2CMax100kHz) { // 100 kHz-only I2C device? + bitWrite(Settings.I2C_SPI_bus_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED, 1); // Then: Enable Force Slow I2C speed checkbox by default + } + } +# endif // if FEATURE_I2C + } + else if (taskdevicenumber != INVALID_PLUGIN_ID) // save settings + { + handle_devices_CopySubmittedSettings(taskIndex, taskdevicenumber); + } + + if (taskdevicenumber != INVALID_PLUGIN_ID) { + // Task index has a task device number, so it makes sense to save. + // N.B. When calling delete, the settings were already saved. + if (nosave) { + Cache.updateExtraTaskSettingsCache(); } else { - i2c_mux_channelCount++; + addHtmlError(SaveTaskSettings(taskIndex)); + addHtmlError(SaveSettings()); } - FormSelectorOptions selector( - i2c_mux_channelCount, - i2c_mux_channels, - i2c_mux_channelOptions); - selector.reloadonchange = true; - selector.addFormSelector( - F("Multiplexer channels"), - F("taskdeviceflags1"), - muxPortsOption ? 1 : 0); - } - - if (muxPortsOption) { - addRowLabel(F("Select connections"), EMPTY_STRING); - html_table(EMPTY_STRING, false); // Sub-table - html_table_header(F("Channel"), 100); - html_table_header(F("Enable"), 80); - html_table_header(F("Channel"), 100); - html_table_header(F("Enable"), 80); - - for (int x = 0; x < I2CMultiplexerMaxChannels(i2cBus); x++) { - if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels - html_TD(); - addHtml(concat(F("Channel "), x)); - html_TD(); - addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(taskDeviceI2CMuxPort, x), false); - } - html_end_table(); - } else { - const uint32_t mux_max = I2CMultiplexerMaxChannels(i2cBus); - String i2c_mux_portoptions[mux_max + 1]; - int i2c_mux_portchoices[mux_max + 1]; - i2c_mux_portoptions[0] = F("(Not connected via multiplexer)"); - i2c_mux_portchoices[0] = -1; - - for (uint32_t x = 0; x < mux_max; x++) { - const uint32_t mux_opt = x + 1; - i2c_mux_portoptions[mux_opt] = concat(F("Channel "), x); - i2c_mux_portchoices[mux_opt] = x; - } - - if (taskDeviceI2CMuxPort >= static_cast(mux_max)) { taskDeviceI2CMuxPort = -1; } // Reset if out of range - const FormSelectorOptions selector( - mux_max + 1, - i2c_mux_portoptions, - i2c_mux_portchoices); - selector.addFormSelector( - F("Connected to"), - F("taskdevicei2cmuxport"), - taskDeviceI2CMuxPort); - } - } -} - -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 -#endif // if FEATURE_I2C - -void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - struct EventStruct TempEvent(taskIndex); - int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); - - switch (Device[DeviceIndex].OutputDataType) - { - case Output_Data_type_t::Default: - return; - case Output_Data_type_t::Simple: - - if (pconfigIndex >= 0) { - sensorTypeHelper_webformLoad_simple(&TempEvent, pconfigIndex); - return; - } - break; - case Output_Data_type_t::All: - { - if (pconfigIndex >= 0) { - sensorTypeHelper_webformLoad_allTypes(&TempEvent, pconfigIndex); - return; - } - break; - } - } - addFormSubHeader(F("Output Configuration")); - String dummy; - PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, &TempEvent, dummy); -} - -# if FEATURE_PLUGIN_STATS - -void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (Device[DeviceIndex].PluginStats) - { - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); - - if (taskData != nullptr) { - if (taskData->hasPluginStats()) { - addFormSubHeader(F("Statistics")); - } - # if FEATURE_CHART_JS - - if (taskData->nrSamplesPresent() > 0) { - addRowColspan(2); - - // addRowLabel(F("Historic data")); - taskData->plot_ChartJS(); - addHtml(F("
")); // remove left padding 2x to align vertically with other inputs - html_TD(F("width:50px;padding-left:0")); - addCheckBox( - getPluginCustomArgName(F("TDSD"), controllerNr), // ="taskdevicesenddata" - Settings.TaskDeviceSendData[controllerNr][taskIndex]); - - # if FEATURE_STRING_VARIABLES - const bool allowSendDerived = !device.HideDerivedValues && - (validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).allowSendDerived); - # endif // if FEATURE_STRING_VARIABLES - - if (showControllerIDX - # if FEATURE_MQTT_DISCOVER - || showMqttGroup - # endif // if FEATURE_MQTT_DISCOVER - ) { - html_TD(); - addHtml( - # if FEATURE_MQTT_DISCOVER - showMqttGroup && !showControllerIDX ? F("Group:") : - # endif // if FEATURE_MQTT_DISCOVER - F("IDX:")); - html_TD(); - addNumericBox( - getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" - Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); - } - # if FEATURE_MQTT && FEATURE_MQTT_DISCOVER - - if (showMqttGroup) { - html_TD(); - addHtml(F("Retained:")); - html_TD(); - addCheckBox(getPluginCustomArgName(F("TSRT"), controllerNr), Settings.SendRetainedTaskValues(taskIndex, controllerNr), false - # if FEATURE_TOOLTIPS - , F("Send values with Retain flag") - # endif // if FEATURE_TOOLTIPS - ); - } - # endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER - # if FEATURE_STRING_VARIABLES - - if (allowSendDerived) { - html_TD(); - addHtml(F("Send derived:")); - html_TD(); - addCheckBox(getPluginCustomArgName(F("TSND"), controllerNr), Settings.SendDerivedTaskValues(taskIndex, controllerNr), false - # if FEATURE_TOOLTIPS - , F("Send derived values") - # endif // if FEATURE_TOOLTIPS - ); - } - # endif // if FEATURE_STRING_VARIABLES - - # if FEATURE_MQTT_DISCOVER - - if (showMqttGroup && - (mqttDiscoveryController == controllerNr)) { - html_TD(); - addHtml(F("Resend MQTT Discovery:")); - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDDSC"), controllerNr), // ="taskdevicediscover" - false); - } - # endif // if FEATURE_MQTT_DISCOVER - html_end_table(); - } - } + handle_devicess_ShowAllTasksTable(page); } -} -void devicePage_show_interval_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) { return; } - - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.TimerOption) + // Show edit form if a specific entry is chosen with the edit button + else { - // FIXME: shoudn't the max be ULONG_MAX because Settings.TaskDeviceTimer is an uint32_t? addFormNumericBox only supports ints - // for min and max specification - addFormNumericBox(F("Interval"), F("TDT"), Settings.TaskDeviceTimer[taskIndex], 0, 65535); // ="taskdevicetimer" - addUnit(F("sec")); - - if (device.TimerOptional) { - addHtml(F(" (Optional for this Device)")); - } + handle_devices_TaskSettingsPage(taskIndex, page); } -} -void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) { return; } + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_devices")); + # endif // ifndef BUILD_NO_RAM_TRACKER +# ifndef BUILD_NO_DEBUG - // section: Values - const uint8_t valueCount = getValueCountForTask(taskIndex); - - const DeviceStruct& device = Device[DeviceIndex]; - - if (!device.Custom && (valueCount > 0)) - { - int colCount = 2; - addFormSubHeader(F("Values")); - html_end_table(); - html_table_class_normal(); - - // table header - addHtml(F("
#")); - html_table_header(F("Name"), 500); - - if (device.FormulaOption) - { - html_table_header(F("Formula"), F("RTDPlugin/_Plugin.html#formula"), 500); - ++colCount; - } - - if (device.configurableDecimals()) - { - html_table_header(F("Decimals"), 30); - ++colCount; - } - -# if FEATURE_PLUGIN_STATS - - if (device.PluginStats) - { - html_table_header(F("Stats"), 30); - ++colCount; - html_table_header(F("Hide"), 30); - ++colCount; - html_table_header(F("Axis"), 30); - ++colCount; - } -# endif // if FEATURE_PLUGIN_STATS - - # if FEATURE_TASKVALUE_UNIT_OF_MEASURE - html_table_header(F("Unit of Measure"), 300); - ++colCount; - EventStruct uomEvent(taskIndex); - String uomDummy; - bool limitedUom = PluginCall(PLUGIN_GET_UOM_GROUPS, &uomEvent, uomDummy); - if (!limitedUom) { - PluginCall(PLUGIN_GET_DEVICEVTYPE, &uomEvent, uomDummy); // Get Sensor_VType - limitedUom = getDefaultUoMforSensorVType(&uomEvent); // Populate UoM groups for known Sensor_VTypes - #if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE - if (!limitedUom) { - // Fill in standard Unit of measurement and Value Type, if possible - std::vector discoveryItems; - MQTT_DiscoveryGetDeviceVType(taskIndex, discoveryItems, getValueCountForTask(taskIndex), uomDummy); - - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { - // Match varNr with the DiscoveryItems to find the Sensor_VType for the value - for (uint8_t j = 0; j < discoveryItems.size(); ++j) { - for (uint8_t k = 0; k < discoveryItems[j].valueCount; ++k) { // Can have multiple values for 1 VType - if (varNr == discoveryItems[j].varIndex + k) { - const String uom = getValueType2DefaultHAUoM(discoveryItems[j].VType); - const uint16_t uomGroup = getUoMGroupForUoM(uom); - const uint16_t uomGroup2 = getUoMGroupForUoM(Cache.getTaskVarUnitOfMeasure(taskIndex, varNr)); - - if (uomGroup > 0) { - uomEvent.Par64N[varNr] = (uint64_t)(1ULL << (uomGroup - 1024)) | (uomGroup2 > 0 ? (uint64_t)(1ULL << (uomGroup2 - 1024)) : 0ULL); - limitedUom = true; - } else { - uomEvent.Par64N[varNr] = UOM_GROUP_ALL; - } - } - } - } - } - } - #endif // if FEATURE_MQTT_DISCOVER && FEATURE_CUSTOM_TASKVAR_VTYPE - } - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - # if FEATURE_CUSTOM_TASKVAR_VTYPE - - if (device.CustomVTypeVar) { - html_table_header(F("Value Type"), 100); - ++colCount; - } - # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE - - # if FEATURE_MQTT_STATE_CLASS - - if (device.MqttStateClass) { - html_table_header(F("MQTT State Class"), 350); - ++colCount; - } - # endif // if FEATURE_MQTT_STATE_CLASS - - - // placeholder header - html_table_header(F("")); - ++colCount; - - # if FEATURE_CUSTOM_TASKVAR_VTYPE - std::vector singleOptions; - - if (device.CustomVTypeVar) { - // Build a list of all single-value available value VTypes from PR #5199 - constexpr uint8_t maxVType = static_cast(Sensor_VType::SENSOR_TYPE_NOT_SET); - singleOptions.push_back(0); // Empty/None value - - for (uint8_t i = 0; i < maxVType; ++i) { - if (getValueCountFromSensorType(static_cast(i), false) == 1) { - singleOptions.push_back(i); - } - } - } - # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE - - # if FEATURE_PLUGIN_STATS - const __FlashStringHelper *chartAxis[] = { - F("L1"), - F("L2"), - F("L3"), - F("L4"), - F("R1"), - F("R2"), - F("R3"), - F("R4") - }; - constexpr size_t chartAxisCount = NR_ELEMENTS(chartAxis); - #endif // if FEATURE_PLUGIN_STATS - - #if FEATURE_MQTT_STATE_CLASS - const __FlashStringHelper *stateClasses[] = { - MQTT_sensor_StateClass(0), - MQTT_sensor_StateClass(1), - MQTT_sensor_StateClass(2), - MQTT_sensor_StateClass(3), - MQTT_sensor_StateClass(4), - }; - constexpr size_t stateCount = NR_ELEMENTS(stateClasses); - #endif // if FEATURE_MQTT_STATE_CLASS - - // table body - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { - html_TR_TD(); - addHtmlInt(varNr + 1); - html_TD(); - { - const String id = getPluginCustomArgName(F("TDVN"), varNr); // ="taskdevicevaluename" - addTextBox(id, Cache.getTaskDeviceValueName(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); - } - - if (device.FormulaOption) - { - html_TD(); - const String id = getPluginCustomArgName(F("TDF"), varNr); // ="taskdeviceformula" - addTextBox(id, Cache.getTaskDeviceFormula(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); - } - - if (device.configurableDecimals()) - { - html_TD(); - const String id = getPluginCustomArgName(F("TDVD"), varNr); // ="taskdevicevaluedecimals" - addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); - } - -# if FEATURE_PLUGIN_STATS - - if (device.PluginStats) - { - PluginStats_Config_t cachedConfig = Cache.getPluginStatsConfig(taskIndex, varNr); - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDS"), varNr), // ="taskdevicestats" - cachedConfig.isEnabled()); - - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDSH"), varNr), // ="taskdevicestats Hidden" - cachedConfig.showHidden()); - - html_TD(); - - int selected = cachedConfig.getAxisIndex(); - - if (!cachedConfig.isLeft()) { - selected += 4; - } - - const FormSelectorOptions selector(chartAxisCount, chartAxis); - selector.addSelector( - getPluginCustomArgName(F("TDSA"), varNr), - selected); - } -# endif // if FEATURE_PLUGIN_STATS - - # if FEATURE_TASKVALUE_UNIT_OF_MEASURE - html_TD(); - const uint64_t uomGroups = limitedUom ? uomEvent.Par64N[varNr] : UOM_GROUP_ALL; - addUnitOfMeasureSelector(getPluginCustomArgName(F("TUOM"), varNr), Cache.getTaskVarUnitOfMeasure(taskIndex, varNr), uomGroups); - #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - - # if FEATURE_CUSTOM_TASKVAR_VTYPE - - if (device.CustomVTypeVar) { - html_TD(); - sensorTypeCategoriesHelper_Selector( - getPluginCustomArgName(F("TDTV"), varNr), - singleOptions.size(), - &singleOptions[0], - static_cast(Cache.getTaskVarCustomVType(taskIndex, varNr))); - } - # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE - - # if FEATURE_MQTT_STATE_CLASS - - if (device.MqttStateClass) { - html_TD(); - - const FormSelectorOptions selectorSC(stateCount, stateClasses); - selectorSC.addSelector( - getPluginCustomArgName(F("TDSC"), varNr), - Cache.getTaskVarStateClass(taskIndex, varNr)); - } - # endif // if FEATURE_MQTT_STATE_CLASS - } - addFormSeparator(colCount); + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + addLogMove(LOG_LEVEL_DEBUG_DEV, concat(F("DEBUG: String size:"), static_cast(TXBuffer.sentBytes))); } +# endif // ifndef BUILD_NO_DEBUG + sendTail_stdtemplate(); } #endif // ifdef WEBSERVER_DEVICES diff --git a/src/src/WebServer/DevicesPage.h b/src/src/WebServer/DevicesPage.h index aed0001a1..2578dab58 100644 --- a/src/src/WebServer/DevicesPage.h +++ b/src/src/WebServer/DevicesPage.h @@ -1,97 +1,28 @@ -#ifndef WEBSERVER_WEBSERVER_DEVICESPAGE_H -#define WEBSERVER_WEBSERVER_DEVICESPAGE_H +#pragma once #include "../WebServer/common.h" #ifdef WEBSERVER_DEVICES -#include "../DataTypes/DeviceIndex.h" -#include "../DataTypes/TaskIndex.h" -#include "../DataTypes/PluginID.h" +# include "../DataTypes/DeviceIndex.h" +# include "../DataTypes/TaskIndex.h" +# include "../DataTypes/PluginID.h" -void handle_devices(); - -// ******************************************************************************** -// Add a device select dropdown list -// TODO TD-er: Add JavaScript filter: -// https://www.w3schools.com/howto/howto_js_filter_dropdown.asp -// ******************************************************************************** -void addDeviceSelect(const __FlashStringHelper * name, pluginID_t choice); - -// ******************************************************************************** -// Collect all submitted form data and store the task settings -// ******************************************************************************** -void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t taskdevicenumber); - -// ******************************************************************************** -// Show table with all selected Tasks/Devices -// ******************************************************************************** -void handle_devicess_ShowAllTasksTable(uint8_t page); - -#if FEATURE_ESPEASY_P2P -void format_originating_node(uint8_t remoteUnit); -#endif -#if FEATURE_I2C +# if FEATURE_I2C void format_I2C_port_description(taskIndex_t x); -#endif -#if FEATURE_SPI -void format_SPI_port_description(int8_t spi_gpios[3], uint8_t spi_bus); -#endif -#if FEATURE_I2C -void format_I2C_pin_description(taskIndex_t x); -#endif - -#if FEATURE_SPI -void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSpin = true); -#endif - - - -// ******************************************************************************** -// Show the task settings page -// ******************************************************************************** -void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page); - -void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); - -#ifdef PLUGIN_USES_SERIAL -void devicePage_show_serial_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); -#endif - -#if FEATURE_SPI -void devicePage_show_SPI_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); -#endif - -#if FEATURE_I2C -void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); -#endif - -# if FEATURE_I2CMULTIPLEXER +# 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); + bool & muxPortsOption, + int & selectedPorts); +# endif // if FEATURE_I2CMULTIPLEXER +# endif // if FEATURE_I2C -#if FEATURE_PLUGIN_STATS -void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); -#endif // if FEATURE_PLUGIN_STATS - -void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); - -void devicePage_show_interval_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); - -void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); +void handle_devices(); #endif // ifdef WEBSERVER_DEVICES - - - -#endif \ No newline at end of file diff --git a/src/src/WebServer/DownloadPage.cpp b/src/src/WebServer/DownloadPage.cpp index 4bd1d0e76..2083621a7 100644 --- a/src/src/WebServer/DownloadPage.cpp +++ b/src/src/WebServer/DownloadPage.cpp @@ -16,24 +16,8 @@ // ******************************************************************************** // Web Interface download page // ******************************************************************************** -void handle_download() { -# if FEATURE_TARSTREAM_SUPPORT - handle_config_download(false, false); -} - -void handle_full_backup() { - handle_config_download(true, false); -} - -void handle_full_backup_no_usr_pwd() { - handle_config_download(true, true); -} - -void handle_config_download(bool fullBackup, - bool noCreds) { -# else // if FEATURE_TARSTREAM_SUPPORT - const bool noCreds = false; -# endif // if FEATURE_TARSTREAM_SUPPORT +static void handle_config_download(bool fullBackup, + bool noCreds) { # ifndef BUILD_NO_RAM_TRACKER checkRAM(F("handle_download")); # endif // ifndef BUILD_NO_RAM_TRACKER @@ -84,9 +68,9 @@ void handle_config_download(bool fullBackup, if (fullBackup && (nullptr != tarStream)) { const String security_dat = getFileName(FileType::SECURITY_DAT); -#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE const String device_security_dat = getFileName(FileType::DEV_SECURITY_DAT); -#endif +# endif # if defined(ESP8266) fs::Dir dir = ESPEASY_FS.openDir(""); @@ -95,12 +79,12 @@ void handle_config_download(bool fullBackup, fs::File file = dir.openFile("r"); if (file) { - if (!noCreds || - (noCreds - && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())) -#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE - && (0 != strncasecmp(file.name(), device_security_dat.c_str(), device_security_dat.length())) -#endif + if (!noCreds || + (noCreds + && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())) +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + && (0 != strncasecmp(file.name(), device_security_dat.c_str(), device_security_dat.length())) +# endif )) { tarStream->addFile(file.name(), file.size()); } @@ -114,12 +98,12 @@ void handle_config_download(bool fullBackup, while (file) { if (!file.isDirectory()) { - if (!noCreds || - (noCreds - && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())) -#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE - && (0 != strncasecmp(file.name(), device_security_dat.c_str(), device_security_dat.length())) -#endif + if (!noCreds || + (noCreds + && (0 != strncasecmp(file.name(), security_dat.c_str(), security_dat.length())) +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + && (0 != strncasecmp(file.name(), device_security_dat.c_str(), device_security_dat.length())) +# endif )) { tarStream->addFileIfExists(file.name()); } @@ -145,10 +129,11 @@ void handle_config_download(bool fullBackup, if (!noCreds) { tarStream->addFileIfExists(getFileName(FileType::SECURITY_DAT)); -#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + // TODO TD-er: Must add separate flag for this -// tarStream->addFileIfExists(getFileName(FileType::DEV_SECURITY_DAT)); -#endif + // tarStream->addFileIfExists(getFileName(FileType::DEV_SECURITY_DAT)); +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE } // rules.txt files @@ -195,4 +180,15 @@ void handle_config_download(bool fullBackup, dataFile.close(); } +void handle_download() { handle_config_download(false, false); } + +# if FEATURE_TARSTREAM_SUPPORT + +void handle_full_backup() { handle_config_download(true, false); } + +void handle_full_backup_no_usr_pwd() { handle_config_download(true, true); } + +# endif // if FEATURE_TARSTREAM_SUPPORT + + #endif // ifdef WEBSERVER_DOWNLOAD diff --git a/src/src/WebServer/DownloadPage.h b/src/src/WebServer/DownloadPage.h index 430ce250c..678c329fe 100644 --- a/src/src/WebServer/DownloadPage.h +++ b/src/src/WebServer/DownloadPage.h @@ -1,5 +1,4 @@ -#ifndef WEBSERVER_WEBSERVER_DOWNLOADPAGE_H -#define WEBSERVER_WEBSERVER_DOWNLOADPAGE_H +#pragma once #include "../WebServer/common.h" @@ -12,11 +11,5 @@ void handle_download(); # if FEATURE_TARSTREAM_SUPPORT void handle_full_backup(); void handle_full_backup_no_usr_pwd(); -void handle_config_download(bool fullBackup, - bool noCreds); # endif // if FEATURE_TARSTREAM_SUPPORT - #endif // ifdef WEBSERVER_DOWNLOAD - - -#endif // ifndef WEBSERVER_WEBSERVER_DOWNLOADPAGE_H diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index f01a075bb..7c9691e14 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -9,7 +9,8 @@ #include "../WebServer/ConfigPage.h" #include "../WebServer/ControlPage.h" #include "../WebServer/ControllerPage.h" -//#include "../WebServer/CustomPage.h" + +// #include "../WebServer/CustomPage.h" #include "../WebServer/DevicesPage.h" #include "../WebServer/DownloadPage.h" #include "../WebServer/EepromVarPage.h" @@ -20,11 +21,13 @@ #include "../WebServer/InterfacesPage.h" #include "../WebServer/I2C_Scanner.h" #include "../WebServer/JSON.h" -//#include "../WebServer/LoadFromFS.h" + +// #include "../WebServer/LoadFromFS.h" #include "../WebServer/Log.h" -//#include "../WebServer/Markup.h" -//#include "../WebServer/Markup_Buttons.h" -//#include "../WebServer/Markup_Forms.h" + +// #include "../WebServer/Markup.h" +// #include "../WebServer/Markup_Buttons.h" +// #include "../WebServer/Markup_Forms.h" #include "../WebServer/NetworkPage.h" #include "../WebServer/NotificationPage.h" #include "../WebServer/PluginListPage.h" @@ -71,7 +74,6 @@ #include - void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size) { if (hasArg(arg)) { safe_strncpy(dest, webArg(arg).c_str(), max_size); @@ -82,48 +84,6 @@ void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper *arg, size safe_strncpy_webserver_arg(dest, String(arg), max_size); } -void sendHeadandTail(const __FlashStringHelper *tmplName, bool Tail, bool rebooting) { - // This function is called twice per serving a web page. - // So it must keep track of the timer longer than the scope of this function. - // Therefore use a local static variable. - #if FEATURE_TIMING_STATS - static uint64_t statisticsTimerStart = 0; - - if (!Tail) { - statisticsTimerStart = getMicros64(); - } - #endif // if FEATURE_TIMING_STATS - { - const String fileName = concat(tmplName, F(".htm")); - fs::File f = tryOpenFile(fileName, "r"); - - WebTemplateParser templateParser(Tail, rebooting); - - if (f) { - bool success = true; - - while (f.available() && success) { - success = templateParser.process((char)f.read()); - } - f.close(); - } else { - getWebPageTemplateDefault(tmplName, templateParser); - } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("sendWebPage")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // web activity timer - lastWeb = millis(); - } - - if (shouldReboot) { - // we only add this here as a seperate chunk to prevent using too much memory at once - serve_JS(JSfiles_e::Reboot); - } - STOP_TIMER(HANDLE_SERVING_WEBPAGE); -} - void sendTail_stdtemplate(bool rebooting) { sendHeadandTail(F("TmplStd"), _TAIL, rebooting); TXBuffer.endStream(); @@ -131,31 +91,39 @@ void sendTail_stdtemplate(bool rebooting) { // We have sent a lot of data at once. // try to flush it to the connected client to free up some RAM // from pending transfers -// TXBuffer.flush(); -// delay(10); + // TXBuffer.flush(); + // delay(10); +} + +static bool clientConnectedToAP() +{ + const IPAddress client_localIP = web_server.client().localIP(); + + return IPAddressSet(client_localIP) && client_localIP == apIP; } bool captivePortal() { - // We only need to check if a client connected here via AP + // We only need to check if a client connected here via AP // as currently we don't have any interface which allows forwarding // packets and thus acting as a gateway for others. - if (!Settings.ApCaptivePortal() || !clientConnectedToAP()) return false; + if (!Settings.ApCaptivePortal() || !clientConnectedToAP()) { return false; } #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, concat(F("CaptivePortal: hostHeader: "), web_server.hostHeader())); #endif + if (!ESPEasy::net::NetworkConnected()) { return false; } - if (!isIP(web_server.hostHeader()) + if (!isIP(web_server.hostHeader()) #if FEATURE_MDNS && !getValue(LabelType::M_DNS).equalsIgnoreCase(web_server.hostHeader()) #endif -) { + ) { const IPAddress client_localIP = web_server.client().localIP(); - String redirectURL = concat(F("http://"), formatIP(client_localIP)); + String redirectURL = concat(F("http://"), formatIP(client_localIP)); #ifdef WEBSERVER_SETUP if (ESPEasy::net::wifi::shouldRedirectTo_setup()) { @@ -171,28 +139,23 @@ bool captivePortal() { return false; } -bool clientConnectedToAP() -{ - const IPAddress client_localIP = web_server.client().localIP(); - return IPAddressSet(client_localIP) && client_localIP == apIP; -} - -ESPEasy::net::networkIndex_t getNetworkIndex_ClientConnectsTo() -{ - const IPAddress client_localIP = web_server.client().localIP(); - if (!IPAddressSet(client_localIP)) +/* + ESPEasy::net::networkIndex_t getNetworkIndex_ClientConnectsTo() + { + const IPAddress client_localIP = web_server.client().localIP(); + if (!IPAddressSet(client_localIP)) return ESPEasy::net::INVALID_NETWORK_INDEX; - if (client_localIP == apIP) { + if (client_localIP == apIP) { // Easy to check as this is a global variable return NETWORK_INDEX_WIFI_AP; - } - #ifdef ESP8266 - if (client_localIP == WiFi.localIP()) { + } + #ifdef ESP8266 + if (client_localIP == WiFi.localIP()) { return NETWORK_INDEX_WIFI_STA; - } - #endif - #ifdef ESP32 - for (ESPEasy::net::networkIndex_t x = 0; x < NETWORK_MAX; ++x) { + } + #endif + #ifdef ESP32 + for (ESPEasy::net::networkIndex_t x = 0; x < NETWORK_MAX; ++x) { if (Settings.getNetworkEnabled(x)) { struct EventStruct TempEvent; TempEvent.NetworkIndex = x; @@ -202,14 +165,14 @@ ESPEasy::net::networkIndex_t getNetworkIndex_ClientConnectsTo() { const NWPlugin::IP_type ip_types[] = { NWPlugin::IP_type::inet, - # if CONFIG_LWIP_IPV6 + # if CONFIG_LWIP_IPV6 NWPlugin::IP_type::ipv6_unknown, NWPlugin::IP_type::ipv6_global, NWPlugin::IP_type::ipv6_link_local, NWPlugin::IP_type::ipv6_site_local, NWPlugin::IP_type::ipv6_unique_local, NWPlugin::IP_type::ipv4_mapped_ipv6, - # endif // if CONFIG_LWIP_IPV6 + # endif // if CONFIG_LWIP_IPV6 }; @@ -219,19 +182,19 @@ ESPEasy::net::networkIndex_t getNetworkIndex_ClientConnectsTo() } } } - } + } - #endif - return ESPEasy::net::INVALID_NETWORK_INDEX; -} + #endif + return ESPEasy::net::INVALID_NETWORK_INDEX; + } + */ // ******************************************************************************** // Web Interface init // ******************************************************************************** // #include "core_version.h" - -void WebServerInit() +static void WebServerInit() { if (webserver_init) { return; } webserver_init = true; @@ -243,7 +206,7 @@ void WebServerInit() // Entries for several captive portal URLs. // Maybe not needed. Might be handled by notFound handler. web_server.on(UriGlob("/generate_204*"), handle_root); // Android captive portal. Handle "/generate_204_"-like requests. -//web_server.on(F("/generate_204"), handle_root); // android captive portal redirect + // web_server.on(F("/generate_204"), handle_root); // android captive portal redirect web_server.on(F("/fwlink"), handle_root); // Microsoft captive portal. web_server.on(F("/redirect"), handle_root); // microsoft redirect web_server.on(F("/hotspot-detect.html"), handle_root); // apple call home @@ -253,10 +216,10 @@ void WebServerInit() #endif // ifdef WEBSERVER_ROOT #ifdef WEBSERVER_ADVANCED - web_server.on(F("/advanced"), handle_advanced); + web_server.on(F("/advanced"), handle_advanced); # if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT - web_server.on(F("/backup"), handle_full_backup); - web_server.on(F("/backupnup"), handle_full_backup_no_usr_pwd); + web_server.on(F("/backup"), handle_full_backup); + web_server.on(F("/backupnup"), handle_full_backup_no_usr_pwd); # endif // if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT #endif // ifdef WEBSERVER_ADVANCED #ifdef WEBSERVER_CONFIG @@ -286,62 +249,62 @@ void WebServerInit() #endif // USES_C016 #ifdef WEBSERVER_FACTORY_RESET - web_server.on(F("/factoryreset"), handle_factoryreset); + web_server.on(F("/factoryreset"), handle_factoryreset); #endif // ifdef WEBSERVER_FACTORY_RESET #if FEATURE_SETTINGS_ARCHIVE - web_server.on(F("/settingsarchive"), handle_settingsarchive); + web_server.on(F("/settingsarchive"), handle_settingsarchive); #endif // if FEATURE_SETTINGS_ARCHIVE #ifdef WEBSERVER_FILELIST - web_server.on(F("/filelist"), handle_filelist); + web_server.on(F("/filelist"), handle_filelist); #endif // ifdef WEBSERVER_FILELIST #ifdef WEBSERVER_HARDWARE - web_server.on(F("/hardware"), handle_hardware); + web_server.on(F("/hardware"), handle_hardware); #endif // ifdef WEBSERVER_HARDWARE #ifdef WEBSERVER_INTERFACES - web_server.on(F("/interfaces"), handle_interfaces); -#if FEATURE_I2C - web_server.on(F("/interfaces_i2c"), handle_interfaces_i2c); -#endif // if FEATURE_I2C -#if FEATURE_SPI - web_server.on(F("/interfaces_spi"), handle_interfaces_spi); -#endif // if FEATURE_SPI -#if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB - web_server.on(F("/interfaces_modbus"), handle_interfaces_modbus); -#endif // if FEATURE_MODBUS -#if FEATURE_CAN - web_server.on(F("/interfaces_can"), handle_interfaces_can); -#endif // if FEATURE_CAN -#if FEATURE_WRMBUS - web_server.on(F("/interfaces_wrmbus"), handle_interfaces_wrmbus); -#endif // if FEATURE_WRMBUS -#if FEATURE_WIMBUS - web_server.on(F("/interfaces_wimbus"), handle_interfaces_wimbus); -#endif // if FEATURE_WIMBUS + web_server.on(F("/interfaces"), handle_interfaces); +# if FEATURE_I2C + web_server.on(F("/interfaces_i2c"), handle_interfaces_i2c); +# endif // if FEATURE_I2C +# if FEATURE_SPI + web_server.on(F("/interfaces_spi"), handle_interfaces_spi); +# endif // if FEATURE_SPI +# if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB + web_server.on(F("/interfaces_modbus"), handle_interfaces_modbus); +# endif // if FEATURE_MODBUS +# if FEATURE_CAN + web_server.on(F("/interfaces_can"), handle_interfaces_can); +# endif // if FEATURE_CAN +# if FEATURE_WRMBUS + web_server.on(F("/interfaces_wrmbus"), handle_interfaces_wrmbus); +# endif // if FEATURE_WRMBUS +# if FEATURE_WIMBUS + web_server.on(F("/interfaces_wimbus"), handle_interfaces_wimbus); +# endif // if FEATURE_WIMBUS #endif // ifdef WEBSERVER_INTERFACES #ifdef WEBSERVER_I2C_SCANNER - web_server.on(F("/i2cscanner"), handle_i2cscanner); + web_server.on(F("/i2cscanner"), handle_i2cscanner); #endif // ifdef WEBSERVER_I2C_SCANNER #ifdef WEBSERVER_JSON - web_server.on(F("/json"), handle_json); // Also part of WEBSERVER_NEW_UI + web_server.on(F("/json"), handle_json); // Also part of WEBSERVER_NEW_UI #endif #ifdef WEBSERVER_CSVVAL - web_server.on(F("/csv"), handle_csvval); + web_server.on(F("/csv"), handle_csvval); #endif #ifdef WEBSERVER_LOG - web_server.on(F("/log"), handle_log); - web_server.on(F("/logjson"), handle_log_JSON); // Also part of WEBSERVER_NEW_UI - #endif + web_server.on(F("/log"), handle_log); + web_server.on(F("/logjson"), handle_log_JSON); // Also part of WEBSERVER_NEW_UI + #endif // ifdef WEBSERVER_LOG #if FEATURE_NOTIFIER - web_server.on(F("/notifications"), handle_notifications); + web_server.on(F("/notifications"), handle_notifications); #endif // if FEATURE_NOTIFIER #ifdef WEBSERVER_PINSTATES - web_server.on(F("/pinstates"), handle_pinstates); + web_server.on(F("/pinstates"), handle_pinstates); #endif // ifdef WEBSERVER_PINSTATES #ifdef WEBSERVER_RULES - web_server.on(F("/rules"), handle_rules_new); - web_server.on(F("/rules/"), Goto_Rules_Root); + web_server.on(F("/rules"), handle_rules_new); + web_server.on(F("/rules/"), Goto_Rules_Root); # ifdef WEBSERVER_NEW_RULES - web_server.on(F("/rules/add"), []() + web_server.on(F("/rules/add"), []() { handle_rules_edit(web_server.uri(), true); }); @@ -477,7 +440,74 @@ void setWebserverRunning(bool state) { ESPEasy::net::CheckRunningServices(true); // Uses webserverRunning state. } -void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser) +static void getWebPageTemplateDefaultHead(WebTemplateParser& parser, bool addMeta, bool addJS) { + if (parser.isTail()) { return; } + parser.process(F("" + "" + "" + "" + "{{name}}")); + + if (addMeta) { parser.process(F("{{meta}}")); } + + if (addJS) { parser.process(F("{{js}}")); } + + parser.process(F("{{css}}" + "")); +} + +static void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, const __FlashStringHelper *title, bool addMenu) { + { + if (parser.isTail()) { return; } + #ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + parser.process(strformat(F("

ESP Easy Mega: "), isGpMenuSecondLevel(navMenuIndex) ? '2' : ' ')); + parser.process(title); + # if BUILD_IN_WEBHEADER + parser.process(F( + "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{date}}" GITHUB_RELEASES_LINK_SUFFIX + "
")); + # endif // #if BUILD_IN_WEBHEADER + parser.process(F("


")); + #else // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + String tmp = F(WEBPAGE_TEMPLATE_DEFAULT_HEADER); + tmp.replace(F("{{title}}"), title); + parser.process(tmp); + #endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + } + + if (addMenu) { parser.process(F("{{menu}}")); } + parser.process(F("
")); +} + +static void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser) { + parser.process(F("
" + "" + "{{error}}" + "" + "{{content}}" + "
" + )); +} + +static void getWebPageTemplateDefaultFooter(WebTemplateParser& parser) { + if (!parser.isTail()) { return; } + #ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER + parser.process(F("
" + "
" + "
Powered by Let's Control It community" + # if BUILD_IN_WEBFOOTER + "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{build}} {{date}}" GITHUB_RELEASES_LINK_SUFFIX "
" + # endif // #if BUILD_IN_WEBFOOTER + "
" + "
" + "" + )); +#else // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER + parser.process(F(WEBPAGE_TEMPLATE_DEFAULT_FOOTER)); +#endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER +} + +static void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser) { const bool addJS = true; const bool addMeta = true; @@ -562,71 +592,46 @@ void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser // addLog(LOG_LEVEL_INFO, concat(F("tmpl.length(): "), tmpl.length())); } -void getWebPageTemplateDefaultHead(WebTemplateParser& parser, bool addMeta, bool addJS) { - if (parser.isTail()) { return; } - parser.process(F("" - "" - "" - "" - "{{name}}")); +void sendHeadandTail(const __FlashStringHelper *tmplName, bool Tail, bool rebooting) { + // This function is called twice per serving a web page. + // So it must keep track of the timer longer than the scope of this function. + // Therefore use a local static variable. + #if FEATURE_TIMING_STATS + static uint64_t statisticsTimerStart = 0; - if (addMeta) { parser.process(F("{{meta}}")); } - - if (addJS) { parser.process(F("{{js}}")); } - - parser.process(F("{{css}}" - "")); -} - -void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, const __FlashStringHelper *title, bool addMenu) { + if (!Tail) { + statisticsTimerStart = getMicros64(); + } + #endif // if FEATURE_TIMING_STATS { - if (parser.isTail()) { return; } - #ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - parser.process(strformat(F("

ESP Easy Mega: "), isGpMenuSecondLevel(navMenuIndex) ? '2' : ' ')); - parser.process(title); - # if BUILD_IN_WEBHEADER - parser.process(F( - "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{date}}" GITHUB_RELEASES_LINK_SUFFIX - "
")); - # endif // #if BUILD_IN_WEBHEADER - parser.process(F("


")); - #else // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - String tmp = F(WEBPAGE_TEMPLATE_DEFAULT_HEADER); - tmp.replace(F("{{title}}"), title); - parser.process(tmp); - #endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + const String fileName = concat(tmplName, F(".htm")); + fs::File f = tryOpenFile(fileName, "r"); + + WebTemplateParser templateParser(Tail, rebooting); + + if (f) { + bool success = true; + + while (f.available() && success) { + success = templateParser.process((char)f.read()); + } + f.close(); + } else { + getWebPageTemplateDefault(tmplName, templateParser); + } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("sendWebPage")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // web activity timer + lastWeb = millis(); } - if (addMenu) { parser.process(F("{{menu}}")); } - parser.process(F("
")); -} - -void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser) { - parser.process(F("
" - "" - "{{error}}" - "" - "{{content}}" - "
" - )); -} - -void getWebPageTemplateDefaultFooter(WebTemplateParser& parser) { - if (!parser.isTail()) { return; } - #ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER - parser.process(F("
" - "
" - "
Powered by Let's Control It community" - # if BUILD_IN_WEBFOOTER - "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{build}} {{date}}" GITHUB_RELEASES_LINK_SUFFIX "
" - # endif // #if BUILD_IN_WEBFOOTER - "
" - "
" - "" - )); -#else // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER - parser.process(F(WEBPAGE_TEMPLATE_DEFAULT_FOOTER)); -#endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER + if (shouldReboot) { + // we only add this here as a seperate chunk to prevent using too much memory at once + serve_JS(JSfiles_e::Reboot); + } + STOP_TIMER(HANDLE_SERVING_WEBPAGE); } void writeDefaultCSS(void) @@ -669,9 +674,7 @@ void writeDefaultCSS(void) int8_t level = 0; int8_t lastLevel = -1; -void json_quote_name(const __FlashStringHelper *val) { - json_quote_name(String(val)); -} +void json_quote_name(const __FlashStringHelper *val) { json_quote_name(String(val)); } void json_quote_name(const String& val) { if (lastLevel == level) { @@ -684,13 +687,9 @@ void json_quote_name(const String& val) { } } -void json_quote_val(const String& val) { - addHtml(to_json_value(val)); -} +void json_quote_val(const String& val) { addHtml(to_json_value(val)); } -void json_open(bool arr) { - json_open(arr, EMPTY_STRING); -} +void json_open(bool arr) { json_open(arr, EMPTY_STRING); } void json_open(bool arr, const __FlashStringHelper *name) { json_quote_name(name); @@ -711,9 +710,7 @@ void json_init() { lastLevel = -1; } -void json_close() { - json_close(false); -} +void json_close() { json_close(false); } void json_close(bool arr) { addHtml(arr ? ']' : '}'); @@ -721,14 +718,9 @@ void json_close(bool arr) { lastLevel = level; } -void json_number(const __FlashStringHelper *name, const String& value) -{ - json_prop(name, value); -} +void json_number(const __FlashStringHelper *name, const String& value) { json_prop(name, value); } -void json_number(const String& name, const String& value) { - json_prop(name, value); -} +void json_number(const String& name, const String& value) { json_prop(name, value); } void json_prop(const __FlashStringHelper *name, const String& value) { @@ -743,10 +735,10 @@ void json_prop(const String& name, const String& value) { lastLevel = level; } -void json_prop(LabelType::Enum label) { - json_prop(getInternalLabel(label, '-'), getValue(label)); -} -#endif +void json_prop(LabelType::Enum label) { json_prop(getInternalLabel(label, '-'), getValue(label)); } + +#endif // ifdef WEBSERVER_NEW_UI + // ******************************************************************************** // Add a task select dropdown list // This allows to select a task index based on the existing tasks. @@ -761,10 +753,11 @@ void addTaskSelect(const String& name, taskIndex_t choice) for (taskIndex_t x = 0; x <= TASKS_MAX; x++) { deviceNr.clear(); + if (validTaskIndex(x)) { const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); deviceName = getPluginNameFromDeviceIndex(DeviceIndex); - deviceNr += (x + 1); + deviceNr += (x + 1); } else { deviceName = F("Not Set"); } @@ -777,7 +770,7 @@ void addTaskSelect(const String& name, taskIndex_t choice) FormSelectorOptions selector(TASKS_MAX + 1, options, nullptr, attrs); selector.reloadonchange = true; - selector.classname = F("wide"); + selector.classname = F("wide"); selector.addSelector(name, choice); } @@ -880,6 +873,7 @@ void startStream_send_stdTemplate_NoLoginCheck(uint8_t newNavIndex, bool rebooti TXBuffer.startStream(); sendHeadandTail(F("TmplStd"), _HEAD, rebooting); + // TODO TD-er: This should be the only place where sendHeadandTail_stdtemplate(_HEAD) is called @@ -915,11 +909,10 @@ void startStream_send_stdTemplate_NoLoginCheck(uint8_t newNavIndex, bool rebooti addLogMove(LOG_LEVEL_INFO, log); } } - */ + */ #endif // ifndef BUILD_NO_DEBUG } - bool startJSON_Stream() { if (!isLoggedIn()) { return false; } @@ -928,7 +921,6 @@ bool startJSON_Stream() return true; } - String getControllerSymbol(uint8_t index) { String ret = F("&#"); @@ -938,15 +930,15 @@ String getControllerSymbol(uint8_t index) return ret; } -void handle_printWebString() +void handle_printWebString() { - if (printWebString.isEmpty()) return; + if (printWebString.isEmpty()) { return; } addRowColspan(2); addHtml(F("Command Output
")); free_string(printWebString); - printToWeb = false; + printToWeb = false; } /* @@ -958,54 +950,35 @@ void handle_printWebString() return ret; } */ -void addSVG_param(const char key, int value) -{ - addHtml(strformat(F(" %c=\"%d\""), key, value)); + + #if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) + +static void addSVG_param(const char key, int value) { addHtml(strformat(F(" %c=\"%d\""), key, value)); } + +static void addSVG_param(const __FlashStringHelper *key, int value) { addHtml(strformat(F(" %s=\"%d\""), FsP(key), value)); } + +#endif // if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) + +static void addSVG_param(const char key, const String& value) { addHtml(strformat(F(" %c=\"%s\""), key, value.c_str())); } + +static void addSVG_param(const __FlashStringHelper *key, const String& value) { + addHtml(strformat(F(" %s=\"%s\""), FsP(key), value.c_str())); } -void addSVG_param(const char key, float value) -{ - addSVG_param(key, toString(value, 2)); -} +static void addSVG_param(const char key, float value) { addSVG_param(key, toString(value, 2)); } -void addSVG_param(const char key, const String& value) -{ - addHtml(strformat(F(" %c=\"%s\""), key, value.c_str())); -} +static void addSVG_param(const __FlashStringHelper *key, float value) { addSVG_param(key, toString(value, 2)); } -void addSVG_param(const __FlashStringHelper *key, int value) { - addHtml(strformat(F(" %s=\"%d\""), key, value)); -} - -void addSVG_param(const __FlashStringHelper *key, float value) { - addSVG_param(key, toString(value, 2)); -} - -void addSVG_param(const __FlashStringHelper *key, const String& value) { - addHtml(strformat(F(" %s=\"%s\""), key, value.c_str())); -} - -void createSvgRect_noStroke(const __FlashStringHelper *classname, - unsigned int fillColor, - float xoffset, - float yoffset, - float width, - float height, - float rx, - float ry) { - createSvgRect(classname, fillColor, fillColor, xoffset, yoffset, width, height, 0, rx, ry); -} - -void createSvgRect(const String& classname, - unsigned int fillColor, - unsigned int strokeColor, - float xoffset, - float yoffset, - float width, - float height, - float strokeWidth, - float rx, - float ry) { +static void createSvgRect(const String& classname, + unsigned int fillColor, + unsigned int strokeColor, + float xoffset, + float yoffset, + float width, + float height, + float strokeWidth, + float rx, + float ry) { addHtml(F("")); } -void createSvgHorRectPath(unsigned int color, int xoffset, int yoffset, int size, int height, int range, float SVG_BAR_WIDTH) { - if (range == 0) { - range = 1; - } - float width = (SVG_BAR_WIDTH * size) / range; - - if (width < 2) { width = 2; } - addHtml(formatToHex(color, F("\n")); +static void createSvgRect_noStroke(const __FlashStringHelper *classname, + unsigned int fillColor, + float xoffset, + float yoffset, + float width, + float height, + float rx, + float ry) { + createSvgRect(classname, fillColor, fillColor, xoffset, yoffset, width, height, 0, rx, ry); } -void createSvgTextElement(const String& text, float textXoffset, float textYoffset) { - addHtml(F("', '\n'); - - addHtml(F("'); - - addHtml(text); - addHtml(F("\n")); -} #define SVG_BAR_HEIGHT 16 #define SVG_BAR_WIDTH 400 -void write_SVG_image_header(int width, int height, bool useViewbox) { +static void write_SVG_image_header(int width, int height, bool useViewbox) { addHtml(F("\n")); +} + +static void createSvgTextElement(const String& text, float textXoffset, float textYoffset) { + addHtml(F("', '\n'); + + addHtml(F("'); + + addHtml(text); + addHtml(F("\n")); +} + void getConfig_dat_file_layout() { const int shiftY = 2; float yOffset = shiftY; - write_SVG_image_header(SVG_BAR_WIDTH + 250, SVG_BAR_HEIGHT + shiftY); + write_SVG_image_header(SVG_BAR_WIDTH + 250, SVG_BAR_HEIGHT + shiftY, false); int max_index, offset, max_size{}; int struct_size = 0; @@ -1190,7 +1176,7 @@ void getStorageTableSVG(SettingsType::Enum settingsType) { if (max_index == 0) { return; } // One more to add bar indicating struct size vs. reserved space. - write_SVG_image_header(SVG_BAR_WIDTH + 250, (max_index + 1) * SVG_BAR_HEIGHT + shiftY); + write_SVG_image_header(SVG_BAR_WIDTH + 250, (max_index + 1) * SVG_BAR_HEIGHT + shiftY, false); float yOffset = shiftY; for (int i = 0; i < max_index; ++i) { @@ -1229,7 +1215,7 @@ void getStorageTableSVG(SettingsType::Enum settingsType) { String text = strformat( F("%s/%s per item"), formatHumanReadable(struct_size, 1024).c_str(), - formatHumanReadable(max_size, 1024).c_str()); + formatHumanReadable(max_size, 1024).c_str()); createSvgTextElement(text, textXoffset, textYoffset); } else { createSvgTextElement(F("Variable size"), textXoffset, textYoffset); @@ -1237,7 +1223,7 @@ void getStorageTableSVG(SettingsType::Enum settingsType) { addHtml(F("\n")); } -void drawPartitionChartSVG( +static void drawPartitionChartSVG( float yOffset, uint32_t realSize, uint32_t partitionAddress, @@ -1261,7 +1247,6 @@ void drawPartitionChartSVG( # include - void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor) { int nrPartitions = getPartionCount(pType); @@ -1273,11 +1258,12 @@ void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor) { const esp_partition_t *_mypart; esp_partition_iterator_t _mypartiterator = esp_partition_find(partitionType, ESP_PARTITION_SUBTYPE_ANY, nullptr); - write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); + write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY, false); float yOffset = shiftY; if (_mypartiterator) { - do { + do + { _mypart = esp_partition_get(_mypartiterator); drawPartitionChartSVG( yOffset, @@ -1297,12 +1283,13 @@ void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor) { # endif // ifdef ESP32 # ifdef ESP8266 + void getPartitionTableSVG() { // sketch / OTA / FS / EEPROM / RFcal / wifi const int nrPartitions = 6; const int shiftY = 2; - write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); + write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY, false); float yOffset = shiftY; for (int i = 0; i < nrPartitions; ++i) { @@ -1315,7 +1302,8 @@ void getPartitionTableSVG() { String descr; unsigned int partitionColor = 0xab56e6; - switch (ptype) { + switch (ptype) + { case ESP8266_partition_type::sketch: label = F("sketch"); partitionColor = 0xab56e6; @@ -1375,6 +1363,4 @@ void getPartitionTableSVG() { # endif // ifdef ESP8266 #endif // if FEATURE_CHART_STORAGE_LAYOUT -bool webArg2ip(const __FlashStringHelper *arg, uint8_t *IP) { - return str2ip(webArg(arg), IP); -} +bool webArg2ip(const __FlashStringHelper *arg, uint8_t *IP) { return str2ip(webArg(arg), IP); } diff --git a/src/src/WebServer/ESPEasy_WebServer.h b/src/src/WebServer/ESPEasy_WebServer.h index bb6517d97..31396b5c8 100644 --- a/src/src/WebServer/ESPEasy_WebServer.h +++ b/src/src/WebServer/ESPEasy_WebServer.h @@ -14,51 +14,35 @@ // Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. // *INDENT-ON* -void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size); +void safe_strncpy_webserver_arg(char *dest, + const String& arg, + size_t max_size); -void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper * arg, size_t max_size); +void safe_strncpy_webserver_arg(char *dest, + const __FlashStringHelper *arg, + size_t max_size); -void sendHeadandTail(const __FlashStringHelper * tmplName, - bool Tail = false, - bool rebooting = false); +void sendHeadandTail(const __FlashStringHelper *tmplName, + bool Tail = false, + bool rebooting = false); -void sendTail_stdtemplate(bool rebooting = false); +void sendTail_stdtemplate(bool rebooting = false); -void WebServerInit(); - // ******************************************************************************** -// Redirect to captive portal if we got a request for another domain. +// Redirect to captive portal if we got a request for another domain. // Return true in that case so the page handler does not try to handle the request again. // ******************************************************************************** -bool captivePortal(); - -bool clientConnectedToAP(); +bool captivePortal(); // Determine which network interface is being used by the client to access this ESPEasy node -ESPEasy::net::networkIndex_t getNetworkIndex_ClientConnectsTo(); +// ESPEasy::net::networkIndex_t getNetworkIndex_ClientConnectsTo(); - -void setWebserverRunning(bool state); - -void getWebPageTemplateDefault(const String& tmplName, - WebTemplateParser& parser); - -void getWebPageTemplateDefaultHead(WebTemplateParser& parser, - bool addMeta, - bool addJS); - -void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, - const __FlashStringHelper * title, - bool addMenu); - -void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser); - -void getWebPageTemplateDefaultFooter(WebTemplateParser& parser); +void setWebserverRunning(bool state); -void writeDefaultCSS(void); +void writeDefaultCSS(void); // ******************************************************************************** @@ -70,15 +54,15 @@ void writeDefaultCSS(void); extern int8_t level; extern int8_t lastLevel; -void json_quote_name(const __FlashStringHelper * val); +void json_quote_name(const __FlashStringHelper *val); void json_quote_name(const String& val); void json_quote_val(const String& val); void json_open(bool arr = false); -void json_open(bool arr, - const __FlashStringHelper * name); +void json_open(bool arr, + const __FlashStringHelper *name); void json_open(bool arr, const String& name); @@ -89,26 +73,26 @@ void json_close(); void json_close(bool arr); -void json_number(const __FlashStringHelper * name, - const String& value); +void json_number(const __FlashStringHelper *name, + const String & value); void json_number(const String& name, const String& value); -void json_prop(const __FlashStringHelper * name, - const String& value); +void json_prop(const __FlashStringHelper *name, + const String & value); void json_prop(const String& name, const String& value); void json_prop(LabelType::Enum label); -#endif +#endif // ifdef WEBSERVER_NEW_UI // ******************************************************************************** // Add a task select dropdown list // This allows to select a task index based on the existing tasks. // When changing a selected task, the page reloads with the new settings applied. -// However, these changes will not trigger a save, so make sure to store those in +// However, these changes will not trigger a save, so make sure to store those in // PCONFIG() and not in extra settings // ******************************************************************************** void addTaskSelect(const String& name, @@ -131,77 +115,23 @@ bool isLoggedIn(bool mustProvideLogin = true); // Thus not for non-HTML pages. bool startStream_send_stdTemplate(uint8_t newNavIndex); -void startStream_send_stdTemplate_NoLoginCheck(uint8_t newNavIndex, bool rebooting = false); +void startStream_send_stdTemplate_NoLoginCheck(uint8_t newNavIndex, + bool rebooting = false); // Check isLoggedIn and start JSON stream // JSON can be called from anywhere, so no need to update the nav index -bool startJSON_Stream(); +bool startJSON_Stream(); -String getControllerSymbol(uint8_t index); +String getControllerSymbol(uint8_t index); -void handle_printWebString(); +void handle_printWebString(); /* String getValueSymbol(uint8_t index); */ -void addSVG_param(const char key, - int value); -void addSVG_param(const char key, - float value); -void addSVG_param(const char key, - const String& value); - -void addSVG_param(const __FlashStringHelper * key, - int value); - -void addSVG_param(const __FlashStringHelper * key, - float value); - -void addSVG_param(const __FlashStringHelper * key, - const String& value); - -void createSvgRect_noStroke(const __FlashStringHelper * classname, - unsigned int fillColor, - float xoffset, - float yoffset, - float width, - float height, - float rx, - float ry); - -void createSvgRect(const String& classname, - unsigned int fillColor, - unsigned int strokeColor, - float xoffset, - float yoffset, - float width, - float height, - float strokeWidth, - float rx, - float ry); - -void createSvgHorRectPath(unsigned int color, - int xoffset, - int yoffset, - int size, - int height, - int range, - float SVG_BAR_WIDTH); - -void createSvgTextElement(const String& text, - float textXoffset, - float textYoffset); - -void write_SVG_image_header(int width, - int height, - bool useViewbox = false); - -/* - void getESPeasyLogo(int width_pixels); - */ void getWiFi_RSSI_icon(int rssi, int width_pixels); @@ -210,20 +140,20 @@ void getConfig_dat_file_layout(); void getStorageTableSVG(SettingsType::Enum settingsType); -#ifdef ESP32 +# ifdef ESP32 void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor); -#endif // ifdef ESP32 -#ifdef ESP8266 +# endif // ifdef ESP32 +# ifdef ESP8266 void getPartitionTableSVG(); -#endif +# endif -#endif +#endif // if FEATURE_CHART_STORAGE_LAYOUT && !defined(BUILD_NO_DEBUG) -bool webArg2ip(const __FlashStringHelper * arg, - uint8_t *IP); +bool webArg2ip(const __FlashStringHelper *arg, + uint8_t *IP); -#endif // ifndef WEBSERVER_ESPEASY_WEBSERVER_H \ No newline at end of file +#endif // ifndef WEBSERVER_ESPEASY_WEBSERVER_H diff --git a/src/src/WebServer/FactoryResetPage.cpp b/src/src/WebServer/FactoryResetPage.cpp index 3e849eec9..e613369b9 100644 --- a/src/src/WebServer/FactoryResetPage.cpp +++ b/src/src/WebServer/FactoryResetPage.cpp @@ -17,6 +17,29 @@ #include "../Helpers/ESPEasy_Storage.h" #include "../Helpers/Hardware.h" + +// ******************************************************************************** +// Create pre-defined config selector +// ******************************************************************************** +#ifndef LIMIT_BUILD_SIZE +static void addPreDefinedConfigSelector() { + DeviceModel active_model = ResetFactoryDefaultPreference.getDeviceModel(); + + addSelector_Head_reloadOnChange(F("fdm")); + + for (uint8_t x = 0; x < static_cast(DeviceModel::DeviceModel_MAX); ++x) { + DeviceModel model = static_cast(x); + if (modelMatchingFlashSize(model)) { + addSelector_Item( + getDeviceModelString(model), + x, + model == active_model); + } + } + addSelector_Foot(true); +} +#endif + // ******************************************************************************** // Web Interface Factory Reset // ******************************************************************************** @@ -110,25 +133,6 @@ void handle_factoryreset() { sendTail_stdtemplate(); } -// ******************************************************************************** -// Create pre-defined config selector -// ******************************************************************************** -void addPreDefinedConfigSelector() { - DeviceModel active_model = ResetFactoryDefaultPreference.getDeviceModel(); - - addSelector_Head_reloadOnChange(F("fdm")); - - for (uint8_t x = 0; x < static_cast(DeviceModel::DeviceModel_MAX); ++x) { - DeviceModel model = static_cast(x); - if (modelMatchingFlashSize(model)) { - addSelector_Item( - getDeviceModelString(model), - x, - model == active_model); - } - } - addSelector_Foot(true); -} #ifdef WEBSERVER_NEW_UI void handle_factoryreset_json() { diff --git a/src/src/WebServer/FactoryResetPage.h b/src/src/WebServer/FactoryResetPage.h index 8f49d0dfb..bf035c40c 100644 --- a/src/src/WebServer/FactoryResetPage.h +++ b/src/src/WebServer/FactoryResetPage.h @@ -12,10 +12,6 @@ // ******************************************************************************** void handle_factoryreset(); -// ******************************************************************************** -// Create pre-defined config selector -// ******************************************************************************** -void addPreDefinedConfigSelector(); #ifdef WEBSERVER_NEW_UI void handle_factoryreset_json(); diff --git a/src/src/WebServer/FileList.cpp b/src/src/WebServer/FileList.cpp index b089e6c5f..b51a4a48e 100644 --- a/src/src/WebServer/FileList.cpp +++ b/src/src/WebServer/FileList.cpp @@ -135,6 +135,73 @@ void handle_filelist_json() { #endif // WEBSERVER_NEW_UI #ifdef WEBSERVER_FILELIST + +static void handle_filelist_add_file(const String& filename, int filesize, int startIdx) { + html_TR_TD(); + + if (!isProtectedFileType(filename)) + { + html_add_button_prefix(); + addHtml(F("filelist?delete=")); + addHtml(filename); + + if (startIdx > 0) + { + addHtml(F("&start=")); + addHtmlInt(startIdx); + } + addHtml(F("'>Del")); + } + { + addHtml(F("
'); + addHtml(filename); + addHtml(F("")); + + if (filesize >= 0) { + addHtmlInt(filesize); + } + } +} + +static void handle_filelist_buttons(int start_prev, int start_next, bool cacheFilesPresent, bool bakFilesPresent) { + html_end_table(); + html_end_form(); + html_BR(); + addButton(F("/upload"), F("Upload")); + + if (start_prev >= 0) + { + html_add_button_prefix(); + addHtml(F("/filelist?start=")); + addHtmlInt(start_prev); + addHtml(F("'>Previous")); + } + + if (start_next >= 0) + { + html_add_button_prefix(); + addHtml(F("/filelist?start=")); + addHtmlInt(start_next); + addHtml(F("'>Next")); + } +#if FEATURE_RTC_CACHE_STORAGE + if (cacheFilesPresent) { + html_add_button_prefix(F("red"), true); + addHtml(F("filelist?delcache=1'>Delete Cache Files")); + } +#endif +#if FEATURE_DOWNLOAD + if (bakFilesPresent) { + html_add_button_prefix(F("red"), true); + addHtml(F("filelist?delbak=1'>Delete '_bak' Files")); + } +#endif + addHtml(F("

")); + sendTail_stdtemplate(); +} + void handle_filelist() { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("handle_filelist")); @@ -267,71 +334,7 @@ void handle_filelist() { handle_filelist_buttons(start_prev, start_next, cacheFilesPresent, bakFilesPresent); } -void handle_filelist_add_file(const String& filename, int filesize, int startIdx) { - html_TR_TD(); - if (!isProtectedFileType(filename)) - { - html_add_button_prefix(); - addHtml(F("filelist?delete=")); - addHtml(filename); - - if (startIdx > 0) - { - addHtml(F("&start=")); - addHtmlInt(startIdx); - } - addHtml(F("'>Del")); - } - { - addHtml(F("
'); - addHtml(filename); - addHtml(F("")); - - if (filesize >= 0) { - addHtmlInt(filesize); - } - } -} - -void handle_filelist_buttons(int start_prev, int start_next, bool cacheFilesPresent, bool bakFilesPresent) { - html_end_table(); - html_end_form(); - html_BR(); - addButton(F("/upload"), F("Upload")); - - if (start_prev >= 0) - { - html_add_button_prefix(); - addHtml(F("/filelist?start=")); - addHtmlInt(start_prev); - addHtml(F("'>Previous")); - } - - if (start_next >= 0) - { - html_add_button_prefix(); - addHtml(F("/filelist?start=")); - addHtmlInt(start_next); - addHtml(F("'>Next")); - } -#if FEATURE_RTC_CACHE_STORAGE - if (cacheFilesPresent) { - html_add_button_prefix(F("red"), true); - addHtml(F("filelist?delcache=1'>Delete Cache Files")); - } -#endif -#if FEATURE_DOWNLOAD - if (bakFilesPresent) { - html_add_button_prefix(F("red"), true); - addHtml(F("filelist?delbak=1'>Delete '_bak' Files")); - } -#endif - addHtml(F("

")); - sendTail_stdtemplate(); -} #endif // ifdef WEBSERVER_FILELIST diff --git a/src/src/WebServer/FileList.h b/src/src/WebServer/FileList.h index 138cfd65a..7685302f8 100644 --- a/src/src/WebServer/FileList.h +++ b/src/src/WebServer/FileList.h @@ -16,10 +16,6 @@ void handle_filelist_json(); #ifdef WEBSERVER_FILELIST void handle_filelist(); -void handle_filelist_add_file(const String& filename, int filesize, int startIdx); - -void handle_filelist_buttons(int start_prev, int start_next, bool cacheFilesPresent, bool bakFilesPresent); - #endif // ifdef WEBSERVER_FILELIST // ******************************************************************************** diff --git a/src/src/WebServer/I2C_Scanner.cpp b/src/src/WebServer/I2C_Scanner.cpp index dcc44a8b5..b525896cd 100644 --- a/src/src/WebServer/I2C_Scanner.cpp +++ b/src/src/WebServer/I2C_Scanner.cpp @@ -14,161 +14,8 @@ #include -#ifdef WEBSERVER_NEW_UI -// ******************************************************************************** -// Web Interface I2C scanner -// ******************************************************************************** - -int scanI2CbusForDevices_json( // Utility function for scanning the I2C bus for valid devices, with JSON output - uint8_t i2cBus, - int8_t muxAddr - , int8_t channel - , int nDevices - #if FEATURE_I2CMULTIPLEXER - , i2c_addresses_t &excludeDevices - #endif // if FEATURE_I2CMULTIPLEXER -) { - uint8_t error, address; - - for (address = 1; address <= 127; address++) - { - #if FEATURE_I2CMULTIPLEXER - bool skipCheck = false; - if (channel != -1 && excludeDevices.size() > address) { - skipCheck = excludeDevices[address]; - } - if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels - #endif // if FEATURE_I2CMULTIPLEXER - I2C_wakeup(address); // Wakeup, workaround for slow-responding devices, see https://github.com/letscontrolit/ESPEasy/issues/3781 - delay(1); - error = I2C_wakeup(address); // Get status - delay(1); - - if ((error == 0) || (error == 4)) - { - json_open(); - json_prop(F("addr"), formatToHex(address, 2)); - #if FEATURE_I2CMULTIPLEXER - if (muxAddr != -1) { - if (channel == -1){ - json_prop(F("I2Cbus"), F("Standard I2C bus")); - excludeDevices[address] = true; - } else { - String i2cChannel = F("Multiplexer channel "); - i2cChannel += String(channel); - json_prop(F("I2Cbus"), i2cChannel); - } - } - #endif // if FEATURE_I2CMULTIPLEXER - json_number(F("status"), String(error)); - - if (error == 4) { - json_prop(F("error"), F("Unknown error at address ")); - } else { - String description = getKnownI2Cdevice(address); - - if (description.length() > 0) { - json_open(true, F("known devices")); - int pos = 0; - - while (pos >= 0) { - int newpos = description.indexOf(',', pos); - - if (pos != 0) { - addHtml(','); - } - - if (newpos == -1) { - json_quote_val(description.substring(pos)); - } else { - json_quote_val(description.substring(pos, newpos)); - } - pos = newpos; - - if (newpos != -1) { - ++pos; - } - } - json_close(true); - } - nDevices++; - } - json_close(); - addHtml('\n'); - } - #if FEATURE_I2CMULTIPLEXER - } - #endif // if FEATURE_I2CMULTIPLEXER - } - return nDevices; -} - -void handle_i2cscanner_json() { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_i2cscanner")); - #endif - - if (!startJSON_Stream()) { return; } - - json_init(); - json_open(true); - - int nDevices = 0; - #ifdef ESP8266 - #endif - #if !FEATURE_I2C_MULTIPLE - const uint8_t i2cBus = 0; - #else - for (uint8_t i2cBus = 0; i2cBus < getI2CBusCount(); ++i2cBus) - #endif - { - if (Settings.isI2CEnabled(i2cBus)) - { - I2CSelect_Max100kHz_ClockSpeed(i2cBus); // Always scan in low speed to also find old/slow devices - #if FEATURE_I2CMULTIPLEXER - int8_t Multiplexer_Addr = Settings.I2C_Multiplexer_Addr; // TODO Move to SettingsStruct - #if FEATURE_I2C_MULTIPLE - if (i2cBus == 1) { - Multiplexer_Addr = Settings.I2C2_Multiplexer_Addr; - } - #if FEATURE_I2C_INTERFACE_3 - else if (i2cBus == 2) { - Multiplexer_Addr = Settings.I2C3_Multiplexer_Addr; - } - #endif // if FEATURE_I2C_INTERFACE_3 - #endif // if FEATURE_I2C_MULTIPLE - i2c_addresses_t mainBusDevices; - mainBusDevices.resize(128); - for (int i = 0; i < 128; i++) { - mainBusDevices[i] = false; - } - nDevices = scanI2CbusForDevices_json(i2cBus, Multiplexer_Addr, -1, nDevices, mainBusDevices); // Channel -1 = standard I2C bus - #else // if FEATURE_I2CMULTIPLEXER - nDevices = scanI2CbusForDevices_json(0, -1, -1, nDevices); // Standard scan - #endif // if FEATURE_I2CMULTIPLEXER - - #if FEATURE_I2CMULTIPLEXER - if (isI2CMultiplexerEnabled(i2cBus)) { - uint8_t mux_max = I2CMultiplexerMaxChannels(i2cBus); - for (int8_t channel = 0; channel < mux_max; channel++) { - I2CMultiplexerSelect(i2cBus, channel); - nDevices += scanI2CbusForDevices_json(i2cBus, Multiplexer_Addr, channel, nDevices, mainBusDevices); // Specific channels - } - I2CMultiplexerOff(i2cBus); - } - #endif // if FEATURE_I2CMULTIPLEXER - } - } - I2CSelectHighClockSpeed(0); // Reset bus to standard speed - - json_close(true); - TXBuffer.endStream(); -} -#endif // WEBSERVER_NEW_UI - - -String getKnownI2Cdevice(uint8_t address) { +static String getKnownI2Cdevice(uint8_t address) { String result; #if FEATURE_I2C_DEVICE_SCAN @@ -414,7 +261,163 @@ String getKnownI2Cdevice(uint8_t address) { return result; } -int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid devices, with HTML table output +#ifdef WEBSERVER_NEW_UI + +// ******************************************************************************** +// Web Interface I2C scanner +// ******************************************************************************** + +int scanI2CbusForDevices_json( // Utility function for scanning the I2C bus for valid devices, with JSON output + uint8_t i2cBus, + int8_t muxAddr + , int8_t channel + , int nDevices + #if FEATURE_I2CMULTIPLEXER + , i2c_addresses_t &excludeDevices + #endif // if FEATURE_I2CMULTIPLEXER +) { + uint8_t error, address; + + for (address = 1; address <= 127; address++) + { + #if FEATURE_I2CMULTIPLEXER + bool skipCheck = false; + if (channel != -1 && excludeDevices.size() > address) { + skipCheck = excludeDevices[address]; + } + if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels + #endif // if FEATURE_I2CMULTIPLEXER + I2C_wakeup(address); // Wakeup, workaround for slow-responding devices, see https://github.com/letscontrolit/ESPEasy/issues/3781 + delay(1); + error = I2C_wakeup(address); // Get status + delay(1); + + if ((error == 0) || (error == 4)) + { + json_open(); + json_prop(F("addr"), formatToHex(address, 2)); + #if FEATURE_I2CMULTIPLEXER + if (muxAddr != -1) { + if (channel == -1){ + json_prop(F("I2Cbus"), F("Standard I2C bus")); + excludeDevices[address] = true; + } else { + String i2cChannel = F("Multiplexer channel "); + i2cChannel += String(channel); + json_prop(F("I2Cbus"), i2cChannel); + } + } + #endif // if FEATURE_I2CMULTIPLEXER + json_number(F("status"), String(error)); + + if (error == 4) { + json_prop(F("error"), F("Unknown error at address ")); + } else { + String description = getKnownI2Cdevice(address); + + if (description.length() > 0) { + json_open(true, F("known devices")); + int pos = 0; + + while (pos >= 0) { + int newpos = description.indexOf(',', pos); + + if (pos != 0) { + addHtml(','); + } + + if (newpos == -1) { + json_quote_val(description.substring(pos)); + } else { + json_quote_val(description.substring(pos, newpos)); + } + pos = newpos; + + if (newpos != -1) { + ++pos; + } + } + json_close(true); + } + nDevices++; + } + json_close(); + addHtml('\n'); + } + #if FEATURE_I2CMULTIPLEXER + } + #endif // if FEATURE_I2CMULTIPLEXER + } + return nDevices; +} + +void handle_i2cscanner_json() { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_i2cscanner")); + #endif + + if (!startJSON_Stream()) { return; } + + json_init(); + json_open(true); + + int nDevices = 0; + #ifdef ESP8266 + #endif + #if !FEATURE_I2C_MULTIPLE + const uint8_t i2cBus = 0; + #else + for (uint8_t i2cBus = 0; i2cBus < getI2CBusCount(); ++i2cBus) + #endif + { + if (Settings.isI2CEnabled(i2cBus)) + { + I2CSelect_Max100kHz_ClockSpeed(i2cBus); // Always scan in low speed to also find old/slow devices + #if FEATURE_I2CMULTIPLEXER + int8_t Multiplexer_Addr = Settings.I2C_Multiplexer_Addr; // TODO Move to SettingsStruct + #if FEATURE_I2C_MULTIPLE + if (i2cBus == 1) { + Multiplexer_Addr = Settings.I2C2_Multiplexer_Addr; + } + #if FEATURE_I2C_INTERFACE_3 + else if (i2cBus == 2) { + Multiplexer_Addr = Settings.I2C3_Multiplexer_Addr; + } + #endif // if FEATURE_I2C_INTERFACE_3 + #endif // if FEATURE_I2C_MULTIPLE + i2c_addresses_t mainBusDevices; + mainBusDevices.resize(128); + for (int i = 0; i < 128; i++) { + mainBusDevices[i] = false; + } + nDevices = scanI2CbusForDevices_json(i2cBus, Multiplexer_Addr, -1, nDevices, mainBusDevices); // Channel -1 = standard I2C bus + #else // if FEATURE_I2CMULTIPLEXER + nDevices = scanI2CbusForDevices_json(0, -1, -1, nDevices); // Standard scan + #endif // if FEATURE_I2CMULTIPLEXER + + #if FEATURE_I2CMULTIPLEXER + if (isI2CMultiplexerEnabled(i2cBus)) { + uint8_t mux_max = I2CMultiplexerMaxChannels(i2cBus); + for (int8_t channel = 0; channel < mux_max; channel++) { + I2CMultiplexerSelect(i2cBus, channel); + nDevices += scanI2CbusForDevices_json(i2cBus, Multiplexer_Addr, channel, nDevices, mainBusDevices); // Specific channels + } + I2CMultiplexerOff(i2cBus); + } + #endif // if FEATURE_I2CMULTIPLEXER + } + } + I2CSelectHighClockSpeed(0); // Reset bus to standard speed + + json_close(true); + TXBuffer.endStream(); +} +#endif // WEBSERVER_NEW_UI + + + + +static int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid devices, with HTML table output uint8_t i2cBus, int8_t muxAddr , int8_t channel diff --git a/src/src/WebServer/I2C_Scanner.h b/src/src/WebServer/I2C_Scanner.h index 5ac953d51..6bbefe7f4 100644 --- a/src/src/WebServer/I2C_Scanner.h +++ b/src/src/WebServer/I2C_Scanner.h @@ -32,19 +32,6 @@ void handle_i2cscanner_json(); # endif // WEBSERVER_NEW_UI -String getKnownI2Cdevice(uint8_t address); - -int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid devices, with HTML table output - uint8_t i2cBus, - int8_t muxAddr, - int8_t channel, - int nDevices - # if FEATURE_I2CMULTIPLEXER - , - i2c_addresses_t& excludeDevices - # endif // if FEATURE_I2CMULTIPLEXER - ); - // FIXME TD-er: Query all included plugins for their supported addresses (return name of plugin) void handle_i2cscanner(); #endif // WEBSERVER_I2C_SCANNER diff --git a/src/src/WebServer/InterfacesPage.cpp b/src/src/WebServer/InterfacesPage.cpp index 25c0afc28..c027eed5a 100644 --- a/src/src/WebServer/InterfacesPage.cpp +++ b/src/src/WebServer/InterfacesPage.cpp @@ -28,191 +28,85 @@ // ******************************************************************************** // Web Interface hardware page // ******************************************************************************** -#if FEATURE_I2C +# if FEATURE_I2C + void handle_interfaces_i2c() { navMenuIndex = MENU_INDEX_INTERFACES_I2C; handle_interfaces(); } -#endif // if FEATURE_I2C -#if FEATURE_SPI +# endif // if FEATURE_I2C + +# if FEATURE_SPI + void handle_interfaces_spi() { navMenuIndex = MENU_INDEX_INTERFACES_SPI; handle_interfaces(); } -#endif // if FEATURE_SPI -#if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB +# endif // if FEATURE_SPI + +# if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB + void handle_interfaces_modbus() { navMenuIndex = MENU_INDEX_INTERFACES_MODBUS; handle_interfaces(); } -#endif // if FEATURE_MODBUS -#if FEATURE_CAN +# endif // if FEATURE_MODBUS + +# if FEATURE_CAN + void handle_interfaces_can() { navMenuIndex = MENU_INDEX_INTERFACES_CAN; handle_interfaces(); } -#endif // if FEATURE_CAN -#if FEATURE_WRMBUS +# endif // if FEATURE_CAN + +# if FEATURE_WRMBUS + void handle_interfaces_wrmbus() { navMenuIndex = MENU_INDEX_INTERFACES_WRMBUS; handle_interfaces(); } -#endif // if FEATURE_WRMBUS -#if FEATURE_WIMBUS +# endif // if FEATURE_WRMBUS + +# if FEATURE_WIMBUS + void handle_interfaces_wimbus() { navMenuIndex = MENU_INDEX_INTERFACES_WIMBUS; handle_interfaces(); } -#endif // if FEATURE_WIMBUS -void handle_interfaces() { -# ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_interfaces")); -# endif +# endif // if FEATURE_WIMBUS - uint8_t newNavIndex = navMenuIndex; - if ((newNavIndex != MENU_INDEX_INTERFACES) && (newNavIndex <= MENU_MAX_INDEX_SHOWN)) { -#if FEATURE_I2C - newNavIndex = MENU_INDEX_INTERFACES_I2C; -#elif FEATURE_SPI - newNavIndex = MENU_INDEX_INTERFACES_SPI; -#elif FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB - newNavIndex = MENU_INDEX_INTERFACES_MODBUS; -#elif FEATURE_CAN - newNavIndex = MENU_INDEX_INTERFACES_CAN; -#elif FEATURE_WRMBUS - newNavIndex = MENU_INDEX_INTERFACES_WRMBUS; -#elif FEATURE_WIMBUS - newNavIndex = MENU_INDEX_INTERFACES_WIMBUS; -#else - // FIXME TD-er: Should we still continue here? Why not simply return; - newNavIndex = MENU_INDEX_INTERFACES; -#endif + +# if FEATURE_I2C +# if FEATURE_PLUGIN_PRIORITY + +static bool isI2CPriorityTaskActive(uint8_t i2cBus) { + bool hasI2CPriorityTask = false; + + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX && !hasI2CPriorityTask; taskIndex++) { + hasI2CPriorityTask |= isPluginI2CPowerManager_from_TaskIndex(taskIndex, i2cBus); } - if (!startStream_send_stdTemplate(newNavIndex)) return; - - save_interfaces(); - - - 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")); - - #if FEATURE_I2C - if (navMenuIndex == MENU_INDEX_INTERFACES_I2C) { - interfaces_show_I2C(); - } - #endif - - #if FEATURE_SPI - if (navMenuIndex == MENU_INDEX_INTERFACES_SPI) { - interfaces_show_SPI(); - } - #endif - - #if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB - if (navMenuIndex == MENU_INDEX_INTERFACES_MODBUS) { - interfaces_show_MODBUS(); - } - #endif // if FEATURE_MODBUS - - #if FEATURE_CAN - if (navMenuIndex == MENU_INDEX_INTERFACES_CAN) { - interfaces_show_CAN(); - } - #endif // if FEATURE_CAN - - #if FEATURE_WRMBUS - if (navMenuIndex == MENU_INDEX_INTERFACES_WRMBUS) { - interfaces_show_WRMBUS(); - } - #endif // if FEATURE_WRMBUS - - #if FEATURE_WIMBUS - if (navMenuIndex == MENU_INDEX_INTERFACES_WIMBUS) { - interfaces_show_WIMBUS(); - } - #endif // if FEATURE_WIMBUS - - addFormSeparator(2); - - html_TR_TD(); - html_TD(); - addSubmitButton(); - html_TR_TD(); - html_end_table(); - html_end_form(); - - sendTail_stdtemplate(); + return hasI2CPriorityTask; } -void save_interfaces() { +# endif // if FEATURE_PLUGIN_PRIORITY - String error; - bool updated{}; - - #if FEATURE_I2C - - if ((navMenuIndex == MENU_INDEX_INTERFACES_I2C) && save_I2C(error)) { updated = true; } - #endif - - #if FEATURE_SPI - if ((navMenuIndex == MENU_INDEX_INTERFACES_SPI) && save_SPI(error)) { updated = true; } - #endif - - #if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB - if ((navMenuIndex == MENU_INDEX_INTERFACES_MODBUS) && save_MODBUS(error)) { updated = true; } - #endif // if FEATURE_MODBUS - - #if FEATURE_CAN - if ((navMenuIndex == MENU_INDEX_INTERFACES_CAN) && save_CAN(error)) { updated = true; } - #endif // if FEATURE_CAN - - #if FEATURE_WRMBUS - if ((navMenuIndex == MENU_INDEX_INTERFACES_WRMBUS) && save_WRMBUS(error)) { updated = true; } - #endif // if FEATURE_WRMBUS - - #if FEATURE_WIMBUS - if ((navMenuIndex == MENU_INDEX_INTERFACES_WIMBUS) && save_WIMBUS(error)) { updated = true; } - #endif // if FEATURE_WIMBUS - - if (updated) { - error += SaveSettings(); - addHtmlError(error); - - if (error.isEmpty()) { -#if FEATURE_I2C - // Apply I2C settings. - initI2C(); -#endif - -#if FEATURE_SPI - // Apply SPI settings - initializeSPIBuses(); -#endif - } - } -} - -#if FEATURE_I2C - -bool save_I2C(String& error) { +static bool save_I2C(String& error) { bool updated{}; if (isFormItem(F("pi2csp0"))) { updated = true; -# if FEATURE_PLUGIN_PRIORITY +# if FEATURE_PLUGIN_PRIORITY if (!isI2CPriorityTaskActive(0)) -# endif // if FEATURE_PLUGIN_PRIORITY +# endif // if FEATURE_PLUGIN_PRIORITY { update_whenset_FormItemInt(F("psda0"), Settings.Pin_i2c_sda); update_whenset_FormItemInt(F("pscl0"), Settings.Pin_i2c_scl); @@ -220,7 +114,7 @@ bool save_I2C(String& error) { Settings.I2C_clockSpeed = getFormItemInt(F("pi2csp0"), DEFAULT_I2C_CLOCK_SPEED); Settings.I2C_clockSpeed_Slow = getFormItemInt(F("pi2cspslow0"), DEFAULT_I2C_CLOCK_SPEED_SLOW); Settings.WireClockStretchLimit = getFormItemInt(F("wirestretch")); -# if FEATURE_I2CMULTIPLEXER +# if FEATURE_I2CMULTIPLEXER Settings.I2C_Multiplexer_Type = getFormItemInt(F("pi2cmuxtype0")); if (Settings.I2C_Multiplexer_Type != I2C_MULTIPLEXER_NONE) { @@ -229,23 +123,23 @@ bool save_I2C(String& error) { Settings.I2C_Multiplexer_Addr = -1; } Settings.I2C_Multiplexer_ResetPin = getFormItemInt(F("pi2cmuxreset0")); -# endif // if FEATURE_I2CMULTIPLEXER +# endif // if FEATURE_I2CMULTIPLEXER } -# if FEATURE_I2C_MULTIPLE // No loop used here, to avoid adding setters to the SettingsStruct template code +# if FEATURE_I2C_MULTIPLE // No loop used here, to avoid adding setters to the SettingsStruct template code if ((getI2CBusCount() > 1) && isFormItem(F("pi2csp1"))) { updated = true; -# if FEATURE_PLUGIN_PRIORITY +# if FEATURE_PLUGIN_PRIORITY if (!isI2CPriorityTaskActive(1)) -# endif // if FEATURE_PLUGIN_PRIORITY +# endif // if FEATURE_PLUGIN_PRIORITY { update_whenset_FormItemInt(F("psda1"), Settings.Pin_i2c2_sda); update_whenset_FormItemInt(F("pscl1"), Settings.Pin_i2c2_scl); } Settings.I2C2_clockSpeed = getFormItemInt(F("pi2csp1"), DEFAULT_I2C_CLOCK_SPEED); Settings.I2C2_clockSpeed_Slow = getFormItemInt(F("pi2cspslow1"), DEFAULT_I2C_CLOCK_SPEED_SLOW); -# if FEATURE_I2CMULTIPLEXER +# if FEATURE_I2CMULTIPLEXER Settings.I2C2_Multiplexer_Type = getFormItemInt(F("pi2cmuxtype1")); if (Settings.I2C2_Multiplexer_Type != I2C_MULTIPLEXER_NONE) { @@ -254,25 +148,25 @@ bool save_I2C(String& error) { Settings.I2C2_Multiplexer_Addr = -1; } Settings.I2C2_Multiplexer_ResetPin = getFormItemInt(F("pi2cmuxreset1")); -# endif // if FEATURE_I2CMULTIPLEXER +# endif // if FEATURE_I2CMULTIPLEXER } -# if FEATURE_I2C_INTERFACE_3 +# if FEATURE_I2C_INTERFACE_3 if ((getI2CBusCount() > 2) && isFormItem(F("pi2csp2"))) { updated = true; -# if FEATURE_PLUGIN_PRIORITY +# if FEATURE_PLUGIN_PRIORITY if (!isI2CPriorityTaskActive(2)) -# endif // if FEATURE_PLUGIN_PRIORITY +# endif // if FEATURE_PLUGIN_PRIORITY { update_whenset_FormItemInt(F("psda2"), Settings.Pin_i2c3_sda); update_whenset_FormItemInt(F("pscl2"), Settings.Pin_i2c3_scl); } Settings.I2C3_clockSpeed = getFormItemInt(F("pi2csp2"), DEFAULT_I2C_CLOCK_SPEED); Settings.I2C3_clockSpeed_Slow = getFormItemInt(F("pi2cspslow2"), DEFAULT_I2C_CLOCK_SPEED_SLOW); -# if FEATURE_I2CMULTIPLEXER +# if FEATURE_I2CMULTIPLEXER Settings.I2C3_Multiplexer_Type = getFormItemInt(F("pi2cmuxtype2")); if (Settings.I2C3_Multiplexer_Type != I2C_MULTIPLEXER_NONE) { @@ -281,21 +175,22 @@ bool save_I2C(String& error) { Settings.I2C3_Multiplexer_Addr = -1; } Settings.I2C3_Multiplexer_ResetPin = getFormItemInt(F("pi2cmuxreset2")); -# endif // if FEATURE_I2CMULTIPLEXER +# endif // if FEATURE_I2CMULTIPLEXER } -# endif // if FEATURE_I2C_INTERFACE_3 +# endif // if FEATURE_I2C_INTERFACE_3 -# endif // if FEATURE_I2C_MULTIPLE +# endif // if FEATURE_I2C_MULTIPLE return updated; } -#endif +# endif // if FEATURE_I2C -#if FEATURE_SPI -bool save_SPI(String& error) { -# ifdef ESP32 +# if FEATURE_SPI + +static bool save_SPI(String& error) { +# ifdef ESP32 bool SPI_updated{}; if (update_whenset_FormItemInt(F("initspi0"), Settings.InitSPI)) { @@ -324,42 +219,67 @@ bool save_SPI(String& error) { } } return SPI_updated; -# else // for ESP8266 we keep the old UI +# else // for ESP8266 we keep the old UI const auto old_initSPI = Settings.InitSPI; Settings.InitSPI = isFormItemChecked(F("initspi")); // SPI Init return old_initSPI != Settings.InitSPI; -# endif // ifdef ESP32 +# endif // ifdef ESP32 } -#endif +# endif // if FEATURE_SPI -#if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB -bool save_MODBUS(String& error) { +# if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB + +static bool save_MODBUS(String& error) { return false; // TODO } -#endif // if FEATURE_MODBUS -#if FEATURE_CAN -bool save_CAN(String& error) { +# endif // if FEATURE_MODBUS + +# if FEATURE_CAN + +static bool save_CAN(String& error) { return false; // TODO } -#endif // if FEATURE_CAN -#if FEATURE_WRMBUS -bool save_WRMBUS(String& error) { +# endif // if FEATURE_CAN + +# if FEATURE_WRMBUS + +static bool save_WRMBUS(String& error) { return false; // TODO } -#endif // if FEATURE_WRMBUS -#if FEATURE_WIMBUS -bool save_WIMBUS(String& error) { +# endif // if FEATURE_WRMBUS + +# if FEATURE_WIMBUS + +static bool save_WIMBUS(String& error) { return false; // TODO } -#endif // if FEATURE_WIMBUS -#if FEATURE_I2C -void interfaces_show_I2C() { -# if FEATURE_I2CMULTIPLEXER +# endif // if FEATURE_WIMBUS + +# if FEATURE_I2C +# if FEATURE_PLUGIN_PRIORITY + +static void I2CShowSdaSclReadonly(int8_t i2c_sda, int8_t i2c_scl, uint8_t i2cBus) { + int pinnr = -1; + bool input, output, warning = false; + + addFormNote(strformat(F("I2C (%d) GPIO pins can't be changed when an I2C Priority task is configured."), i2cBus)); + addRowLabel(formatGpioName_bidirectional(F("SDA"))); + getGpioInfo(i2c_sda, pinnr, input, output, warning); + addHtml(createGPIO_label(i2c_sda, pinnr, true, true, false)); + addRowLabel(formatGpioName_output(F("SCL"))); + getGpioInfo(i2c_scl, pinnr, input, output, warning); + addHtml(createGPIO_label(i2c_scl, pinnr, true, true, false)); +} + +# endif // if FEATURE_PLUGIN_PRIORITY + +static void interfaces_show_I2C() { +# if FEATURE_I2CMULTIPLEXER const __FlashStringHelper *i2c_muxtype_options[] = { F("- None -"), F("TCA9548a - 8 channel"), @@ -400,26 +320,26 @@ void interfaces_show_I2C() { mux_opt++; } const FormSelectorOptions addrSelector(mux_opt, i2c_mux_options, i2c_mux_choices); -# endif // if FEATURE_I2CMULTIPLEXER +# endif // if FEATURE_I2CMULTIPLEXER uint8_t i2cBus = 0; -# if FEATURE_I2C_MULTIPLE +# if FEATURE_I2C_MULTIPLE for (uint8_t i2cBus = 0; i2cBus < getI2CBusCount(); ++i2cBus) -# endif // if FEATURE_I2C_MULTIPLE +# endif // if FEATURE_I2C_MULTIPLE { -# if !FEATURE_I2C_MULTIPLE +# if !FEATURE_I2C_MULTIPLE addFormSubHeader(F("I2C Bus")); -# else +# else addFormSubHeader(strformat(F("I2C Bus %u"), i2cBus)); -# endif // if !FEATURE_I2C_MULTIPLE +# endif // if !FEATURE_I2C_MULTIPLE addFormDetailsStart((0 == i2cBus) || Settings.isI2CEnabled(i2cBus)); -# if FEATURE_PLUGIN_PRIORITY +# if FEATURE_PLUGIN_PRIORITY if (isI2CPriorityTaskActive(i2cBus)) { I2CShowSdaSclReadonly(Settings.getI2CSdaPin(i2cBus), Settings.getI2CSclPin(i2cBus), i2cBus); } else -# endif // if FEATURE_PLUGIN_PRIORITY +# endif // if FEATURE_PLUGIN_PRIORITY { addFormPinSelectI2C(formatGpioName_bidirectional(F("SDA")), strformat(F("psda%u"), i2cBus), i2cBus, Settings.getI2CSdaPin(i2cBus)); addFormPinSelectI2C(formatGpioName_output(F("SCL")), strformat(F("pscl%u"), i2cBus), i2cBus, Settings.getI2CSclPin(i2cBus)); @@ -433,22 +353,23 @@ void interfaces_show_I2C() { if (0 == i2cBus) { // Only support Clock-stretching on Bus 1 addFormNumericBox(F("I2C ClockStretchLimit"), F("wirestretch"), Settings.WireClockStretchLimit, 0); -# ifdef ESP8266 +# ifdef ESP8266 addUnit(F("usec")); -# endif -# ifdef ESP32 +# endif +# ifdef ESP32 addUnit(F("1/80 usec")); -# endif +# endif } -# if FEATURE_I2CMULTIPLEXER -# if !FEATURE_I2C_MULTIPLE +# if FEATURE_I2CMULTIPLEXER +# if !FEATURE_I2C_MULTIPLE addFormSubHeader(F("I2C Multiplexer")); -# else // if !FEATURE_I2C_MULTIPLE +# else // if !FEATURE_I2C_MULTIPLE addFormSubHeader(strformat(F("I2C Multiplexer %u"), i2cBus)); -# endif // if !FEATURE_I2C_MULTIPLE +# endif // if !FEATURE_I2C_MULTIPLE addFormDetailsStart(Settings.getI2CMultiplexerType(i2cBus) != I2C_MULTIPLEXER_NONE); + // Select the type of multiplexer to use { muxSelector.addFormSelector(F("I2C Multiplexer type"), strformat(F("pi2cmuxtype%u"), i2cBus), Settings.getI2CMultiplexerType(i2cBus)); @@ -463,22 +384,25 @@ void interfaces_show_I2C() { addFormNote(F("Will be pulled low to force a reset. Reset is not available on PCA9540.")); } addFormDetailsEnd(); -# endif // if FEATURE_I2CMULTIPLEXER - addFormDetailsEnd(); +# endif // if FEATURE_I2CMULTIPLEXER + addFormDetailsEnd(); } } -#endif -#if FEATURE_SPI -void interfaces_show_SPI() { +# endif // if FEATURE_I2C -# ifdef ESP32 +# if FEATURE_SPI + +static void interfaces_show_SPI() { + +# ifdef ESP32 for (uint8_t spi_bus = 0; spi_bus < getSPIBusCount(); ++spi_bus) { // SPI Init addFormSubHeader(concat(F("SPI Bus "), spi_bus)); { addFormDetailsStart(0 == spi_bus || ((0 != spi_bus) && (static_cast(Settings.InitSPI1) != SPI_Options_e::None))); + // Script to show GPIO pins for User-defined SPI GPIOs // html_add_script(F("function spiOptionChanged(elem) {var spipinstyle = elem.value == 9 ? '' : // 'none';document.getElementById('tr_spipinsclk').style.display = spipinstyle;document.getElementById('tr_spipinmiso').style.display @@ -493,24 +417,24 @@ void interfaces_show_SPI() { const __FlashStringHelper *spi_options[] = { getSPI_optionToString(SPI_Options_e::None), getSPI_optionToString(SPI_Options_e::Vspi_Fspi), -# ifdef ESP32_CLASSIC +# ifdef ESP32_CLASSIC getSPI_optionToString(SPI_Options_e::Hspi), -# endif +# endif getSPI_optionToString(SPI_Options_e::UserDefined_VSPI) -# if SOC_SPI_PERIPH_NUM > 2 +# if SOC_SPI_PERIPH_NUM > 2 , getSPI_optionToString(SPI_Options_e::UserDefined_HSPI) -# endif +# endif }; const int spi_index[] = { static_cast(SPI_Options_e::None), static_cast(SPI_Options_e::Vspi_Fspi), -# ifdef ESP32_CLASSIC +# ifdef ESP32_CLASSIC static_cast(SPI_Options_e::Hspi), -# endif +# endif static_cast(SPI_Options_e::UserDefined_VSPI) -# if SOC_SPI_PERIPH_NUM > 2 +# if SOC_SPI_PERIPH_NUM > 2 , static_cast(SPI_Options_e::UserDefined_HSPI) -# endif +# endif }; constexpr size_t nrOptions = NR_ELEMENTS(spi_index); FormSelectorOptions selector(nrOptions, spi_options, spi_index); @@ -530,65 +454,193 @@ void interfaces_show_SPI() { addFormDetailsEnd(); } } -# else // for ESP8266 we keep the existing UI +# else // for ESP8266 we keep the existing UI addFormSubHeader(F("SPI Bus 0")); addFormCheckBox(F("Init SPI"), F("initspi"), Settings.InitSPI > static_cast(SPI_Options_e::None)); addFormNote(F("CLK=GPIO-14 (D5), MISO=GPIO-12 (D6), MOSI=GPIO-13 (D7)")); addFormNote(F("Chip Select (CS) config must be done in the plugin")); -# endif // ifdef ESP32 +# endif // ifdef ESP32 } -#endif -#if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB -void interfaces_show_MODBUS() { +# endif // if FEATURE_SPI + +# if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB + +static void interfaces_show_MODBUS() { addRowLabel(F("TODO")); // TODO } -#endif // if FEATURE_MODBUS -#if FEATURE_CAN -void interfaces_show_CAN() { +# endif // if FEATURE_MODBUS + +# if FEATURE_CAN + +static void interfaces_show_CAN() { addRowLabel(F("TODO")); // TODO } -#endif // if FEATURE_CAN -#if FEATURE_WRMBUS -void interfaces_show_WRMBUS() { +# endif // if FEATURE_CAN + +# if FEATURE_WRMBUS + +static void interfaces_show_WRMBUS() { addRowLabel(F("TODO")); // TODO } -#endif // if FEATURE_WRMBUS -#if FEATURE_WIMBUS -void interfaces_show_WIMBUS() { +# endif // if FEATURE_WRMBUS + +# if FEATURE_WIMBUS + +static void interfaces_show_WIMBUS() { addRowLabel(F("TODO")); // TODO } -#endif // if FEATURE_WIMBUS -#if FEATURE_I2C -# if FEATURE_PLUGIN_PRIORITY +# endif // if FEATURE_WIMBUS -bool isI2CPriorityTaskActive(uint8_t i2cBus) { - bool hasI2CPriorityTask = false; +static void save_interfaces() { - for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX && !hasI2CPriorityTask; taskIndex++) { - hasI2CPriorityTask |= isPluginI2CPowerManager_from_TaskIndex(taskIndex, i2cBus); + String error; + bool updated{}; + + # if FEATURE_I2C + + if ((navMenuIndex == MENU_INDEX_INTERFACES_I2C) && save_I2C(error)) { updated = true; } + # endif // if FEATURE_I2C + + # if FEATURE_SPI + + if ((navMenuIndex == MENU_INDEX_INTERFACES_SPI) && save_SPI(error)) { updated = true; } + # endif // if FEATURE_SPI + + # if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB + + if ((navMenuIndex == MENU_INDEX_INTERFACES_MODBUS) && save_MODBUS(error)) { updated = true; } + # endif // if FEATURE_MODBUS + + # if FEATURE_CAN + + if ((navMenuIndex == MENU_INDEX_INTERFACES_CAN) && save_CAN(error)) { updated = true; } + # endif // if FEATURE_CAN + + # if FEATURE_WRMBUS + + if ((navMenuIndex == MENU_INDEX_INTERFACES_WRMBUS) && save_WRMBUS(error)) { updated = true; } + # endif // if FEATURE_WRMBUS + + # if FEATURE_WIMBUS + + if ((navMenuIndex == MENU_INDEX_INTERFACES_WIMBUS) && save_WIMBUS(error)) { updated = true; } + # endif // if FEATURE_WIMBUS + + if (updated) { + error += SaveSettings(); + addHtmlError(error); + + if (error.isEmpty()) { +# if FEATURE_I2C + + // Apply I2C settings. + initI2C(); +# endif // if FEATURE_I2C + +# if FEATURE_SPI + + // Apply SPI settings + initializeSPIBuses(); +# endif // if FEATURE_SPI + } } - return hasI2CPriorityTask; } -void I2CShowSdaSclReadonly(int8_t i2c_sda, int8_t i2c_scl, uint8_t i2cBus) { - int pinnr = -1; - bool input, output, warning = false; +void handle_interfaces() { +# ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_interfaces")); +# endif - addFormNote(strformat(F("I2C (%d) GPIO pins can't be changed when an I2C Priority task is configured."), i2cBus)); - addRowLabel(formatGpioName_bidirectional(F("SDA"))); - getGpioInfo(i2c_sda, pinnr, input, output, warning); - addHtml(createGPIO_label(i2c_sda, pinnr, true, true, false)); - addRowLabel(formatGpioName_output(F("SCL"))); - getGpioInfo(i2c_scl, pinnr, input, output, warning); - addHtml(createGPIO_label(i2c_scl, pinnr, true, true, false)); + uint8_t newNavIndex = navMenuIndex; + + if ((newNavIndex != MENU_INDEX_INTERFACES) && (newNavIndex <= MENU_MAX_INDEX_SHOWN)) { +# if FEATURE_I2C + newNavIndex = MENU_INDEX_INTERFACES_I2C; +# elif FEATURE_SPI + newNavIndex = MENU_INDEX_INTERFACES_SPI; +# elif FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB + newNavIndex = MENU_INDEX_INTERFACES_MODBUS; +# elif FEATURE_CAN + newNavIndex = MENU_INDEX_INTERFACES_CAN; +# elif FEATURE_WRMBUS + newNavIndex = MENU_INDEX_INTERFACES_WRMBUS; +# elif FEATURE_WIMBUS + newNavIndex = MENU_INDEX_INTERFACES_WIMBUS; +# else // if FEATURE_I2C + + // FIXME TD-er: Should we still continue here? Why not simply return; + newNavIndex = MENU_INDEX_INTERFACES; +# endif // if FEATURE_I2C + } + + if (!startStream_send_stdTemplate(newNavIndex)) { return; } + + save_interfaces(); + + + 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")); + + # if FEATURE_I2C + + if (navMenuIndex == MENU_INDEX_INTERFACES_I2C) { + interfaces_show_I2C(); + } + # endif // if FEATURE_I2C + + # if FEATURE_SPI + + if (navMenuIndex == MENU_INDEX_INTERFACES_SPI) { + interfaces_show_SPI(); + } + # endif // if FEATURE_SPI + + # if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB + + if (navMenuIndex == MENU_INDEX_INTERFACES_MODBUS) { + interfaces_show_MODBUS(); + } + # endif // if FEATURE_MODBUS + + # if FEATURE_CAN + + if (navMenuIndex == MENU_INDEX_INTERFACES_CAN) { + interfaces_show_CAN(); + } + # endif // if FEATURE_CAN + + # if FEATURE_WRMBUS + + if (navMenuIndex == MENU_INDEX_INTERFACES_WRMBUS) { + interfaces_show_WRMBUS(); + } + # endif // if FEATURE_WRMBUS + + # if FEATURE_WIMBUS + + if (navMenuIndex == MENU_INDEX_INTERFACES_WIMBUS) { + interfaces_show_WIMBUS(); + } + # endif // if FEATURE_WIMBUS + + addFormSeparator(2); + + html_TR_TD(); + html_TD(); + addSubmitButton(); + html_TR_TD(); + html_end_table(); + html_end_form(); + + sendTail_stdtemplate(); } -# endif // if FEATURE_PLUGIN_PRIORITY -#endif - #endif // ifdef WEBSERVER_INTERFACES diff --git a/src/src/WebServer/InterfacesPage.h b/src/src/WebServer/InterfacesPage.h index a96601c7a..365f5ec8a 100644 --- a/src/src/WebServer/InterfacesPage.h +++ b/src/src/WebServer/InterfacesPage.h @@ -9,49 +9,27 @@ // ******************************************************************************** void handle_interfaces(); -void save_interfaces(); -#if FEATURE_I2C +# if FEATURE_I2C void handle_interfaces_i2c(); -bool save_I2C(String& error); -void interfaces_show_I2C(); -#endif -#if FEATURE_SPI -void handle_interfaces_spi(); -bool save_SPI(String& error); -void interfaces_show_SPI(); -#endif - -#if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB -void handle_interfaces_modbus(); -bool save_MODBUS(String& error); -void interfaces_show_MODBUS(); -#endif // if FEATURE_MODBUS - -#if FEATURE_CAN -void handle_interfaces_can(); -bool save_CAN(String& error); -void interfaces_show_CAN(); -#endif // if FEATURE_CAN - -#if FEATURE_WRMBUS -void handle_interfaces_wrmbus(); -bool save_WRMBUS(String& error); -void interfaces_show_WRMBUS(); -#endif // if FEATURE_WRMBUS - -#if FEATURE_WIMBUS -void handle_interfaces_wimbus(); -bool save_WIMBUS(String& error); -void interfaces_show_WIMBUS(); -#endif // if FEATURE_WIMBUS - - -#if FEATURE_I2C -# if FEATURE_PLUGIN_PRIORITY -bool isI2CPriorityTaskActive(uint8_t i2cBus); -void I2CShowSdaSclReadonly(int8_t i2c_sda, - int8_t i2c_scl, - uint8_t i2cBus); # endif -#endif -#endif +# if FEATURE_SPI +void handle_interfaces_spi(); +# endif + +# if FEATURE_MODBUS && FEATURE_MODBUS_INTERFACES_TAB +void handle_interfaces_modbus(); +# endif // if FEATURE_MODBUS + +# if FEATURE_CAN +void handle_interfaces_can(); +# endif // if FEATURE_CAN + +# if FEATURE_WRMBUS +void handle_interfaces_wrmbus(); +# endif // if FEATURE_WRMBUS + +# if FEATURE_WIMBUS +void handle_interfaces_wimbus(); +# endif // if FEATURE_WIMBUS + +#endif // ifdef WEBSERVER_INTERFACES diff --git a/src/src/WebServer/JSON.cpp b/src/src/WebServer/JSON.cpp index 8fe67d56d..acfcb4bf5 100644 --- a/src/src/WebServer/JSON.cpp +++ b/src/src/WebServer/JSON.cpp @@ -27,8 +27,9 @@ #include "../../_Plugin_Helper.h" #include "../../ESPEasy-Globals.h" -void stream_comma_newline() { addHtml(',', '\n'); } - +# if (FEATURE_PLUGIN_STATS && FEATURE_CHART_JS) || defined(WEBSERVER_NEW_UI) +static void stream_comma_newline() { addHtml(',', '\n'); } +#endif // ******************************************************************************** // Web Interface get CSV value from task @@ -777,24 +778,25 @@ void handle_buildinfo() { /*********************************************************************************************\ Streaming versions directly to TXBuffer \*********************************************************************************************/ -void stream_to_json_object_value(const __FlashStringHelper *object, const String& value) { - stream_to_json_object_value(String(object), value); +static void stream_to_json_object_value(const String& object, int value) { + addHtml(wrap_String(object, '"', '"')); + addHtml(':'); + addHtmlInt(value); } -void stream_to_json_object_value(const String& object, const String& value) { +static void stream_to_json_object_value(const String& object, const String& value) { addHtml(wrap_String(object, '"', '"')); addHtml(':'); addHtml(to_json_value(value)); } -void stream_to_json_object_value(const __FlashStringHelper *object, int value) { stream_to_json_object_value(String(object), value); } - -void stream_to_json_object_value(const String& object, int value) { - addHtml(wrap_String(object, '"', '"')); - addHtml(':'); - addHtmlInt(value); +static void stream_to_json_object_value(const __FlashStringHelper *object, const String& value) { + stream_to_json_object_value(String(object), value); } +static void stream_to_json_object_value(const __FlashStringHelper *object, int value) { stream_to_json_object_value(String(object), value); } + + #endif // if FEATURE_CHART_JS || defined(WEBSERVER_NEW_UI) String jsonBool(bool value) { return boolToString(value); } diff --git a/src/src/WebServer/JSON.h b/src/src/WebServer/JSON.h index 5c6f879d9..181338398 100644 --- a/src/src/WebServer/JSON.h +++ b/src/src/WebServer/JSON.h @@ -1,10 +1,7 @@ -#ifndef WEBSERVER_WEBSERVER_JSON_H -#define WEBSERVER_WEBSERVER_JSON_H - +#pragma once #include "../WebServer/common.h" -class KeyValueWriter; struct TaskValuesWriterHelper; // ******************************************************************************** @@ -15,14 +12,16 @@ void handle_csvval(); #endif #ifdef WEBSERVER_JSON + // ******************************************************************************** // Web Interface JSON page (no password!) // ******************************************************************************** void handle_json(); -void handle_json_stream_task_value_data(TaskValuesWriterHelper* data); +void handle_json_stream_task_value_data(TaskValuesWriterHelper*data); + +#endif // ifdef WEBSERVER_JSON -#endif // ******************************************************************************** // JSON formatted timing statistics // ******************************************************************************** @@ -30,12 +29,9 @@ void handle_json_stream_task_value_data(TaskValuesWriterHelper* data); #ifdef WEBSERVER_NEW_UI void handle_timingstats_json(); -#endif // WEBSERVER_NEW_UI - -#ifdef WEBSERVER_NEW_UI -#if FEATURE_ESPEASY_P2P +# if FEATURE_ESPEASY_P2P void handle_nodes_list_json(); -#endif +# endif void handle_buildinfo(); #endif // WEBSERVER_NEW_UI @@ -44,35 +40,34 @@ void handle_buildinfo(); /*********************************************************************************************\ Streaming versions directly to TXBuffer \*********************************************************************************************/ -#if FEATURE_CHART_JS || defined(WEBSERVER_NEW_UI) -void stream_to_json_object_value(const __FlashStringHelper * object, const String& value); -void stream_to_json_object_value(const String& object, const String& value); -void stream_to_json_object_value(const __FlashStringHelper * object, int value); -void stream_to_json_object_value(const String& object, int value); -#endif - String jsonBool(bool value); #ifdef WEBSERVER_NEW_UI + // Add JSON formatted data directly to the TXbuffer, including a trailing comma. -void stream_next_json_object_value(const __FlashStringHelper * object, const String& value); -void stream_next_json_object_value(const __FlashStringHelper * object, String&& value); -void stream_next_json_object_value(const String& object, const String& value); -void stream_next_json_object_value(const __FlashStringHelper * object, int value); -void stream_next_json_object_value(const String& object, int value); +void stream_next_json_object_value(const __FlashStringHelper *object, + const String & value); +void stream_next_json_object_value(const __FlashStringHelper *object, + String && value); +void stream_next_json_object_value(const String& object, + const String& value); +void stream_next_json_object_value(const __FlashStringHelper *object, + int value); +void stream_next_json_object_value(const String& object, + int value); // Add JSON formatted data directly to the TXbuffer, including a closing '}' -void stream_last_json_object_value(const __FlashStringHelper * object, const String& value); -void stream_last_json_object_value(const __FlashStringHelper * object, String&& value); -void stream_last_json_object_value(const String& object, const String& value); -void stream_last_json_object_value(const __FlashStringHelper * object, int value); +void stream_last_json_object_value(const __FlashStringHelper *object, + const String & value); +void stream_last_json_object_value(const __FlashStringHelper *object, + String && value); +void stream_last_json_object_value(const String& object, + const String& value); +void stream_last_json_object_value(const __FlashStringHelper *object, + int value); -//void stream_json_object_values(const LabelType::Enum labels[]); +// void stream_json_object_values(const LabelType::Enum labels[]); void stream_next_json_object_value(LabelType::Enum label); void stream_last_json_object_value(LabelType::Enum label); -#endif - - - -#endif \ No newline at end of file +#endif // ifdef WEBSERVER_NEW_UI diff --git a/src/src/WebServer/Metrics.cpp b/src/src/WebServer/Metrics.cpp index e83769f07..878fc0d6a 100644 --- a/src/src/WebServer/Metrics.cpp +++ b/src/src/WebServer/Metrics.cpp @@ -21,6 +21,61 @@ # include # endif // ifdef ESP32 +static void handle_metrics_devices() { + for (taskIndex_t x = 0; validTaskIndex(x); x++) { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); + const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); + + if (pluginID_set) { + if (Settings.TaskDeviceEnabled[x]) { + String deviceName = getTaskDeviceName(x); + + if (deviceName.isEmpty()) { // Empty name, then use taskN + deviceName = F("task"); + deviceName += x + 1; + } + addHtml(F("# HELP espeasy_device_")); + addHtml(deviceName); + addHtml(F(" Values from connected device\n")); + addHtml(F("# TYPE espeasy_device_")); + addHtml(deviceName); + addHtml(F(" gauge\n")); + + if (validDeviceIndex(DeviceIndex)) { + String customValuesString; + + // const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); + const bool customValues = 0; // TODO: handle custom values + + if (!customValues) { + const uint8_t valueCount = getValueCountForTask(x); + struct EventStruct TempEvent(x); + + for (uint8_t varNr = 0; varNr < valueCount; varNr++) { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) { + addHtml(F("espeasy_device_")); + addHtml(deviceName); + addHtml(F("{valueName=\"")); + addHtml(Cache.getTaskDeviceValueName(x, varNr)); + addHtml(F("\"} ")); + const String value(formatUserVarNoCheck(&TempEvent, varNr)); + + if (value.isEmpty()) { + addHtml('0'); // Return 0 for not-set values + } else { + addHtml(value); + } + addHtml('\n'); + } + } + } + } + } + } + } +} + + void handle_metrics() { TXBuffer.startStream(F("text/plain"), F("*")); const __FlashStringHelper *prefixHELP = F("# HELP espeasy_"); @@ -153,58 +208,5 @@ void handle_metrics() { TXBuffer.endStream(); } -void handle_metrics_devices() { - for (taskIndex_t x = 0; validTaskIndex(x); x++) { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); - const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); - - if (pluginID_set) { - if (Settings.TaskDeviceEnabled[x]) { - String deviceName = getTaskDeviceName(x); - - if (deviceName.isEmpty()) { // Empty name, then use taskN - deviceName = F("task"); - deviceName += x + 1; - } - addHtml(F("# HELP espeasy_device_")); - addHtml(deviceName); - addHtml(F(" Values from connected device\n")); - addHtml(F("# TYPE espeasy_device_")); - addHtml(deviceName); - addHtml(F(" gauge\n")); - - if (validDeviceIndex(DeviceIndex)) { - String customValuesString; - - // const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); - const bool customValues = 0; // TODO: handle custom values - - if (!customValues) { - const uint8_t valueCount = getValueCountForTask(x); - struct EventStruct TempEvent(x); - - for (uint8_t varNr = 0; varNr < valueCount; varNr++) { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) { - addHtml(F("espeasy_device_")); - addHtml(deviceName); - addHtml(F("{valueName=\"")); - addHtml(Cache.getTaskDeviceValueName(x, varNr)); - addHtml(F("\"} ")); - const String value(formatUserVarNoCheck(&TempEvent, varNr)); - - if (value.isEmpty()) { - addHtml('0'); // Return 0 for not-set values - } else { - addHtml(value); - } - addHtml('\n'); - } - } - } - } - } - } - } -} #endif // WEBSERVER_METRICS diff --git a/src/src/WebServer/Metrics.h b/src/src/WebServer/Metrics.h index b567f56c5..4951a9114 100644 --- a/src/src/WebServer/Metrics.h +++ b/src/src/WebServer/Metrics.h @@ -6,7 +6,6 @@ #ifdef WEBSERVER_METRICS void handle_metrics(); -void handle_metrics_devices(); #endif // ifdef WEBSERVER_METRICS diff --git a/src/src/WebServer/NetworkPage.cpp b/src/src/WebServer/NetworkPage.cpp index 2465abaab..189551d5a 100644 --- a/src/src/WebServer/NetworkPage.cpp +++ b/src/src/WebServer/NetworkPage.cpp @@ -29,116 +29,28 @@ using namespace ESPEasy::net; -void handle_networks() +// ******************************************************************************** +// Selected network has changed. +// Clear all Network settings and load some defaults +// ******************************************************************************** +static void handle_networks_clearLoadDefaults(ESPEasy::net::networkIndex_t networkindex, NetworkSettingsStruct& NetworkSettings) { -# ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_networks")); -# endif // ifndef BUILD_NO_RAM_TRACKER - - if (!startStream_send_stdTemplate(MENU_INDEX_NETWORK)) { return; } - - // 'index' value in the URL - uint8_t networkindex = getFormItemInt(F("index"), 0); - const bool networkIndexSet = networkindex != 0 && validNetworkIndex(networkindex - 1); - --networkindex; // Index in URL is starting from 1, but starting from 0 in the array. - - const int networkDriver_webarg_value = getFormItemInt(F("networkDriver"), -1); - - // submitted data - if ((networkDriver_webarg_value != -1) && networkIndexSet) - { - bool mustInit = false; - bool mustCallNWpluginSave = false; - - // TODO TD-er: Implement saving submitted settings - const nwpluginID_t nwpluginID = nwpluginID_t::toPluginID(networkDriver_webarg_value); - - auto NetworkSettings = MakeNetworkSettings(); - - if (Settings.getNWPluginID_for_network(networkindex) != nwpluginID) - { -# ifndef LIMIT_BUILD_SIZE - addLog(LOG_LEVEL_INFO, strformat( - F("HandleNW: Driver changed from %d to %d"), - Settings.getNWPluginID_for_network(networkindex).value, - nwpluginID.value)); -# endif // ifndef LIMIT_BUILD_SIZE - - // NetworkDriver has changed. - Settings.setNWPluginID_for_network(networkindex, nwpluginID); - - // there is a networkDriverIndex selected? - if (nwpluginID.isValid()) - { - mustInit = true; - - handle_networks_clearLoadDefaults(networkindex, *NetworkSettings); - } - } - - // subitted same networkDriverIndex - else - { - // there is a networkDriverIndex selected -# ifndef LIMIT_BUILD_SIZE - addLog(LOG_LEVEL_INFO, concat( - F("HandleNW: Driver selected: "), - nwpluginID.value) + (nwpluginID.isValid() ? F("valid") : F("invalid"))); -# endif // ifndef LIMIT_BUILD_SIZE - - if (nwpluginID.isValid()) - { - mustInit = true; - - handle_networks_CopySubmittedSettings(networkindex, *NetworkSettings); - mustCallNWpluginSave = true; - } - } - - if (mustCallNWpluginSave) { - // Call NWPLUGIN_WEBFORM_SAVE after destructing NetworkSettings object to reduce RAM usage. - // Network plugin almost only deals with custom network settings. - // Even if they need to save things to the NetworkSettings, then the changes must - // already be saved first as the NWPluginCall does not have the NetworkSettings as argument. - handle_networks_CopySubmittedSettings_NWPluginCall(networkindex); - } - addHtmlError(SaveSettings()); - - if (mustInit) { - // Init network plugin using the new settings. - NWPlugin_Exit_Init(networkindex); - } - - } - - if (networkIndexSet) - { - handle_networks_NetworkSettingsPage(networkindex); - } - else - { - handle_networks_ShowAllNetworksTable(); - } - - sendTail_stdtemplate(); -} - -void handle_networks_clearLoadDefaults(ESPEasy::net::networkIndex_t networkindex, NetworkSettingsStruct& NetworkSettings) { - // TODO TD-er: Must also check NetworkDriverStruct to see if something else must be done } -void handle_networks_CopySubmittedSettings(ESPEasy::net::networkIndex_t networkindex, NetworkSettingsStruct& NetworkSettings) +// ******************************************************************************** +// Collect all submitted form data and store in the NetworkSettings +// ******************************************************************************** +static void handle_networks_CopySubmittedSettings(ESPEasy::net::networkIndex_t networkindex, NetworkSettingsStruct& NetworkSettings) { // copy all settings to network settings struct for (int parameterIdx = 0; parameterIdx <= NetworkSettingsStruct::NETWORK_ENABLED; ++parameterIdx) { NetworkSettingsStruct::VarType varType = static_cast(parameterIdx); saveNetworkParameterForm(NetworkSettings, networkindex, varType); } - } -void handle_networks_CopySubmittedSettings_NWPluginCall(ESPEasy::net::networkIndex_t networkindex) { +static void handle_networks_CopySubmittedSettings_NWPluginCall(ESPEasy::net::networkIndex_t networkindex) { networkDriverIndex_t NetworkDriverIndex = getNetworkDriverIndex_from_NetworkIndex(networkindex); if (validNetworkDriverIndex(NetworkDriverIndex)) { @@ -176,7 +88,10 @@ void handle_networks_CopySubmittedSettings_NWPluginCall(ESPEasy::net::networkInd } -void handle_networks_ShowAllNetworksTable() +// ******************************************************************************** +// Show table with all selected networks +// ******************************************************************************** +static void handle_networks_ShowAllNetworksTable() { html_add_form(); html_table_class_multirow(); @@ -312,7 +227,10 @@ void handle_networks_ShowAllNetworksTable() html_end_form(); } -void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkindex) +// ******************************************************************************** +// Show the network settings page +// ******************************************************************************** +static void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkindex) { if (!validNetworkIndex(networkindex)) { return; } @@ -402,9 +320,9 @@ void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkind # ifdef ESP32 addFormNote(F( "For fallback interface, it is the delay after connection of primary interface has failed. For non-fallback it is the delay from boot")); - addFormCheckBox(F("Append Name to Hostname"), F("append_hostname"), Settings.getAppendNetworkAdapterNameToHostname(networkindex)); + addFormCheckBox(F("Append Name to Hostname"), F("append_hostname"), Settings.getAppendNetworkAdapterNameToHostname(networkindex)); # endif // ifdef ESP32 - addFormCheckBox(F("Block Web Access"), F("block_web_access"), Settings.getNetworkInterfaceSubnetBlockClientIP(networkindex)); + addFormCheckBox(F("Block Web Access"), F("block_web_access"), Settings.getNetworkInterfaceSubnetBlockClientIP(networkindex)); addFormNote(F("When checked, any host from a subnet on this network interface will be blocked to access the ESPEasy web interface")); # if FEATURE_USE_IPV6 @@ -495,4 +413,98 @@ void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkind } +void handle_networks() +{ +# ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_networks")); +# endif // ifndef BUILD_NO_RAM_TRACKER + + if (!startStream_send_stdTemplate(MENU_INDEX_NETWORK)) { return; } + + // 'index' value in the URL + uint8_t networkindex = getFormItemInt(F("index"), 0); + const bool networkIndexSet = networkindex != 0 && validNetworkIndex(networkindex - 1); + --networkindex; // Index in URL is starting from 1, but starting from 0 in the array. + + const int networkDriver_webarg_value = getFormItemInt(F("networkDriver"), -1); + + // submitted data + if ((networkDriver_webarg_value != -1) && networkIndexSet) + { + bool mustInit = false; + bool mustCallNWpluginSave = false; + + // TODO TD-er: Implement saving submitted settings + const nwpluginID_t nwpluginID = nwpluginID_t::toPluginID(networkDriver_webarg_value); + + auto NetworkSettings = MakeNetworkSettings(); + + if (Settings.getNWPluginID_for_network(networkindex) != nwpluginID) + { +# ifndef LIMIT_BUILD_SIZE + addLog(LOG_LEVEL_INFO, strformat( + F("HandleNW: Driver changed from %d to %d"), + Settings.getNWPluginID_for_network(networkindex).value, + nwpluginID.value)); +# endif // ifndef LIMIT_BUILD_SIZE + + // NetworkDriver has changed. + Settings.setNWPluginID_for_network(networkindex, nwpluginID); + + // there is a networkDriverIndex selected? + if (nwpluginID.isValid()) + { + mustInit = true; + + handle_networks_clearLoadDefaults(networkindex, *NetworkSettings); + } + } + + // subitted same networkDriverIndex + else + { + // there is a networkDriverIndex selected +# ifndef LIMIT_BUILD_SIZE + addLog(LOG_LEVEL_INFO, concat( + F("HandleNW: Driver selected: "), + nwpluginID.value) + (nwpluginID.isValid() ? F("valid") : F("invalid"))); +# endif // ifndef LIMIT_BUILD_SIZE + + if (nwpluginID.isValid()) + { + mustInit = true; + + handle_networks_CopySubmittedSettings(networkindex, *NetworkSettings); + mustCallNWpluginSave = true; + } + } + + if (mustCallNWpluginSave) { + // Call NWPLUGIN_WEBFORM_SAVE after destructing NetworkSettings object to reduce RAM usage. + // Network plugin almost only deals with custom network settings. + // Even if they need to save things to the NetworkSettings, then the changes must + // already be saved first as the NWPluginCall does not have the NetworkSettings as argument. + handle_networks_CopySubmittedSettings_NWPluginCall(networkindex); + } + addHtmlError(SaveSettings()); + + if (mustInit) { + // Init network plugin using the new settings. + NWPlugin_Exit_Init(networkindex); + } + + } + + if (networkIndexSet) + { + handle_networks_NetworkSettingsPage(networkindex); + } + else + { + handle_networks_ShowAllNetworksTable(); + } + + sendTail_stdtemplate(); +} + #endif // ifdef WEBSERVER_NETWORK diff --git a/src/src/WebServer/NetworkPage.h b/src/src/WebServer/NetworkPage.h index fd1533c40..c5a37f554 100644 --- a/src/src/WebServer/NetworkPage.h +++ b/src/src/WebServer/NetworkPage.h @@ -1,5 +1,4 @@ -#ifndef WEBSERVER_WEBSERVER_NETWORKPAGE_H -#define WEBSERVER_WEBSERVER_NETWORKPAGE_H +#pragma once #include "../WebServer/common.h" @@ -8,35 +7,9 @@ #include "../../ESPEasy/net/DataTypes/NetworkIndex.h" #include "../../ESPEasy/net/DataStructs/NetworkSettingsStruct.h" - // ******************************************************************************** // Web Interface Network page // ******************************************************************************** void handle_networks(); -// ******************************************************************************** -// Selected network has changed. -// Clear all Network settings and load some defaults -// ******************************************************************************** -void handle_networks_clearLoadDefaults(ESPEasy::net::networkIndex_t networkindex, ESPEasy::net::NetworkSettingsStruct& NetworkSettings); - -// ******************************************************************************** -// Collect all submitted form data and store in the NetworkSettings -// ******************************************************************************** -void handle_networks_CopySubmittedSettings(ESPEasy::net::networkIndex_t networkindex, ESPEasy::net::NetworkSettingsStruct& NetworkSettings); - -void handle_networks_CopySubmittedSettings_NWPluginCall(ESPEasy::net::networkIndex_t networkindex); - -// ******************************************************************************** -// Show table with all selected networks -// ******************************************************************************** -void handle_networks_ShowAllNetworksTable(); - -// ******************************************************************************** -// Show the network settings page -// ******************************************************************************** -void handle_networks_NetworkSettingsPage(ESPEasy::net::networkIndex_t networkindex); - - #endif // ifdef WEBSERVER_NETWORK -#endif // ifndef WEBSERVER_WEBSERVER_NETWORKPAGE_H diff --git a/src/src/WebServer/Rules.cpp b/src/src/WebServer/Rules.cpp index 97f18b1a3..f51869bc4 100644 --- a/src/src/WebServer/Rules.cpp +++ b/src/src/WebServer/Rules.cpp @@ -20,6 +20,25 @@ #include +static void Rule_showRuleTextArea(const String& fileName) { + // Read rules from file and stream directly into the textarea + + size_t size = 0; + + addHtml(F("")); + #if FEATURE_RULES_EASY_COLOR_CODE + html_add_script(F("initCM();"), false); + #endif + + html_TR_TD(); + addHtml(F("Current size: ")); + addHtmlInt(size); + addHtml(F(" characters")); +} + + // ******************************************************************************** // Web Interface rules page // ******************************************************************************** @@ -109,6 +128,71 @@ void handle_rules() { checkRuleSets(); } +# ifdef WEBSERVER_NEW_RULES +static bool EnumerateFileAndDirectory(String & rootPath + , int skip + , HandlerFileInfo handler) +{ + bool hasMore = false; + int count = 0; + bool next = true; + + # ifdef ESP8266 + fs::Dir dir = ESPEASY_FS.openDir(rootPath); + ESPEASY_SERIAL_0.print(F("Enumerate files of ")); + ESPEASY_SERIAL_0.println(rootPath); + + while (next && dir.next()) { + // Skip files + if (count++ < skip) { + continue; + } + + // Workaround for skipped unwanted files + if (!dir.fileName().startsWith(rootPath + "/")) { + continue; + } + fileInfo fi; + fi.Name = dir.fileName() /*.substring(rootPath.length())*/; + fs::File f = dir.openFile("r"); + fi.Size = f.size(); + f.close(); + next = handler(fi); + } + hasMore = dir.next(); + # endif // ifdef ESP8266 + # ifdef ESP32 + fs::File root = ESPEASY_FS.open(rootPath); + + if (root) + { + fs::File file = root.openNextFile(); + + while (next && file) { + if (count >= skip) { + fileInfo fi; + fi.Name = file.name(); + fi.Size = file.size(); + fi.isDirectory = file.isDirectory(); + next = handler(fi); + } + + if (!file.isDirectory()) { + ++count; + } + file = root.openNextFile(); + } + hasMore = file; + } + else + { + addLog(LOG_LEVEL_ERROR, F("Invalid root.")); + } + # endif // ifdef ESP32 + return hasMore; +} +#endif + // ******************************************************************************** // Web Interface rules page (NEW) // ******************************************************************************** @@ -258,6 +342,33 @@ void handle_rules_new() { # endif // WEBSERVER_NEW_RULES } +# ifdef WEBSERVER_NEW_RULES +static bool Rule_Download(const String& path) +{ + # ifdef WEBSERVER_RULES_DEBUG + ESPEASY_SERIAL_0.print(F("Rule_Download path: ")); + ESPEASY_SERIAL_0.println(path); + # endif // ifdef WEBSERVER_RULES_DEBUG + fs::File dataFile = tryOpenFile(path, "r"); + + if (!dataFile) + { + addLog(LOG_LEVEL_ERROR, concat(F("Invalid path: "), path)); + return false; + } + String filename = concat(path, F(".txt")); + filename.replace(RULE_FILE_SEPARAROR, '_'); + String str = concat(F("attachment; filename="), filename); + sendHeader(F("Content-Disposition"), str); + sendHeader(F("Cache-Control"), F("public,max-age=3600")); + sendHeader(F("Vary"), "*"); + sendHeader(F("ETag"), F("\"2.0.0\"")); + + web_server.streamFile(dataFile, F("application/octet-stream")); + dataFile.close(); + return true; +} + void handle_rules_backup() { if (Settings.OldRulesEngine()) { @@ -368,13 +479,9 @@ void handle_rules_delete() { # endif // ifndef BUILD_NO_RAM_TRACKER # endif // WEBSERVER_NEW_RULES } +#endif -bool handle_rules_edit(const String& originalUri) -{ - return handle_rules_edit(originalUri, false); -} - -bool handle_rules_edit(String originalUri, bool isAddNew) { +static bool handle_rules_edit(String originalUri, bool isAddNew) { // originalUri is passed via deepcopy, since it will be converted to lower case. originalUri.toLowerCase(); # ifndef BUILD_NO_RAM_TRACKER @@ -532,48 +639,9 @@ bool handle_rules_edit(String originalUri, bool isAddNew) { return handle; } -void Rule_showRuleTextArea(const String& fileName) { - // Read rules from file and stream directly into the textarea - - size_t size = 0; - - addHtml(F("")); - #if FEATURE_RULES_EASY_COLOR_CODE - html_add_script(F("initCM();"), false); - #endif - - html_TR_TD(); - addHtml(F("Current size: ")); - addHtmlInt(size); - addHtml(F(" characters")); -} - -bool Rule_Download(const String& path) +bool handle_rules_edit(const String& originalUri) { - # ifdef WEBSERVER_RULES_DEBUG - ESPEASY_SERIAL_0.print(F("Rule_Download path: ")); - ESPEASY_SERIAL_0.println(path); - # endif // ifdef WEBSERVER_RULES_DEBUG - fs::File dataFile = tryOpenFile(path, "r"); - - if (!dataFile) - { - addLog(LOG_LEVEL_ERROR, concat(F("Invalid path: "), path)); - return false; - } - String filename = concat(path, F(".txt")); - filename.replace(RULE_FILE_SEPARAROR, '_'); - String str = concat(F("attachment; filename="), filename); - sendHeader(F("Content-Disposition"), str); - sendHeader(F("Cache-Control"), F("public,max-age=3600")); - sendHeader(F("Vary"), "*"); - sendHeader(F("ETag"), F("\"2.0.0\"")); - - web_server.streamFile(dataFile, F("application/octet-stream")); - dataFile.close(); - return true; + return handle_rules_edit(originalUri, false); } void Goto_Rules_Root() { @@ -581,67 +649,6 @@ void Goto_Rules_Root() { web_server.send(302, F("text/plain"), EMPTY_STRING); } -bool EnumerateFileAndDirectory(String & rootPath - , int skip - , HandlerFileInfo handler) -{ - bool hasMore = false; - int count = 0; - bool next = true; - # ifdef ESP8266 - fs::Dir dir = ESPEASY_FS.openDir(rootPath); - ESPEASY_SERIAL_0.print(F("Enumerate files of ")); - ESPEASY_SERIAL_0.println(rootPath); - - while (next && dir.next()) { - // Skip files - if (count++ < skip) { - continue; - } - - // Workaround for skipped unwanted files - if (!dir.fileName().startsWith(rootPath + "/")) { - continue; - } - fileInfo fi; - fi.Name = dir.fileName() /*.substring(rootPath.length())*/; - fs::File f = dir.openFile("r"); - fi.Size = f.size(); - f.close(); - next = handler(fi); - } - hasMore = dir.next(); - # endif // ifdef ESP8266 - # ifdef ESP32 - fs::File root = ESPEASY_FS.open(rootPath); - - if (root) - { - fs::File file = root.openNextFile(); - - while (next && file) { - if (count >= skip) { - fileInfo fi; - fi.Name = file.name(); - fi.Size = file.size(); - fi.isDirectory = file.isDirectory(); - next = handler(fi); - } - - if (!file.isDirectory()) { - ++count; - } - file = root.openNextFile(); - } - hasMore = file; - } - else - { - addLog(LOG_LEVEL_ERROR, F("Invalid root.")); - } - # endif // ifdef ESP32 - return hasMore; -} #endif // ifdef WEBSERVER_RULES diff --git a/src/src/WebServer/Rules.h b/src/src/WebServer/Rules.h index 10d84e763..19bb61455 100644 --- a/src/src/WebServer/Rules.h +++ b/src/src/WebServer/Rules.h @@ -26,24 +26,15 @@ void handle_rules(); // ******************************************************************************** void handle_rules_new(); +# ifdef WEBSERVER_NEW_RULES void handle_rules_backup(); - void handle_rules_delete(); +#endif bool handle_rules_edit(const String& originalUri); -bool handle_rules_edit(String originalUri, bool isAddNew); - -void Rule_showRuleTextArea(const String& fileName); - -bool Rule_Download(const String& path); - void Goto_Rules_Root(); -bool EnumerateFileAndDirectory(String & rootPath - , int skip - , HandlerFileInfo handler); - #endif From 55828c88516ceba55533d80050ca7b390ef22ba6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 21:02:52 +0000 Subject: [PATCH 04/12] [NodeType] Return empty string again for unknown node types After splitting toNodeTypeDisplayString(), every node type which is not ESP Easy, ESP Easy Mega, RPI Easy, Arduino Easy or Nano Easy was shown as "ESP Easy 32". For example nodeType 0, the default of NodeStruct::nodeType. Before, an empty string was returned for unknown node types. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018S3oPMc7qQGQGGGyMUvcaf --- src/src/DataTypes/NodeTypeID.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/src/DataTypes/NodeTypeID.cpp b/src/src/DataTypes/NodeTypeID.cpp index a4bdd32dd..5804a21db 100644 --- a/src/src/DataTypes/NodeTypeID.cpp +++ b/src/src/DataTypes/NodeTypeID.cpp @@ -20,6 +20,7 @@ static const __FlashStringHelper* toNodeType_ESP32_suffix(uint8_t nodeType) { const __FlashStringHelper* suffix = F(""); switch (nodeType) { + case NODE_TYPE_ID_ESP_EASY32_STD: break; case NODE_TYPE_ID_ESP_EASY32S2_STD: suffix = F("-S2"); break; case NODE_TYPE_ID_ESP_EASY32S3_STD: suffix = F("-S3"); break; case NODE_TYPE_ID_ESP_EASY32S31_STD: suffix = F("-S31"); break; @@ -35,7 +36,7 @@ static const __FlashStringHelper* toNodeType_ESP32_suffix(uint8_t nodeType) { case NODE_TYPE_ID_ESP_EASY32P4r3_STD: suffix = F("-P4r3"); break; default: - break; + return nullptr; // Not an ESP32 node type } return suffix; } @@ -45,5 +46,6 @@ String toNodeTypeDisplayString(uint8_t nodeType) { if (!res.isEmpty()) return res; const __FlashStringHelper* suffix = toNodeType_ESP32_suffix(nodeType); + if (suffix == nullptr) return EMPTY_STRING; return concat(F("ESP Easy 32"), suffix); } From da96905f27a3417925b3228d60b648994ce108dd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 21:27:25 +0000 Subject: [PATCH 05/12] [Dallas] Keep 1-Wire ISR functions in IRAM on ESP32 Dallas_read_bit_ISR() and Dallas_write_bit_ISR() have only a single caller, Dallas_read_bit() / Dallas_write_bit(), which is not in IRAM. The ESP32 builds use LTO (-flto=auto, set by the build flags of the arduino-esp32 framework package), so GCC inlines these functions into their caller and the timing critical code with interrupts disabled runs from flash. DALLAS_IRAM_ATTR thus had no effect for these functions. Without LTO the same happens as soon as these functions are static (-finline-functions-called-once is enabled at -Os). Add noinline to DALLAS_IRAM_ATTR on ESP32. ESP8266 is not affected, as DALLAS_IRAM_ATTR is empty there. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018S3oPMc7qQGQGGGyMUvcaf --- src/src/Helpers/Dallas1WireHelper.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/src/Helpers/Dallas1WireHelper.cpp b/src/src/Helpers/Dallas1WireHelper.cpp index 25bc457db..15864d1bb 100644 --- a/src/src/Helpers/Dallas1WireHelper.cpp +++ b/src/src/Helpers/Dallas1WireHelper.cpp @@ -30,7 +30,11 @@ # define DALLAS_IRAM_ATTR # endif // ifdef ESP8266 # ifdef ESP32 -# define DALLAS_IRAM_ATTR IRAM_ATTR + +// noinline: GCC inlines a function with a single caller into that caller. +// (e.g. Dallas_read_bit_ISR, as LTO is enabled for ESP32 builds) +// The timing critical code would then run from flash, as the caller is not in IRAM. +# define DALLAS_IRAM_ATTR IRAM_ATTR __attribute__((noinline)) # endif // ifdef ESP32 From ff9a7f615020b4bbd7324b00b053a7b4645905ff Mon Sep 17 00:00:00 2001 From: TD-er Date: Sun, 18 Oct 2026 00:53:03 +0200 Subject: [PATCH 06/12] [Build] Reduce build size by using static functions (src/WebServer) 2nd --- .../SecurityStruct_deviceSpecific_webform.cpp | 4 +- src/src/WebServer/SettingsArchive.cpp | 144 +-- src/src/WebServer/SettingsArchive.h | 29 +- src/src/WebServer/SetupPage.cpp | 365 +++---- src/src/WebServer/SetupPage.h | 15 +- src/src/WebServer/SysInfoPage.cpp | 910 +++++++++--------- src/src/WebServer/SysInfoPage.h | 32 +- src/src/WebServer/SysVarPage.cpp | 96 +- src/src/WebServer/SysVarPage.h | 5 - src/src/WebServer/TimingStats.cpp | 94 +- src/src/WebServer/TimingStats.h | 9 - src/src/WebServer/ToolsPage.cpp | 97 +- src/src/WebServer/ToolsPage.h | 18 +- src/src/WebServer/UploadPage.cpp | 37 +- src/src/WebServer/UploadPage.h | 8 +- src/src/WebServer/WebTemplateParser.cpp | 6 +- src/src/WebServer/WebTemplateParser.h | 1 - 17 files changed, 906 insertions(+), 964 deletions(-) diff --git a/src/src/WebServer/SecurityStruct_deviceSpecific_webform.cpp b/src/src/WebServer/SecurityStruct_deviceSpecific_webform.cpp index ab74b53b2..f450e53f0 100644 --- a/src/src/WebServer/SecurityStruct_deviceSpecific_webform.cpp +++ b/src/src/WebServer/SecurityStruct_deviceSpecific_webform.cpp @@ -6,7 +6,7 @@ # include "../Helpers/StringConverter.h" # include "../WebServer/Markup_Forms.h" -String DevSpecific_Security_getLabelString(SecurityStruct_deviceSpecific::KeyType keytype, +static String DevSpecific_Security_getLabelString(SecurityStruct_deviceSpecific::KeyType keytype, uint16_t index, bool displayString, KVS_StorageType::Enum & storageType, @@ -35,7 +35,7 @@ String DevSpecific_Security_getLabelString(SecurityStruct_deviceSpecific::KeyTyp return res; } -WebFormItemParams make_DevSpecific_Security_WebFormItemParams( +static WebFormItemParams make_DevSpecific_Security_WebFormItemParams( SecurityStruct_deviceSpecific::KeyType keytype, uint16_t index, bool suppressIndex_displayString) diff --git a/src/src/WebServer/SettingsArchive.cpp b/src/src/WebServer/SettingsArchive.cpp index c37214384..e49a9199e 100644 --- a/src/src/WebServer/SettingsArchive.cpp +++ b/src/src/WebServer/SettingsArchive.cpp @@ -15,6 +15,79 @@ # include "../Helpers/Misc.h" # include "../Helpers/StringConverter.h" +// ******************************************************************************** +// download filetype selectors +// ******************************************************************************** +static void addDownloadFiletypeCheckbox(FileType::Enum filetype, unsigned int filenr) { + const String fileName = getFileName(filetype, filenr); + + addRowLabel(fileName); + addCheckBox(fileName, getDownloadFiletypeChecked(filetype, filenr)); +} + +static void storeDownloadFiletypeCheckbox(FileType::Enum filetype, unsigned int filenr) { + const bool isChecked = isFormItemChecked(getFileName(filetype, filenr)); + + switch (filetype) + { + case FileType::CONFIG_DAT: ResetFactoryDefaultPreference.fetchConfigDat(isChecked); + break; + case FileType::SECURITY_DAT: ResetFactoryDefaultPreference.fetchSecurityDat(isChecked); + break; + case FileType::NOTIFICATION_DAT: ResetFactoryDefaultPreference.fetchNotificationDat(isChecked); + break; + case FileType::RULES_TXT: + { ResetFactoryDefaultPreference.fetchRulesTXT(filenr, isChecked); + break; + } + case FileType::PROVISIONING_DAT: + { ResetFactoryDefaultPreference.fetchProvisioningDat(isChecked); + break; + } +# if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + case FileType::DEV_SECURITY_DAT: + { ResetFactoryDefaultPreference.fetchDeviceSecurityDat(isChecked); + break; + } +# endif // if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE + + case FileType::MAX_FILETYPE: + break; + } +} + +# if FEATURE_CUSTOM_PROVISIONING + +static void addAllowFiletypeCheckbox(const ProvisioningStruct& ProvisioningSettings, FileType::Enum filetype, unsigned int filenr) +{ + const String fileName = getFileName(filetype, filenr); + + addRowLabel(fileName); + addCheckBox(concat(F("allow_"), fileName), ProvisioningSettings.fetchFileTypeAllowed(filetype, filenr)); +} + +static void storeAllowFiletypeCheckbox(ProvisioningStruct& ProvisioningSettings, FileType::Enum filetype, unsigned int filenr) +{ + const bool isChecked = isFormItemChecked(concat(F("allow_"), getFileName(filetype, filenr))); + + ProvisioningSettings.setFetchFileTypeAllowed(filetype, filenr, isChecked); +} + +# endif // if FEATURE_CUSTOM_PROVISIONING + +static bool tryDownloadFileType(const String& url, const String& user, const String& pass, FileType::Enum filetype, unsigned int filenr) { + const String filename = getFileName(filetype, filenr); + + addRowLabel(filename); + const String error = downloadFileType(url, user, pass, filetype, filenr); + + if (error.length() == 0) { + addHtml(F("Success")); + return true; + } + addHtml(error); + return false; +} // ******************************************************************************** // Web Interface to manage archived settings @@ -41,7 +114,7 @@ void handle_settingsarchive() { storeDownloadFiletypeCheckbox(FileType::RULES_TXT, i); } } else { - storeDownloadFiletypeCheckbox(ft); + storeDownloadFiletypeCheckbox(ft, 0); } } @@ -62,6 +135,7 @@ void handle_settingsarchive() { ProvisioningSettings->ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); ProvisioningSettings->allowedFlags.allowFetchFirmware = isFormItemChecked(F("firmware")); + for (int i = 0; i < FileType::MAX_FILETYPE; ++i) { const FileType::Enum ft = static_cast(i); @@ -70,7 +144,7 @@ void handle_settingsarchive() { storeAllowFiletypeCheckbox(*ProvisioningSettings, FileType::RULES_TXT, i); } } else { - storeAllowFiletypeCheckbox(*ProvisioningSettings, ft); + storeAllowFiletypeCheckbox(*ProvisioningSettings, ft, 0); } } @@ -109,7 +183,7 @@ void handle_settingsarchive() { if (ft != FileType::RULES_TXT) { if (getDownloadFiletypeChecked(ft, 0)) { - if (tryDownloadFileType(url, user, pass, ft)) { + if (tryDownloadFileType(url, user, pass, ft, 0)) { somethingDownloaded = true; } } @@ -174,6 +248,7 @@ void handle_settingsarchive() { addFormTextBox(F("User"), F("user"), user, 64); addFormPasswordBox(F("Pass"), F("pass"), pass, 64); # if FEATURE_CUSTOM_PROVISIONING + if (ProvisioningSettings.get()) { addFormCheckBox(F("Store Credentials"), F("savecred"), ResetFactoryDefaultPreference.storeCredentials()); @@ -184,7 +259,7 @@ void handle_settingsarchive() { const FileType::Enum ft = static_cast(i); if (ft != FileType::RULES_TXT) { - addAllowFiletypeCheckbox(*ProvisioningSettings, ft); + addAllowFiletypeCheckbox(*ProvisioningSettings, ft, 0); } } @@ -212,7 +287,7 @@ void handle_settingsarchive() { const FileType::Enum ft = static_cast(i); if (ft != FileType::RULES_TXT) { - addDownloadFiletypeCheckbox(ft); + addDownloadFiletypeCheckbox(ft, 0); } } @@ -235,63 +310,4 @@ void handle_settingsarchive() { sendTail_stdtemplate(); } -// ******************************************************************************** -// download filetype selectors -// ******************************************************************************** -void addDownloadFiletypeCheckbox(FileType::Enum filetype, unsigned int filenr) { - const String fileName = getFileName(filetype, filenr); - addRowLabel(fileName); - addCheckBox(fileName, getDownloadFiletypeChecked(filetype, filenr)); -} - -void storeDownloadFiletypeCheckbox(FileType::Enum filetype, unsigned int filenr) { - const bool isChecked = isFormItemChecked(getFileName(filetype, filenr)); - - switch (filetype) { - case FileType::CONFIG_DAT: ResetFactoryDefaultPreference.fetchConfigDat(isChecked); break; - case FileType::SECURITY_DAT: ResetFactoryDefaultPreference.fetchSecurityDat(isChecked); break; - case FileType::NOTIFICATION_DAT: ResetFactoryDefaultPreference.fetchNotificationDat(isChecked); break; - case FileType::RULES_TXT: { ResetFactoryDefaultPreference.fetchRulesTXT(filenr, isChecked); break; } - case FileType::PROVISIONING_DAT: { ResetFactoryDefaultPreference.fetchProvisioningDat(isChecked); break; } -#if FEATURE_STORE_CREDENTIALS_SEPARATE_FILE - case FileType::DEV_SECURITY_DAT: { ResetFactoryDefaultPreference.fetchDeviceSecurityDat(isChecked); break; } -#endif - - case FileType::MAX_FILETYPE: - break; - } -} - -# if FEATURE_CUSTOM_PROVISIONING -void addAllowFiletypeCheckbox(const ProvisioningStruct& ProvisioningSettings, FileType::Enum filetype, unsigned int filenr) -{ - const String fileName = getFileName(filetype, filenr); - addRowLabel(fileName); - addCheckBox(concat(F("allow_"), fileName), ProvisioningSettings.fetchFileTypeAllowed(filetype, filenr)); -} - -void storeAllowFiletypeCheckbox(ProvisioningStruct& ProvisioningSettings, FileType::Enum filetype, unsigned int filenr) -{ - const bool isChecked = isFormItemChecked(concat(F("allow_"), getFileName(filetype, filenr))); - - ProvisioningSettings.setFetchFileTypeAllowed(filetype, filenr, isChecked); -} - -# endif // if FEATURE_CUSTOM_PROVISIONING - - -bool tryDownloadFileType(const String& url, const String& user, const String& pass, FileType::Enum filetype, unsigned int filenr) { - const String filename = getFileName(filetype, filenr); - - addRowLabel(filename); - const String error = downloadFileType(url, user, pass, filetype, filenr); - - if (error.length() == 0) { - addHtml(F("Success")); - return true; - } - addHtml(error); - return false; -} - #endif // if FEATURE_SETTINGS_ARCHIVE diff --git a/src/src/WebServer/SettingsArchive.h b/src/src/WebServer/SettingsArchive.h index 2fb5d922e..bb0a1b9ff 100644 --- a/src/src/WebServer/SettingsArchive.h +++ b/src/src/WebServer/SettingsArchive.h @@ -1,16 +1,14 @@ -#ifndef WEBSERVER_WEBSERVER_SETTINGSARCHIVE_H -#define WEBSERVER_WEBSERVER_SETTINGSARCHIVE_H +#pragma once #include "../WebServer/common.h" #if FEATURE_SETTINGS_ARCHIVE -#include "../DataTypes/ESPEasyFileType.h" - -#if FEATURE_CUSTOM_PROVISIONING -#include "../DataStructs/ProvisioningStruct.h" -#endif +# include "../DataTypes/ESPEasyFileType.h" +# if FEATURE_CUSTOM_PROVISIONING +# include "../DataStructs/ProvisioningStruct.h" +# endif // ******************************************************************************** @@ -18,21 +16,4 @@ // ******************************************************************************** void handle_settingsarchive(); -// ******************************************************************************** -// download filetype selectors -// ******************************************************************************** -void addDownloadFiletypeCheckbox(FileType::Enum filetype, unsigned int filenr = 0); - -void storeDownloadFiletypeCheckbox(FileType::Enum filetype, unsigned int filenr = 0); - -# if FEATURE_CUSTOM_PROVISIONING -void addAllowFiletypeCheckbox(const ProvisioningStruct& ProvisioningSettings, FileType::Enum filetype, unsigned int filenr = 0); - -void storeAllowFiletypeCheckbox(ProvisioningStruct& ProvisioningSettings, FileType::Enum filetype, unsigned int filenr = 0); -# endif - -bool tryDownloadFileType(const String& url, const String& user, const String& pass, FileType::Enum filetype, unsigned int filenr = 0); - #endif // if FEATURE_SETTINGS_ARCHIVE - -#endif \ No newline at end of file diff --git a/src/src/WebServer/SetupPage.cpp b/src/src/WebServer/SetupPage.cpp index a4a1b1bbb..5491aeb9d 100644 --- a/src/src/WebServer/SetupPage.cpp +++ b/src/src/WebServer/SetupPage.cpp @@ -31,6 +31,190 @@ # endif + +static void handle_setup_scan_and_show(const String& ssid, const String& other, const String& password) { + int8_t scanCompleteStatus = ESPEasy::net::wifi::WiFi_AP_Candidates.scanComplete(); + const bool needsRescan = + (scanCompleteStatus == 0 || // No AP found + scanCompleteStatus == -2) // Scan not triggered + && ESPEasy::net::wifi::WiFiScanAllowed(); + + if (needsRescan) { + WiFiMode_t cur_wifimode = WiFi.getMode(); + ESPEasy::net::wifi::WifiScan(false); + scanCompleteStatus = ESPEasy::net::wifi::WiFi_AP_Candidates.scanComplete(); + ESPEasy::net::wifi::setWifiMode(cur_wifimode); + } + + + if (scanCompleteStatus <= 0) { + addHtml(F("No Access Points found")); + } + else + { + html_table_class_multirow(); + html_TR(); + html_table_header(F("Pick"), 50); + html_table_header(F("Network info")); + html_table_header(F("RSSI"), 50); + + for (auto it = ESPEasy::net::wifi::WiFi_AP_Candidates.scanned_begin(); it != ESPEasy::net::wifi::WiFi_AP_Candidates.scanned_end(); ++it) + { + html_TR_TD(); + const String id = it->toString(""); + addHtml(F("")); + + html_TD(); + addHtml(F("")); + + html_TD(); + addHtml(F("")); + } + html_end_table(); + } + + html_BR(); + + addSubmitButton(F("Rescan"), F("performrescan")); + + html_BR(); + + html_table_class_normal(); + html_TR_TD(); + + addHtml(F("")); + + html_TD(); + + addHtml(F("")); + + + html_TR(); + + html_BR(); + html_BR(); + + addFormSeparator(2); + + html_BR(); + + addFormPasswordBox(F("Password"), F("pass"), password, 63); + addFormCheckBox(F("Allow Empty Password"), F("emptypass"), false); + + /* + if (SecuritySettings.hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first)) { + addFormCheckBox(F("Clear Stored SSID1"), F("clearssid1"), false); + addFormNote(String(F("Current: ")) + getValue(LabelType::WIFI_STORED_SSID1)); + } + if (SecuritySettings.hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second)) { + addFormCheckBox(F("Clear Stored SSID2"), F("clearssid2"), false); + addFormNote(String(F("Current: ")) + getValue(LabelType::WIFI_STORED_SSID2)); + } + */ + + html_TR_TD(); + html_TD(); + html_BR(); + addSubmitButton(F("Connect"), EMPTY_STRING); + + html_end_table(); +} + +static bool handle_setup_connectingStage(uint8_t refreshCount) { + if (refreshCount > 0) + { + // safe_strncpy(SecuritySettings.WifiSSID, "ssid", sizeof(SecuritySettings.WifiSSID)); + // SecuritySettings.WifiKey[0] = 0; + addButton(F("/setup"), F("Back to Setup")); + html_TR_TD(); + html_BR(); + + // WiFiEventData.wifiSetupConnect = false; + return false; + } + int wait = WIFI_RECONNECT_WAIT / 1000; + + if (refreshCount != 0) { + wait = 3; + } + addHtml(F("Please wait for

20..

" + "