From 4e191fc5b5571b2dc554057e770018d87792fa63 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 5 Aug 2026 15:46:52 +0200 Subject: [PATCH] [Rules] Exclude new features for ESP8266 by default for build-size reasons --- src/_P037_MQTTImport.ino | 33 +++++--- src/src/CustomBuild/define_plugin_sets.h | 19 ++++- src/src/ESPEasyCore/ESPEasyRules.cpp | 93 +++++++++++++++++---- src/src/Helpers/HTTPResponseParser.cpp | 102 +++++++++++++++++++++++ src/src/Helpers/JSON_helper.h | 2 +- src/src/Helpers/StringParser.cpp | 12 ++- src/src/PluginStructs/P037_data_struct.h | 9 +- 7 files changed, 235 insertions(+), 35 deletions(-) diff --git a/src/_P037_MQTTImport.ino b/src/_P037_MQTTImport.ino index d45ddf4c9..f381df58d 100644 --- a/src/_P037_MQTTImport.ino +++ b/src/_P037_MQTTImport.ino @@ -557,7 +557,18 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) if (!jsonAttribute.isEmpty()) { key = jsonAttribute; - Payload = getJsonValue(P037_data->root, key, false); + # if FEATURE_JSON_PARSE + Payload = getJsonValue(P037_data->root, key, false); + # else + + if (key.indexOf('.') > -1) { + String part1 = parseStringKeepCase(key, 1, '.'); + String part2 = parseStringKeepCase(key, 2, '.'); + Payload = P037_data->doc[part1][part2].as(); + } else { + Payload = P037_data->doc[key].as(); + } + # endif // if FEATURE_JSON_PARSE unparsedPayload = Payload; int8_t jIndex = jsonIndex.toInt(); @@ -626,10 +637,10 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) success = false; break; } - numericPayload = false; // No, it isn't numeric - doublePayload = NAN; // Invalid value + numericPayload = false; // No, it isn't numeric + doublePayload = NAN; // Invalid value } - UserVar.setFloat(event->TaskIndex, x, doublePayload); // Save the new value + UserVar.setFloat(event->TaskIndex, x, doublePayload); // Save the new value // Generate event for rules processing - proposed by TridentTD @@ -664,9 +675,9 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) addEscapeCharacters(tmp); // Add escape characters to avoid problems with rules processing if a JSON message is received String RuleEvent = strformat(F("%s#%s=%s"), - getTaskDeviceName(event->TaskIndex).c_str(), - event->String1.c_str(), - wrapWithQuotesIfContainsParameterSeparatorChar(tmp).c_str()); + getTaskDeviceName(event->TaskIndex).c_str(), + event->String1.c_str(), + wrapWithQuotesIfContainsParameterSeparatorChar(tmp).c_str()); P037_addEventToQueue(event, RuleEvent); } @@ -675,9 +686,9 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("IMPT : [%s#%s] : %s"), - getTaskDeviceName(event->TaskIndex).c_str(), - checkJson ? key.c_str() : getTaskValueName(event->TaskIndex, x).c_str(), - toString(doublePayload, ExtraTaskSettings.TaskDeviceValueDecimals[x]).c_str())); + getTaskDeviceName(event->TaskIndex).c_str(), + checkJson ? key.c_str() : getTaskValueName(event->TaskIndex, x).c_str(), + toString(doublePayload, ExtraTaskSettings.TaskDeviceValueDecimals[x]).c_str())); } # endif // if !defined(P037_LIMIT_BUILD_SIZE) || defined(P037_OVERRIDE) @@ -694,7 +705,7 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) } else { String tmp = Payload; addEscapeCharacters(tmp); // Add escape characters to avoid problems with rules processing if a JSON message is received - + RuleEvent += wrapWithQuotesIfContainsParameterSeparatorChar(tmp); } P037_addEventToQueue(event, RuleEvent); diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 054f6ac4f..f901eb489 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -4395,13 +4395,26 @@ To create/register a plugin, you have to : //-------------------End of HTTPResponseParser Section---------- #ifndef FEATURE_JSON_PARSE - #if defined(USES_P037) || FEATURE_JSON_EVENT // and other JSON-parsing features - #define FEATURE_JSON_PARSE 1 + #if defined(ESP32) && (defined(USES_P037) || FEATURE_JSON_EVENT) // and other JSON-parsing features + #define FEATURE_JSON_PARSE 1 // By default only on ESP32 #else #define FEATURE_JSON_PARSE 0 #endif // if defined(USES_P037) || FEATURE_JSON_EVENT #endif // ifndef FEATURE_JSON_PARSE +#ifndef FEATURE_EXTENDED_STRING_FUNCTIONS + #ifdef ESP32 + #define FEATURE_EXTENDED_STRING_FUNCTIONS 1 + #endif // ifdef ESP32 + #ifdef ESP8266 + #define FEATURE_EXTENDED_STRING_FUNCTIONS 0 + #endif // ifdef ESP8266 +#endif // ifndef FEATURE_EXTENDED_STRING_FUNCTIONS +#if defined(ESP8266) && FEATURE_JSON_PARSE && !FEATURE_EXTENDED_STRING_FUNCTIONS + #undef FEATURE_JSON_PARSE + #define FEATURE_JSON_PARSE 0 +#endif // if FEATURE_JSON_PARSE && !FEATURE_EXTENDED_STRING_FUNCTIONS + #if !(defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED) #ifdef USES_P152 #undef USES_P152 @@ -4422,7 +4435,7 @@ To create/register a plugin, you have to : #if FEATURE_WIFI #ifndef USES_NW001 #define USES_NW001 - #endif + #endif #ifndef USES_NW002 #define USES_NW002 #endif diff --git a/src/src/ESPEasyCore/ESPEasyRules.cpp b/src/src/ESPEasyCore/ESPEasyRules.cpp index 91e955413..176dac901 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.cpp +++ b/src/src/ESPEasyCore/ESPEasyRules.cpp @@ -29,10 +29,10 @@ #include #include -#if FEATURE_JSON_PARSE +#if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS #include #include "../Helpers/JSON_helper.h" -#endif // if FEATURE_JSON_PARSE +#endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS #ifdef WEBSERVER_NEW_RULES String EventToFileName(const String& eventName) { @@ -471,12 +471,14 @@ bool parse_math_functions(const String& cmd_s_lower, const String& arg1, const S 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 + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS "|json" - #endif // if FEATURE_JSON_PARSE + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS ; enum class string_commands_e { substring, @@ -493,16 +495,19 @@ enum class string_commands_e { 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 + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS json, - #endif // if FEATURE_JSON_PARSE + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS }; + #if FEATURE_EXTENDED_STRING_FUNCTIONS void hideEscaped(String &line, bool &mustReplaceEscapedBracket, bool &mustReplaceEscapedCurlyBracket, bool &mustReplaceEscapedColon) { String MaskEscapedBracket; @@ -577,12 +582,13 @@ void restoreMaskedAndEscaped(String &line, bool mustReplaceMaskedChars, bool mus 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 + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS DynamicJsonDocument*root = nullptr; uint16_t lastJsonMessageLength = 512; @@ -594,14 +600,37 @@ void parse_string_commands(String& line) { root = nullptr; } }; - #endif // if FEATURE_JSON_PARSE + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS bool mustReplaceMaskedChars = false; bool mustReplaceEscapedBracket = false; bool mustReplaceEscapedCurlyBracket = false; - bool mustReplaceEscapedColon = false; String MaskEscapedBracket; + + #if FEATURE_EXTENDED_STRING_FUNCTIONS + bool mustReplaceEscapedColon = false; hideEscaped(line, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); + #else // if FEATURE_EXTENDED_STRING_FUNCTIONS + 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; + } + #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS while (get_next_inner_bracket(line, startIndex, closingIndex, '}')) { // Command without opening and closing brackets. @@ -657,6 +686,7 @@ void parse_string_commands(String& line) { 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, ':'); @@ -673,6 +703,7 @@ void parse_string_commands(String& line) { 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: @@ -770,7 +801,7 @@ void parse_string_commands(String& line) { replacement = URLEncode(arg1); } break; - #if FEATURE_STRING_VARIABLES + #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::[]:} @@ -786,8 +817,8 @@ void parse_string_commands(String& line) { hideMasked(replacement, mustReplaceMaskedChars); // re-apply } break; - #endif // if FEATURE_STRING_VARIABLES - #if FEATURE_JSON_PARSE + #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::[]:} @@ -838,7 +869,7 @@ void parse_string_commands(String& line) { } break; } - #endif // if FEATURE_JSON_PARSE + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS } } } @@ -850,7 +881,12 @@ void parse_string_commands(String& line) { // See: https://github.com/letscontrolit/ESPEasy/issues/2932#issuecomment-596139096 replacement = line.substring(startIndex, closingIndex + 1); mustReplaceMaskedChars = true; + #if FEATURE_EXTENDED_STRING_FUNCTIONS hideMasked(replacement, mustReplaceMaskedChars); // apply + #else // if FEATURE_EXTENDED_STRING_FUNCTIONS + line.replace('{', static_cast(0x02)); + line.replace('}', static_cast(0x03)); + #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS } // Replace the full command including opening and closing brackets. @@ -864,14 +900,41 @@ void parse_string_commands(String& line) { } } - #if FEATURE_JSON_PARSE + #if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS if (nullptr != root) { cleanupJSON(); } - #endif // if FEATURE_JSON_PARSE + #endif // if FEATURE_JSON_PARSE && FEATURE_EXTENDED_STRING_FUNCTIONS + #if FEATURE_EXTENDED_STRING_FUNCTIONS restoreMaskedAndEscaped(line, mustReplaceMaskedChars, mustReplaceEscapedBracket, mustReplaceEscapedCurlyBracket, mustReplaceEscapedColon); + #else // if FEATURE_EXTENDED_STRING_FUNCTIONS + 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("\\}")); + } + #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS } void substitute_eventvalue(String& line, const String& event) { diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 9d57f6d8a..30cc93215 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -322,6 +322,7 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { } } + # if FEATURE_JSON_PARSE const String val = getJsonValue(root, key, false); // Return arrays and objects as csv, _not_ JSON formatted if (!val.isEmpty()) { @@ -329,6 +330,107 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { 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(); diff --git a/src/src/Helpers/JSON_helper.h b/src/src/Helpers/JSON_helper.h index 0566a8a66..a6b8e6520 100644 --- a/src/src/Helpers/JSON_helper.h +++ b/src/src/Helpers/JSON_helper.h @@ -1,6 +1,6 @@ #pragma once -/** See */ +/** See JSON_helper.cpp for changelog */ #if FEATURE_JSON_PARSE # include # include "../Helpers/StringConverter_Numerical.h" diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index b95982fc9..533870176 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -37,7 +37,11 @@ bool hasEscapedCharacter(String& str, const char EscapeChar) void stripEscapeCharacters(String& str) { - const char braces[] = { '%', '[', ']', '{', '}', '(', ')', ':' }; + const char braces[] = { '%', '[', ']', '{', '}', '(', ')' + #if FEATURE_EXTENDED_STRING_FUNCTIONS + , ':' + #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS + }; constexpr uint8_t nrbraces = NR_ELEMENTS(braces); for (uint8_t i = 0; i < nrbraces; ++i) { @@ -48,7 +52,11 @@ void stripEscapeCharacters(String& str) void addEscapeCharacters(String& str) { - const char braces[] = { '%', '[', ']', '{', '}', '(', ')', ':' }; + const char braces[] = { '%', '[', ']', '{', '}', '(', ')' + #if FEATURE_EXTENDED_STRING_FUNCTIONS + , ':' + #endif // if FEATURE_EXTENDED_STRING_FUNCTIONS + }; constexpr uint8_t nrbraces = NR_ELEMENTS(braces); for (uint8_t i = 0; i < nrbraces; ++i) { diff --git a/src/src/PluginStructs/P037_data_struct.h b/src/src/PluginStructs/P037_data_struct.h index f6b70070a..5dc10c6d8 100644 --- a/src/src/PluginStructs/P037_data_struct.h +++ b/src/src/PluginStructs/P037_data_struct.h @@ -10,9 +10,6 @@ # include "../Helpers/Misc.h" # include "../Helpers/StringParser.h" # include "../Globals/MQTT.h" -# include "../Helpers/JSON_helper.h" - -# include // # define PLUGIN_037_DEBUG // Additional debugging information @@ -85,6 +82,12 @@ # endif // ifndef P037_FILTER_PER_TOPIC # endif // if P037_FILTER_SUPPORT && P037_MAX_FILTERS == VARS_PER_TASK +#if P037_JSON_SUPPORT +# include "../Helpers/JSON_helper.h" + +# include +#endif // if P037_JSON_SUPPORT + # define P037_ARRAY_SIZE (P037_MAX_MAPPINGS + P037_MAX_FILTERS) // Storage layout definitions # define P037_START_MAPPINGS 0 # define P037_END_MAPPINGS (P037_MAX_MAPPINGS - 1)