From 514a140a967b4e9ca74a9352f1c7d11ba3f98623 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:20:30 +0100 Subject: [PATCH 01/24] Update Networking.cpp --- src/src/Helpers/Networking.cpp | 133 +++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index d18cb7c81a..c88cb3bd5b 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1682,6 +1682,139 @@ int http_authenticate(const String& logIdentifier, } } #endif + + # if FEATURE_OMETEO_EVENT + + // Generate an event with the response of an open-meteo request (https://open-meteo.com/en/docs) + // Example command: + // sendtohttp,api.open-meteo.com,80,/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1 + // No need for an api key and it is free (daily requests are limited to 10,000 in the free version) + // Visit the URL and build your personal URL by selecting the location and values you want to receive. + // Supported variable kinds are current, hourly, daily! + // In rules you can grep the reply by the kind of weather variables with "On Openmeteo# Do ..." + // e.g. "On Openmeteo#current Do ..." + // Note: hourly and daily results are arrays which can become very long. + // Best to make seperate calls. Especially for hourly results. + + if ((httpCode == 200) && equals(host, F("api.open-meteo.com"))) + { + // Length of the response is limited to 5000 characters + // ToDo: What would be right size? + if (uri.length() > 5000) { + addLog(LOG_LEVEL_ERROR, F("Response exceeds 5000 characters")); + } + else { + String str = http.getString(); + + // Process URL to get unique keys + + + auto getCombinedParams = [](String url, String paramName) { + int start = url.indexOf(paramName + "="); + + if (start == -1) { return String(""); } + start += paramName.length() + 1; + int end = url.indexOf('&', start); + return (end == -1) ? url.substring(start) : url.substring(start, end); + }; + + // Extract current and daily parameters + String currentParams = getCombinedParams(uri, "current"); + String hourlyParams = getCombinedParams(uri, "hourly"); + String dailyParams = getCombinedParams(uri, "daily"); + + auto processAndQueueParams = [](String& params, String& str, String eventName) { + if (!params.isEmpty()) { + String keys[20]; + int keyCount = 0; + + int startIndex = 0; + int commaIndex = params.indexOf(','); + + // Split and add keys to the array + while (commaIndex != -1) { + String key = params.substring(startIndex, commaIndex); + + + keys[keyCount++] = key; + + startIndex = commaIndex + 1; + commaIndex = params.indexOf(',', startIndex); + } + + // Add the last key + String lastKey = params.substring(startIndex); + + + keys[keyCount++] = lastKey; + + String csv = ""; + int startStringIndex = str.indexOf("\"" + eventName + "\":") + eventName.length() + 4; + int endStringIndex = str.indexOf("}", startStringIndex); + + for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration + { + String key = keys[i]; + String value = ""; + int startIndex = str.indexOf(key + "\":", startStringIndex); + + if (startIndex == -1) + { + // Handle case where key is not found + value = "-256"; // Placeholder value + } + else + { + int endIndex = 0; + + if (eventName != "current") { // in daily and hourly the values are stored in an + // array + // so + // get rid of the brackets and put the values in a csv + startIndex += key.length() + 3; // Move index past the key + endIndex = str.indexOf("]", startIndex); + } + else { + startIndex += key.length() + 2; // Move index past the key + endIndex = str.indexOf(",", startIndex); + } + + // Find the index of the next comma + if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) + { + endIndex = str.indexOf("}", startIndex); // If no comma is found or comma comes after }, + // take + // the + // rest of the string + } + + value = str.substring(startIndex, endIndex); + value.trim(); // Remove any surrounding whitespace + } + + if (!csv.isEmpty()) + { + csv += ","; + } + csv += value; + } + eventName = "OpenMeteo#" + eventName; + eventQueue.addMove(strformat(F("%s=%s"), eventName.c_str(), csv.c_str())); + } + }; + + + // Process currentParams + processAndQueueParams(currentParams, str, "current"); + + // Process hourlyParams + processAndQueueParams(hourlyParams, str, "hourly"); + + // Process dailyParams + processAndQueueParams(dailyParams, str, "daily"); + } + } + # endif // if FEATURE_OMETEO_EVENT } #ifndef BUILD_NO_DEBUG From a5536715e1952faa7a0bd75305f46c2b2f79a5ca Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:02:40 +0100 Subject: [PATCH 02/24] a tiny bit better formatting --- src/src/Helpers/Networking.cpp | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index c88cb3bd5b..285916eba1 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1685,11 +1685,11 @@ int http_authenticate(const String& logIdentifier, # if FEATURE_OMETEO_EVENT - // Generate an event with the response of an open-meteo request (https://open-meteo.com/en/docs) + // Generate an event with the response of an open-meteo request. // Example command: - // sendtohttp,api.open-meteo.com,80,/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1 + // sendtohttp,api.open-meteo.com,80,"/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1" // No need for an api key and it is free (daily requests are limited to 10,000 in the free version) - // Visit the URL and build your personal URL by selecting the location and values you want to receive. + // Visit the URL (https://open-meteo.com/en/docs) and build your personal URL by selecting the location and values you want to receive. // Supported variable kinds are current, hourly, daily! // In rules you can grep the reply by the kind of weather variables with "On Openmeteo# Do ..." // e.g. "On Openmeteo#current Do ..." @@ -1707,8 +1707,6 @@ int http_authenticate(const String& logIdentifier, String str = http.getString(); // Process URL to get unique keys - - auto getCombinedParams = [](String url, String paramName) { int start = url.indexOf(paramName + "="); @@ -1718,7 +1716,7 @@ int http_authenticate(const String& logIdentifier, return (end == -1) ? url.substring(start) : url.substring(start, end); }; - // Extract current and daily parameters + // Extract current hourly and daily parameters String currentParams = getCombinedParams(uri, "current"); String hourlyParams = getCombinedParams(uri, "hourly"); String dailyParams = getCombinedParams(uri, "daily"); @@ -1734,18 +1732,13 @@ int http_authenticate(const String& logIdentifier, // Split and add keys to the array while (commaIndex != -1) { String key = params.substring(startIndex, commaIndex); - - keys[keyCount++] = key; - - startIndex = commaIndex + 1; - commaIndex = params.indexOf(',', startIndex); + startIndex = commaIndex + 1; + commaIndex = params.indexOf(',', startIndex); } // Add the last key String lastKey = params.substring(startIndex); - - keys[keyCount++] = lastKey; String csv = ""; @@ -1803,14 +1796,8 @@ int http_authenticate(const String& logIdentifier, } }; - - // Process currentParams processAndQueueParams(currentParams, str, "current"); - - // Process hourlyParams processAndQueueParams(hourlyParams, str, "hourly"); - - // Process dailyParams processAndQueueParams(dailyParams, str, "daily"); } } From 86812a168f0c7673c9c1eea537ca8cd7bbe9a4f3 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:25:44 +0100 Subject: [PATCH 03/24] changes for OPENMETEO_EVENT and THINGSPEAK_EVENT - added FEATURE_OPENMETEO_EVENT to Custom-sample.h - disabled FEATURE_THINGSPEAK_EVENT and FEATURE_OPENMETEO_EVENT on default - both got the same structure in define_plugin_sets.h --- src/Custom-sample.h | 3 ++- src/src/CustomBuild/define_plugin_sets.h | 8 ++++---- src/src/Helpers/Networking.cpp | 11 +++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Custom-sample.h b/src/Custom-sample.h index b27dbc43f0..01230740c1 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -25,7 +25,8 @@ #define FEATURE_RULES_EASY_COLOR_CODE 1 // Use code highlighting, autocompletion and command suggestions in Rules #define FEATURE_ESPEASY_P2P 1 // (1/0) enables the ESP Easy P2P protocol #define FEATURE_ARDUINO_OTA 1 // enables the Arduino OTA capabilities -#define FEATURE_THINGSPEAK_EVENT 1 // generate an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) +#define FEATURE_THINGSPEAK_EVENT 0 // generate an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) +#define FEATURE_OPENMETEO_EVENT 0 // Generate an event with the response of a open-meteo request (https://open-meteo.com/en/docs) // #define FEATURE_SD 1 // Enable SD card support // #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 58b48d044a..d634a25bd0 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3621,11 +3621,11 @@ To create/register a plugin, you have to : #ifndef FEATURE_THINGSPEAK_EVENT - #ifdef LIMIT_BUILD_SIZE #define FEATURE_THINGSPEAK_EVENT 0 - #else - #define FEATURE_THINGSPEAK_EVENT 1 - #endif + #endif + + #ifndef FEATURE_OPENMETEO_EVENT + #define FEATURE_OPENMETEO_EVENT 0 #endif #if !(defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 285916eba1..bd7ec73280 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1683,7 +1683,7 @@ int http_authenticate(const String& logIdentifier, } #endif - # if FEATURE_OMETEO_EVENT + # if FEATURE_OPENMETEO_EVENT // Generate an event with the response of an open-meteo request. // Example command: @@ -1724,10 +1724,9 @@ int http_authenticate(const String& logIdentifier, auto processAndQueueParams = [](String& params, String& str, String eventName) { if (!params.isEmpty()) { String keys[20]; - int keyCount = 0; - - int startIndex = 0; - int commaIndex = params.indexOf(','); + int keyCount = 0; + int startIndex = 0; + int commaIndex = params.indexOf(','); // Split and add keys to the array while (commaIndex != -1) { @@ -1800,7 +1799,7 @@ int http_authenticate(const String& logIdentifier, processAndQueueParams(hourlyParams, str, "hourly"); processAndQueueParams(dailyParams, str, "daily"); } - } + } # endif // if FEATURE_OMETEO_EVENT } From 3bc16d81c9973eab3ce1f91d9a70163e40600747 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Thu, 2 Jan 2025 23:48:01 +0100 Subject: [PATCH 04/24] Update Networking.cpp --- src/src/Helpers/Networking.cpp | 55 ++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index bd7ec73280..78e9d5a816 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1683,7 +1683,7 @@ int http_authenticate(const String& logIdentifier, } #endif - # if FEATURE_OPENMETEO_EVENT + # if FEATURE_OPENMETEO_EVENT // Generate an event with the response of an open-meteo request. // Example command: @@ -1704,24 +1704,24 @@ int http_authenticate(const String& logIdentifier, addLog(LOG_LEVEL_ERROR, F("Response exceeds 5000 characters")); } else { - String str = http.getString(); + const String str = http.getString(); // Process URL to get unique keys - auto getCombinedParams = [](String url, String paramName) { - int start = url.indexOf(paramName + "="); + auto getCombinedParams = [](const String& url, const String& paramName) { + int start = url.indexOf(paramName + '='); - if (start == -1) { return String(""); } + if (start == -1) { return EMPTY_STRING; } start += paramName.length() + 1; - int end = url.indexOf('&', start); + const int end = url.indexOf('&', start); return (end == -1) ? url.substring(start) : url.substring(start, end); }; // Extract current hourly and daily parameters - String currentParams = getCombinedParams(uri, "current"); - String hourlyParams = getCombinedParams(uri, "hourly"); - String dailyParams = getCombinedParams(uri, "daily"); + const String currentParams = getCombinedParams(uri, F("current")); + const String hourlyParams = getCombinedParams(uri, F("hourly")); + const String dailyParams = getCombinedParams(uri, F("daily")); - auto processAndQueueParams = [](String& params, String& str, String eventName) { + auto processAndQueueParams = [](const String& params, const String& str, const String& eventName) { if (!params.isEmpty()) { String keys[20]; int keyCount = 0; @@ -1730,6 +1730,10 @@ int http_authenticate(const String& logIdentifier, // Split and add keys to the array while (commaIndex != -1) { + if (keyCount >= 20){ + // stop adding keys if array is full + break; + } String key = params.substring(startIndex, commaIndex); keys[keyCount++] = key; startIndex = commaIndex + 1; @@ -1737,29 +1741,33 @@ int http_authenticate(const String& logIdentifier, } // Add the last key - String lastKey = params.substring(startIndex); - keys[keyCount++] = lastKey; + if (keyCount < 20) { + const String lastKey = params.substring(startIndex); + keys[keyCount++] = lastKey; + } else { + addLog(LOG_LEVEL_ERROR, F("Too many keys in the URL")); + } - String csv = ""; - int startStringIndex = str.indexOf("\"" + eventName + "\":") + eventName.length() + 4; - int endStringIndex = str.indexOf("}", startStringIndex); + String csv; + const int startStringIndex = str.indexOf("\"" + eventName + "\":") + eventName.length() + 4; + const int endStringIndex = str.indexOf("}", startStringIndex); for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration { String key = keys[i]; - String value = ""; + String value; int startIndex = str.indexOf(key + "\":", startStringIndex); if (startIndex == -1) { // Handle case where key is not found - value = "-256"; // Placeholder value + value = F("-256"); // Placeholder value } else { int endIndex = 0; - if (eventName != "current") { // in daily and hourly the values are stored in an + if (!equals(eventName, F("current"))) { // in daily and hourly the values are stored in an // array // so // get rid of the brackets and put the values in a csv @@ -1786,18 +1794,19 @@ int http_authenticate(const String& logIdentifier, if (!csv.isEmpty()) { - csv += ","; + csv += ','; } csv += value; } - eventName = "OpenMeteo#" + eventName; + + concat(F("OpenMeteo#"), eventName); eventQueue.addMove(strformat(F("%s=%s"), eventName.c_str(), csv.c_str())); } }; - processAndQueueParams(currentParams, str, "current"); - processAndQueueParams(hourlyParams, str, "hourly"); - processAndQueueParams(dailyParams, str, "daily"); + processAndQueueParams(currentParams, str, F("current")); + processAndQueueParams(hourlyParams, str, F("hourly")); + processAndQueueParams(dailyParams, str, F("daily")); } } # endif // if FEATURE_OMETEO_EVENT From aaa938ea6f57821e3b2f075917ba3c87bf253fa7 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 00:19:26 +0100 Subject: [PATCH 05/24] combining two functions --- src/src/Helpers/Networking.cpp | 185 +++++++++++++++------------------ 1 file changed, 84 insertions(+), 101 deletions(-) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 78e9d5a816..12985a7e2d 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1706,107 +1706,90 @@ int http_authenticate(const String& logIdentifier, else { const String str = http.getString(); - // Process URL to get unique keys - auto getCombinedParams = [](const String& url, const String& paramName) { - int start = url.indexOf(paramName + '='); - - if (start == -1) { return EMPTY_STRING; } - start += paramName.length() + 1; - const int end = url.indexOf('&', start); - return (end == -1) ? url.substring(start) : url.substring(start, end); - }; - - // Extract current hourly and daily parameters - const String currentParams = getCombinedParams(uri, F("current")); - const String hourlyParams = getCombinedParams(uri, F("hourly")); - const String dailyParams = getCombinedParams(uri, F("daily")); - - auto processAndQueueParams = [](const String& params, const String& str, const String& eventName) { - if (!params.isEmpty()) { - String keys[20]; - int keyCount = 0; - int startIndex = 0; - int commaIndex = params.indexOf(','); - - // Split and add keys to the array - while (commaIndex != -1) { - if (keyCount >= 20){ - // stop adding keys if array is full - break; - } - String key = params.substring(startIndex, commaIndex); - keys[keyCount++] = key; - startIndex = commaIndex + 1; - commaIndex = params.indexOf(',', startIndex); - } - - // Add the last key - if (keyCount < 20) { - const String lastKey = params.substring(startIndex); - keys[keyCount++] = lastKey; - } else { - addLog(LOG_LEVEL_ERROR, F("Too many keys in the URL")); - } - - String csv; - const int startStringIndex = str.indexOf("\"" + eventName + "\":") + eventName.length() + 4; - const int endStringIndex = str.indexOf("}", startStringIndex); - - for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration - { - String key = keys[i]; - String value; - int startIndex = str.indexOf(key + "\":", startStringIndex); - - if (startIndex == -1) - { - // Handle case where key is not found - value = F("-256"); // Placeholder value - } - else - { - int endIndex = 0; - - if (!equals(eventName, F("current"))) { // in daily and hourly the values are stored in an - // array - // so - // get rid of the brackets and put the values in a csv - startIndex += key.length() + 3; // Move index past the key - endIndex = str.indexOf("]", startIndex); - } - else { - startIndex += key.length() + 2; // Move index past the key - endIndex = str.indexOf(",", startIndex); - } - - // Find the index of the next comma - if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) - { - endIndex = str.indexOf("}", startIndex); // If no comma is found or comma comes after }, - // take - // the - // rest of the string - } - - value = str.substring(startIndex, endIndex); - value.trim(); // Remove any surrounding whitespace - } - - if (!csv.isEmpty()) - { - csv += ','; - } - csv += value; - } - - concat(F("OpenMeteo#"), eventName); - eventQueue.addMove(strformat(F("%s=%s"), eventName.c_str(), csv.c_str())); - } - }; - - processAndQueueParams(currentParams, str, F("current")); - processAndQueueParams(hourlyParams, str, F("hourly")); - processAndQueueParams(dailyParams, str, F("daily")); + auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { + // Extract the parameters from the URL + int start = url.indexOf(eventName + '='); + if (start == -1) { + return; // No parameters found for the given eventName + } + start += eventName.length() + 1; + const int end = url.indexOf('&', start); + const String params = (end == -1) ? url.substring(start) : url.substring(start, end); + + if (!params.isEmpty()) { + String keys[20]; + int keyCount = 0; + int startIndex = 0; + int commaIndex = params.indexOf(','); + + // Split and add keys to the array + while (commaIndex != -1) { + if (keyCount >= 20) { + // Stop adding keys if array is full + addLog(LOG_LEVEL_ERROR, F("Too many keys in the URL")); + break; + } + String key = params.substring(startIndex, commaIndex); + keys[keyCount++] = key; + startIndex = commaIndex + 1; + commaIndex = params.indexOf(',', startIndex); + } + + // Add the last key + if (keyCount < 20) { + const String lastKey = params.substring(startIndex); + keys[keyCount++] = lastKey; + } + + String csv; + const int startStringIndex = str.indexOf("\"" + eventName + "\":") + eventName.length() + 4; + const int endStringIndex = str.indexOf("}", startStringIndex); + + for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration + { + String key = keys[i]; + String value; + int startIndex = str.indexOf(key + "\":", startStringIndex); + + if (startIndex == -1) { + // Handle case where key is not found + value = F("-256"); // Placeholder value + } else { + int endIndex = 0; + + if (!equals(eventName, F("current"))) { + // In daily and hourly, the values are stored in an array + startIndex += key.length() + 3; // Move index past the key + endIndex = str.indexOf("]", startIndex); + } else { + startIndex += key.length() + 2; // Move index past the key + endIndex = str.indexOf(",", startIndex); + } + + // Find the index of the next comma + if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) { + endIndex = str.indexOf("}", startIndex); // If no comma is found or comma comes after }, + // take the rest of the string + } + + value = str.substring(startIndex, endIndex); + value.trim(); // Remove any surrounding whitespace + } + + if (!csv.isEmpty()) { + csv += ','; + } + csv += value; + } + + concat(F("OpenMeteo#"), eventName); + eventQueue.addMove(strformat(F("%s=%s"), eventName.c_str(), csv.c_str())); + } + }; + + processAndQueueParams(uri, str, F("current")); + processAndQueueParams(uri, str, F("hourly")); + processAndQueueParams(uri, str, F("daily")); } } # endif // if FEATURE_OMETEO_EVENT From 94ec6b0fa7cb6338cc49e5746beb53d20ef6f281 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 00:27:09 +0100 Subject: [PATCH 06/24] wrong use of concat times two :) --- src/src/Helpers/Networking.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 12985a7e2d..7c8d161dd2 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1781,9 +1781,7 @@ int http_authenticate(const String& logIdentifier, } csv += value; } - - concat(F("OpenMeteo#"), eventName); - eventQueue.addMove(strformat(F("%s=%s"), eventName.c_str(), csv.c_str())); + eventQueue.addMove(strformat(F("OpenMeteo#%s=%s"), eventName.c_str(), csv.c_str())); } }; From 7325ed83bac5938c5aa0e9ce5f9a00f700d9b912 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:55:28 +0100 Subject: [PATCH 07/24] update - add events to max builds - minor code changes - size warnings: defines added --- src/src/CustomBuild/define_plugin_sets.h | 14 +- src/src/Helpers/Networking.cpp | 177 ++++++++++++----------- 2 files changed, 101 insertions(+), 90 deletions(-) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index d634a25bd0..346a59eb62 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3620,12 +3620,20 @@ To create/register a plugin, you have to : */ - #ifndef FEATURE_THINGSPEAK_EVENT - #define FEATURE_THINGSPEAK_EVENT 0 +#ifndef FEATURE_THINGSPEAK_EVENT + #if defined(PLUGIN_BUILD_MAX_ESP32) + #define FEATURE_THINGSPEAK_EVENT 1 + #else + #define FEATURE_THINGSPEAK_EVENT 0 #endif +#endif #ifndef FEATURE_OPENMETEO_EVENT - #define FEATURE_OPENMETEO_EVENT 0 + #if defined(PLUGIN_BUILD_MAX_ESP32) + #define FEATURE_OPENMETEO_EVENT 1 + #else + #define FEATURE_OPENMETEO_EVENT 0 + #endif #endif #if !(defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 7c8d161dd2..09fd3028e3 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1695,101 +1695,104 @@ int http_authenticate(const String& logIdentifier, // e.g. "On Openmeteo#current Do ..." // Note: hourly and daily results are arrays which can become very long. // Best to make seperate calls. Especially for hourly results. + + //define limits + #define WEATHER_KEYS_MAX 10 + #define URI_MAX_LENGTH 5000 if ((httpCode == 200) && equals(host, F("api.open-meteo.com"))) { - // Length of the response is limited to 5000 characters - // ToDo: What would be right size? - if (uri.length() > 5000) { - addLog(LOG_LEVEL_ERROR, F("Response exceeds 5000 characters")); + + const String str = http.getString(); + + if (str.length() > 5000) { + addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); } - else { - const String str = http.getString(); - - auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { - // Extract the parameters from the URL - int start = url.indexOf(eventName + '='); - if (start == -1) { - return; // No parameters found for the given eventName - } - start += eventName.length() + 1; - const int end = url.indexOf('&', start); - const String params = (end == -1) ? url.substring(start) : url.substring(start, end); - - if (!params.isEmpty()) { - String keys[20]; - int keyCount = 0; - int startIndex = 0; - int commaIndex = params.indexOf(','); - - // Split and add keys to the array - while (commaIndex != -1) { - if (keyCount >= 20) { - // Stop adding keys if array is full - addLog(LOG_LEVEL_ERROR, F("Too many keys in the URL")); - break; - } - String key = params.substring(startIndex, commaIndex); - keys[keyCount++] = key; - startIndex = commaIndex + 1; - commaIndex = params.indexOf(',', startIndex); - } - // Add the last key - if (keyCount < 20) { - const String lastKey = params.substring(startIndex); - keys[keyCount++] = lastKey; - } + auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { + // Extract the parameters from the URL + int start = url.indexOf(eventName + '='); + if (start == -1) { + return; // No parameters found for the given eventName + } + start += eventName.length() + 1; + const int end = url.indexOf('&', start); + const String params = (end == -1) ? url.substring(start) : url.substring(start, end); + + if (!params.isEmpty()) { + String keys[WEATHER_KEYS_MAX]; + int keyCount = 0; + int startIndex = 0; + int commaIndex = params.indexOf(','); + + // Split and add keys to the array + while (commaIndex != -1) { + if (keyCount >= WEATHER_KEYS_MAX) { + // Stop adding keys if array is full + addLog(LOG_LEVEL_ERROR, strformat(F("More than %d keys in the URL, this could cause instabilities or crashes! Try to split up the calls.."), WEATHER_KEYS_MAX)); + break; + } + String key = params.substring(startIndex, commaIndex); + keys[keyCount++] = key; + startIndex = commaIndex + 1; + commaIndex = params.indexOf(',', startIndex); + } - String csv; - const int startStringIndex = str.indexOf("\"" + eventName + "\":") + eventName.length() + 4; - const int endStringIndex = str.indexOf("}", startStringIndex); - - for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration - { - String key = keys[i]; - String value; - int startIndex = str.indexOf(key + "\":", startStringIndex); - - if (startIndex == -1) { - // Handle case where key is not found - value = F("-256"); // Placeholder value - } else { - int endIndex = 0; - - if (!equals(eventName, F("current"))) { - // In daily and hourly, the values are stored in an array - startIndex += key.length() + 3; // Move index past the key - endIndex = str.indexOf("]", startIndex); - } else { - startIndex += key.length() + 2; // Move index past the key - endIndex = str.indexOf(",", startIndex); - } - - // Find the index of the next comma - if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) { - endIndex = str.indexOf("}", startIndex); // If no comma is found or comma comes after }, - // take the rest of the string - } - - value = str.substring(startIndex, endIndex); - value.trim(); // Remove any surrounding whitespace - } - - if (!csv.isEmpty()) { - csv += ','; - } - csv += value; - } - eventQueue.addMove(strformat(F("OpenMeteo#%s=%s"), eventName.c_str(), csv.c_str())); - } - }; + // Add the last key + if (keyCount < WEATHER_KEYS_MAX) { + const String lastKey = params.substring(startIndex); + keys[keyCount++] = lastKey; + } - processAndQueueParams(uri, str, F("current")); - processAndQueueParams(uri, str, F("hourly")); - processAndQueueParams(uri, str, F("daily")); - } + String csv; + const int startStringIndex = str.indexOf(strformat(F("\"%s\":"), eventName.c_str())) + eventName.length() + 4; + const int endStringIndex = str.indexOf('}', startStringIndex); + + for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration + { + String key = keys[i]; + String value; + int startIndex = str.indexOf(strformat(F("%s\":"), key.c_str()), startStringIndex); + + if (startIndex == -1) { + // Handle case where key is not found + value = F("-256"); // Placeholder value + } else { + int endIndex = 0; + + if (!equals(eventName, F("current"))) { + // In daily and hourly, the values are stored in an array + startIndex += key.length() + 3; // Move index past the key + endIndex = str.indexOf(']', startIndex); + } else { + startIndex += key.length() + 2; // Move index past the key + endIndex = str.indexOf(',', startIndex); + } + + // Find the index of the next comma + if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) { + endIndex = str.indexOf('}', startIndex); // If no comma is found or comma comes after }, + // take the rest of the string + } + + value = str.substring(startIndex, endIndex); + value.trim(); // Remove any surrounding whitespace + } + + if (!csv.isEmpty()) { + csv += ','; + } + csv += value; + } + eventQueue.addMove(strformat(F("OpenMeteo#%s=%s"), eventName.c_str(), csv.c_str())); + } + }; + + processAndQueueParams(uri, str, F("current")); + processAndQueueParams(uri, str, F("hourly")); + processAndQueueParams(uri, str, F("daily")); } + # endif // if FEATURE_OMETEO_EVENT } From 2bc5810dc65295cfe4b58c7e522cecf807ea8219 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:21:04 +0100 Subject: [PATCH 08/24] Update Networking.cpp --- src/src/Helpers/Networking.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 09fd3028e3..230056c9d0 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1705,7 +1705,7 @@ int http_authenticate(const String& logIdentifier, const String str = http.getString(); - if (str.length() > 5000) { + if (str.length() > URI_MAX_LENGTH) { addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); } From 0f5b5f242d355ee69c84dde23ea7606e5238858a Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:11:10 +0100 Subject: [PATCH 09/24] put thingspeak and openmeteo events into extra file --- src/src/CustomBuild/define_plugin_sets.h | 6 + src/src/Helpers/HTTPResponseParser.cpp | 171 +++++++++++++++++++++++ src/src/Helpers/HTTPResponseParser.h | 13 ++ src/src/Helpers/Networking.cpp | 159 ++------------------- 4 files changed, 198 insertions(+), 151 deletions(-) create mode 100644 src/src/Helpers/HTTPResponseParser.cpp create mode 100644 src/src/Helpers/HTTPResponseParser.h diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 346a59eb62..40b3583193 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3636,6 +3636,12 @@ To create/register a plugin, you have to : #endif #endif + #if defined(FEATURE_THINGSPEAK_EVENT) || defined(FEATURE_OPENMETEO_EVENT) + #define RESPONSE_PARSER_SUPPORT 1 + #else + #define RESPONSE_PARSER_SUPPORT 0 + #endif + #if !(defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED) #ifdef USES_P152 #undef USES_P152 diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp new file mode 100644 index 0000000000..8c0835e132 --- /dev/null +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -0,0 +1,171 @@ +#include "../Helpers/Networking.h" +#include "../Globals/EventQueue.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringProvider.h" + + +void eventFromResponse(const String& host, const int& httpCode, const String& uri, HTTPClient& http) { + #if FEATURE_THINGSPEAK_EVENT + + // Generate event with the response of a + // thingspeak request (https://de.mathworks.com/help/thingspeak/readlastfieldentry.html & + // https://de.mathworks.com/help/thingspeak/readdata.html) + // e.g. command for a specific field: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/fields/5/last.csv" + // command for all fields: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/feeds/last.csv" + // where first eventvalue is the channel number and the second to the nineth event values + // are the field values + // Example of the event: "EVENT: ThingspeakReply=1637928,5,24.2,12,900,..." + // ^ ^ └------┬------┘ + // channel number ┘ | └ received values + // field number (only available for a "single-value-event") + // In rules you can grep the reply by "On ThingspeakReply Do ..." + // ----------------------------------------------------------------------------------------------------------------------------- + // 2024-02-05 - Added the option to get a single value of a field or all values of a channel at a certain time (not only the last entry) + // Examples: + // Single channel: "sendtohttp,api.thingspeak.com,80,channels/1637928/fields/1.csv?end=2024-01-01%2023:59:00&results=1" + // => gets the value of field 1 at (or the last entry before) 23:59:00 of the channel 1637928 + // All channels: "sendtohttp,api.thingspeak.com,80,channels/1637928/feeds.csv?end=2024-01-01%2023:59:00&results=1" + // => gets the value of each field of the channel 1637928 at (or the last entry before) 23:59:00 + // ----------------------------------------------------------------------------------------------------------------------------- + + if ((httpCode == 200) && equals(host, + F("api.thingspeak.com")) && + (uri.endsWith(F("/last.csv")) || ((uri.indexOf(F("results=1")) >= 0) && (uri.indexOf(F(".csv")) >= 0)))) { + String result = http.getString(); + + result.replace(' ', '_'); // if using a single field with a certain time, the result contains a space and would break the code + const int posTimestamp = result.lastIndexOf(':'); + + if (posTimestamp >= 0) { + result = parseStringToEndKeepCase(result.substring(posTimestamp), 3); + + if (uri.indexOf(F("fields")) >= 0) { // when there is a single field call add the field number + // before the value + result = parseStringKeepCase(uri, 4, '/').substring(0, 1) + "," + result; // since the field number is always the fourth part of the + // url and is always a single digit, we can use this to + // extact the fieldnumber + } + eventQueue.addMove(strformat( + F("ThingspeakReply=%s,%s"), + parseStringKeepCase(uri, 2, '/').c_str(), + result.c_str())); + } + } + #endif // if FEATURE_THINGSPEAK_EVENT + + #if FEATURE_OPENMETEO_EVENT + + // Generate an event with the response of an open-meteo request. + // Example command: + // sendtohttp,api.open-meteo.com,80,"/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1" + // No need for an api key and it is free (daily requests are limited to 10,000 in the free version) + // Visit the URL (https://open-meteo.com/en/docs) and build your personal URL by selecting the location and values you want to receive. + // Supported variable kinds are current, hourly, daily! + // In rules you can grep the reply by the kind of weather variables with "On Openmeteo# Do ..." + // e.g. "On Openmeteo#current Do ..." + // Note: hourly and daily results are arrays which can become very long. + // Best to make seperate calls. Especially for hourly results. + + // define limits + # define WEATHER_KEYS_MAX 10 + # define URI_MAX_LENGTH 5000 + + if ((httpCode == 200) && equals(host, F("api.open-meteo.com"))) + { + const String str = http.getString(); + + if (str.length() > URI_MAX_LENGTH) { + addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); + } + + auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { + // Extract the parameters from the URL + int start = url.indexOf(eventName + '='); + + if (start == -1) { + return; // No parameters found for the given eventName + } + start += eventName.length() + 1; + const int end = url.indexOf('&', start); + const String params = (end == -1) ? url.substring(start) : url.substring(start, end); + + if (!params.isEmpty()) { + String keys[WEATHER_KEYS_MAX]; + int keyCount = 0; + int startIndex = 0; + int commaIndex = params.indexOf(','); + + // Split and add keys to the array + while (commaIndex != -1) { + if (keyCount >= WEATHER_KEYS_MAX) { + // Stop adding keys if array is full + addLog(LOG_LEVEL_ERROR, + strformat(F( + "More than %d keys in the URL, this could cause instabilities or crashes! Try to split up the calls.."), + WEATHER_KEYS_MAX)); + break; + } + String key = params.substring(startIndex, commaIndex); + keys[keyCount++] = key; + startIndex = commaIndex + 1; + commaIndex = params.indexOf(',', startIndex); + } + + // Add the last key + if (keyCount < WEATHER_KEYS_MAX) { + const String lastKey = params.substring(startIndex); + keys[keyCount++] = lastKey; + } + + String csv; + const int startStringIndex = str.indexOf(strformat(F("\"%s\":"), + eventName.c_str())) + eventName.length() + 4; + const int endStringIndex = str.indexOf('}', startStringIndex); + + for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration + { + String key = keys[i]; + String value; + int startIndex = str.indexOf(strformat(F("%s\":"), key.c_str()), startStringIndex); + + if (startIndex == -1) { + // Handle case where key is not found + value = F("-256"); // Placeholder value + } else { + int endIndex = 0; + + if (!equals(eventName, F("current"))) { + // In daily and hourly, the values are stored in an array + startIndex += key.length() + 3; // Move index past the key + endIndex = str.indexOf(']', startIndex); + } else { + startIndex += key.length() + 2; // Move index past the key + endIndex = str.indexOf(',', startIndex); + } + + // Find the index of the next comma + if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) { + endIndex = str.indexOf('}', startIndex); // If no comma is found or comma comes after }, + // take the rest of the string + } + + value = str.substring(startIndex, endIndex); + value.trim(); // Remove any surrounding whitespace + } + + if (!csv.isEmpty()) { + csv += ','; + } + csv += value; + } + eventQueue.addMove(strformat(F("OpenMeteo#%s=%s"), eventName.c_str(), csv.c_str())); + } + }; + + processAndQueueParams(uri, str, F("current")); + processAndQueueParams(uri, str, F("hourly")); + processAndQueueParams(uri, str, F("daily")); + } + + #endif // if FEATURE_OMETEO_EVENT +} diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h new file mode 100644 index 0000000000..92c8f29bde --- /dev/null +++ b/src/src/Helpers/HTTPResponseParser.h @@ -0,0 +1,13 @@ +#ifndef HELPERS_HTTPRESPONSEPARSER_H +#define HELPERS_HTTPRESPONSEPARSER_H + +#include "../../ESPEasy_common.h" + +// Function declarations +void eventFromResponse(const String& host, + const int & httpCode, + const String& uri, + HTTPClient & http); + + +#endif // OTHER_H diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 230056c9d0..edd23b976a 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -33,6 +33,10 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringProvider.h" +#ifdef RESPONSE_PARSER_SUPPORT +#include "../Helpers/HTTPResponseParser.h" +#endif + #include "../../ESPEasy-Globals.h" #include @@ -43,6 +47,7 @@ #include + // Generic Networking routines // Syslog @@ -1643,157 +1648,9 @@ int http_authenticate(const String& logIdentifier, // Generate event with the HTTP return code // e.g. http#hostname=401 eventQueue.addMove(strformat(F("http#%s=%d"), host.c_str(), httpCode)); - - #if FEATURE_THINGSPEAK_EVENT - // Generate event with the response of a - // thingspeak request (https://de.mathworks.com/help/thingspeak/readlastfieldentry.html & - // https://de.mathworks.com/help/thingspeak/readdata.html) - // e.g. command for a specific field: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/fields/5/last.csv" - // command for all fields: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/feeds/last.csv" - // where first eventvalue is the channel number and the second to the nineth event values - // are the field values - // Example of the event: "EVENT: ThingspeakReply=1637928,5,24.2,12,900,..." - // ^ ^ └------┬------┘ - // channel number ┘ | └ received values - // field number (only available for a "single-value-event") - // In rules you can grep the reply by "On ThingspeakReply Do ..." - //----------------------------------------------------------------------------------------------------------------------------- - // 2024-02-05 - Added the option to get a single value of a field or all values of a channel at a certain time (not only the last entry) - // Examples: - // Single channel: "sendtohttp,api.thingspeak.com,80,channels/1637928/fields/1.csv?end=2024-01-01%2023:59:00&results=1" - // => gets the value of field 1 at (or the last entry before) 23:59:00 of the channel 1637928 - // All channels: "sendtohttp,api.thingspeak.com,80,channels/1637928/feeds.csv?end=2024-01-01%2023:59:00&results=1" - // => gets the value of each field of the channel 1637928 at (or the last entry before) 23:59:00 - //----------------------------------------------------------------------------------------------------------------------------- - - if (httpCode == 200 && equals(host, F("api.thingspeak.com")) && (uri.endsWith(F("/last.csv")) || (uri.indexOf(F("results=1")) >= 0 && uri.indexOf(F(".csv")) >= 0))){ - String result = http.getString(); - result.replace(' ', '_'); // if using a single field with a certain time, the result contains a space and would break the code - const int posTimestamp = result.lastIndexOf(':'); - if (posTimestamp >= 0){ - result = parseStringToEndKeepCase(result.substring(posTimestamp), 3); - if (uri.indexOf(F("fields")) >= 0){ // when there is a single field call add the field number before the value - result = parseStringKeepCase(uri, 4, '/').substring(0, 1) + "," + result; // since the field number is always the fourth part of the url and is always a single digit, we can use this to extact the fieldnumber - } - eventQueue.addMove(strformat( - F("ThingspeakReply=%s,%s"), - parseStringKeepCase(uri, 2, '/').c_str(), - result.c_str())); - } - } - #endif - - # if FEATURE_OPENMETEO_EVENT - - // Generate an event with the response of an open-meteo request. - // Example command: - // sendtohttp,api.open-meteo.com,80,"/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1" - // No need for an api key and it is free (daily requests are limited to 10,000 in the free version) - // Visit the URL (https://open-meteo.com/en/docs) and build your personal URL by selecting the location and values you want to receive. - // Supported variable kinds are current, hourly, daily! - // In rules you can grep the reply by the kind of weather variables with "On Openmeteo# Do ..." - // e.g. "On Openmeteo#current Do ..." - // Note: hourly and daily results are arrays which can become very long. - // Best to make seperate calls. Especially for hourly results. - - //define limits - #define WEATHER_KEYS_MAX 10 - #define URI_MAX_LENGTH 5000 - - if ((httpCode == 200) && equals(host, F("api.open-meteo.com"))) - { - - const String str = http.getString(); - - if (str.length() > URI_MAX_LENGTH) { - addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); - } - - auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { - // Extract the parameters from the URL - int start = url.indexOf(eventName + '='); - if (start == -1) { - return; // No parameters found for the given eventName - } - start += eventName.length() + 1; - const int end = url.indexOf('&', start); - const String params = (end == -1) ? url.substring(start) : url.substring(start, end); - - if (!params.isEmpty()) { - String keys[WEATHER_KEYS_MAX]; - int keyCount = 0; - int startIndex = 0; - int commaIndex = params.indexOf(','); - - // Split and add keys to the array - while (commaIndex != -1) { - if (keyCount >= WEATHER_KEYS_MAX) { - // Stop adding keys if array is full - addLog(LOG_LEVEL_ERROR, strformat(F("More than %d keys in the URL, this could cause instabilities or crashes! Try to split up the calls.."), WEATHER_KEYS_MAX)); - break; - } - String key = params.substring(startIndex, commaIndex); - keys[keyCount++] = key; - startIndex = commaIndex + 1; - commaIndex = params.indexOf(',', startIndex); - } - - // Add the last key - if (keyCount < WEATHER_KEYS_MAX) { - const String lastKey = params.substring(startIndex); - keys[keyCount++] = lastKey; - } - - String csv; - const int startStringIndex = str.indexOf(strformat(F("\"%s\":"), eventName.c_str())) + eventName.length() + 4; - const int endStringIndex = str.indexOf('}', startStringIndex); - - for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration - { - String key = keys[i]; - String value; - int startIndex = str.indexOf(strformat(F("%s\":"), key.c_str()), startStringIndex); - - if (startIndex == -1) { - // Handle case where key is not found - value = F("-256"); // Placeholder value - } else { - int endIndex = 0; - - if (!equals(eventName, F("current"))) { - // In daily and hourly, the values are stored in an array - startIndex += key.length() + 3; // Move index past the key - endIndex = str.indexOf(']', startIndex); - } else { - startIndex += key.length() + 2; // Move index past the key - endIndex = str.indexOf(',', startIndex); - } - - // Find the index of the next comma - if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) { - endIndex = str.indexOf('}', startIndex); // If no comma is found or comma comes after }, - // take the rest of the string - } - - value = str.substring(startIndex, endIndex); - value.trim(); // Remove any surrounding whitespace - } - - if (!csv.isEmpty()) { - csv += ','; - } - csv += value; - } - eventQueue.addMove(strformat(F("OpenMeteo#%s=%s"), eventName.c_str(), csv.c_str())); - } - }; - - processAndQueueParams(uri, str, F("current")); - processAndQueueParams(uri, str, F("hourly")); - processAndQueueParams(uri, str, F("daily")); - } - - # endif // if FEATURE_OMETEO_EVENT +#ifdef RESPONSE_PARSER_SUPPORT + eventFromResponse(host, httpCode, uri, http); +#endif } #ifndef BUILD_NO_DEBUG From fc6ccaa8baab3aac298480ca42f9ed09f024c541 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:00:56 +0100 Subject: [PATCH 10/24] added inverter event --- src/Custom-sample.h | 5 +- src/src/CustomBuild/define_plugin_sets.h | 8 +- src/src/Helpers/HTTPResponseParser.cpp | 94 +++++++++++++++++++++--- src/src/Helpers/HTTPResponseParser.h | 5 +- 4 files changed, 95 insertions(+), 17 deletions(-) diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 01230740c1..3c7a9c34fc 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -25,8 +25,9 @@ #define FEATURE_RULES_EASY_COLOR_CODE 1 // Use code highlighting, autocompletion and command suggestions in Rules #define FEATURE_ESPEASY_P2P 1 // (1/0) enables the ESP Easy P2P protocol #define FEATURE_ARDUINO_OTA 1 // enables the Arduino OTA capabilities -#define FEATURE_THINGSPEAK_EVENT 0 // generate an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) -#define FEATURE_OPENMETEO_EVENT 0 // Generate an event with the response of a open-meteo request (https://open-meteo.com/en/docs) +#define FEATURE_THINGSPEAK_EVENT 0 // Generates an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) +#define FEATURE_OPENMETEO_EVENT 0 // Generates an event with the response of a open-meteo request (https://open-meteo.com/en/docs) +#define FEATURE_INVERTER_EVENT 0 // Generates an event with the response of an inverter example URL: http://192.168.1.199/solar_api/v1/GetInverterRealtimeData.cgi?Scope=System // #define FEATURE_SD 1 // Enable SD card support // #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 40b3583193..44545a8162 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3636,10 +3636,14 @@ To create/register a plugin, you have to : #endif #endif - #if defined(FEATURE_THINGSPEAK_EVENT) || defined(FEATURE_OPENMETEO_EVENT) + #ifndef FEATURE_INVERTER_EVENT + #define FEATURE_INVERTER_EVENT 0 + #endif + + #if FEATURE_THINGSPEAK_EVENT || FEATURE_OPENMETEO_EVENT || FEATURE_INVERTER_EVENT #define RESPONSE_PARSER_SUPPORT 1 #else - #define RESPONSE_PARSER_SUPPORT 0 + #define RESPONSE_PARSER_SUPPORT 1 #endif #if !(defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 8c0835e132..a0dd2395c6 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -3,9 +3,12 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringProvider.h" +#include void eventFromResponse(const String& host, const int& httpCode, const String& uri, HTTPClient& http) { - #if FEATURE_THINGSPEAK_EVENT + + // -------------------------------------------------------------------------------------------Thingspeak +#if FEATURE_THINGSPEAK_EVENT // Generate event with the response of a // thingspeak request (https://de.mathworks.com/help/thingspeak/readlastfieldentry.html & @@ -28,8 +31,7 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // => gets the value of each field of the channel 1637928 at (or the last entry before) 23:59:00 // ----------------------------------------------------------------------------------------------------------------------------- - if ((httpCode == 200) && equals(host, - F("api.thingspeak.com")) && + if ((httpCode == 200) && equals(host, F("api.thingspeak.com")) && (uri.endsWith(F("/last.csv")) || ((uri.indexOf(F("results=1")) >= 0) && (uri.indexOf(F(".csv")) >= 0)))) { String result = http.getString(); @@ -51,9 +53,10 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur result.c_str())); } } - #endif // if FEATURE_THINGSPEAK_EVENT +#endif // if FEATURE_THINGSPEAK_EVENT - #if FEATURE_OPENMETEO_EVENT + // ------------------------------------------------------------------------------------------- OpenMeteo +#if FEATURE_OPENMETEO_EVENT // Generate an event with the response of an open-meteo request. // Example command: @@ -67,11 +70,10 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // Best to make seperate calls. Especially for hourly results. // define limits - # define WEATHER_KEYS_MAX 10 - # define URI_MAX_LENGTH 5000 +# define WEATHER_KEYS_MAX 10 +# define URI_MAX_LENGTH 5000 - if ((httpCode == 200) && equals(host, F("api.open-meteo.com"))) - { + if ((httpCode == 200) && equals(host, F("api.open-meteo.com"))) { const String str = http.getString(); if (str.length() > URI_MAX_LENGTH) { @@ -119,9 +121,15 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur String csv; const int startStringIndex = str.indexOf(strformat(F("\"%s\":"), - eventName.c_str())) + eventName.length() + 4; + eventName.c_str())) + + eventName.length() + 4; + + // example( },"current":{"time":... ) we want to start after current":{ + const int endStringIndex = str.indexOf('}', startStringIndex); + // ...and want to end before } + for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration { String key = keys[i]; @@ -167,5 +175,69 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur processAndQueueParams(uri, str, F("daily")); } - #endif // if FEATURE_OMETEO_EVENT +#endif // if FEATURE_OMETEO_EVENT + + // ------------------------------------------------------------------------------------------- Inverter +#if FEATURE_INVERTER_EVENT + + if ((httpCode == 200) && (uri.endsWith(F("GetInverterRealtimeData.cgi?Scope=System")))) + { + DynamicJsonDocument *root = nullptr; + uint16_t lastJsonMessageLength = 512; + + const String message = http.getString(); + + // Cleanup lambda to deallocate resources + auto cleanupJSON = [&root]() { + if (root != nullptr) { + root->clear(); + delete root; + root = nullptr; + } + }; + + // Check if root needs resizing or cleanup + if ((root != nullptr) && (message.length() * 1.5 > lastJsonMessageLength)) { + cleanupJSON(); + } + + // Resize lastJsonMessageLength if needed + if (message.length() * 1.5 > lastJsonMessageLength) { + lastJsonMessageLength = message.length() * 1.5; + } + + // Allocate memory for root if needed + if (root == nullptr) { +# ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; +# endif // ifdef USE_SECOND_HEAP + root = new (std::nothrow) DynamicJsonDocument(lastJsonMessageLength); // Dynamic allocation + } + + if (root != nullptr) { + // Parse the JSON + DeserializationError error = deserializeJson(*root, message); + + if (!error) { + int dayEnergy = (*root)["Body"]["Data"]["DAY_ENERGY"]["Values"]["1"].as(); + int pac = (*root)["Body"]["Data"]["PAC"]["Values"]["1"].as(); + int totalEnergy = (*root)["Body"]["Data"]["TOTAL_ENERGY"]["Values"]["1"].as(); + int yearEnergy = (*root)["Body"]["Data"]["YEAR_ENERGY"]["Values"]["1"].as(); + + addLog(LOG_LEVEL_INFO, strformat(F("Inverter: Day Energy: %d, PAC: %d, Total Energy: %d, Year Energy: %d"), + dayEnergy, + pac, + totalEnergy, + yearEnergy)); + eventQueue.addMove(strformat(F("InverterReply=%d,%d,%d,%d"), dayEnergy, pac, totalEnergy, yearEnergy)); + } else { + Serial.println("Failed to parse JSON"); + } + + // Cleanup JSON resources + cleanupJSON(); + } + } + +#endif // ifdef FEATURE_INVERTER_EVENT } diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index 92c8f29bde..3769c83836 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -2,6 +2,8 @@ #define HELPERS_HTTPRESPONSEPARSER_H #include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" + // Function declarations void eventFromResponse(const String& host, @@ -9,5 +11,4 @@ void eventFromResponse(const String& host, const String& uri, HTTPClient & http); - -#endif // OTHER_H +#endif // ifndef HELPERS_HTTPRESPONSEPARSER_H From 0ca2d75e87791c87fab8b14bcb102f0be621a37e Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:39:12 +0100 Subject: [PATCH 11/24] fix for not building on minimal_core_274_esp8266... --- src/src/CustomBuild/define_plugin_sets.h | 2 +- src/src/Helpers/HTTPResponseParser.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 44545a8162..46243076c3 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3643,7 +3643,7 @@ To create/register a plugin, you have to : #if FEATURE_THINGSPEAK_EVENT || FEATURE_OPENMETEO_EVENT || FEATURE_INVERTER_EVENT #define RESPONSE_PARSER_SUPPORT 1 #else - #define RESPONSE_PARSER_SUPPORT 1 + #define RESPONSE_PARSER_SUPPORT 0 #endif #if !(defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED) diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index 3769c83836..d14c141793 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -4,6 +4,10 @@ #include "../../ESPEasy_common.h" #include "../../_Plugin_Helper.h" +# ifdef PLUGIN_BUILD_MINIMAL_OTA +# include +# endif // ifdef ESP8266 + // Function declarations void eventFromResponse(const String& host, From daafc7d5da56f414e67f4cf7681321bb6ea32163 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Fri, 3 Jan 2025 21:52:02 +0100 Subject: [PATCH 12/24] #ifdef to #if. m( --- src/src/Helpers/HTTPResponseParser.h | 6 +++--- src/src/Helpers/Networking.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index d14c141793..a23adbc5be 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -4,9 +4,9 @@ #include "../../ESPEasy_common.h" #include "../../_Plugin_Helper.h" -# ifdef PLUGIN_BUILD_MINIMAL_OTA -# include -# endif // ifdef ESP8266 +#ifdef PLUGIN_BUILD_MINIMAL_OTA +# include +#endif // ifdef ESP8266 // Function declarations diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index edd23b976a..2f69099230 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -33,7 +33,7 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringProvider.h" -#ifdef RESPONSE_PARSER_SUPPORT +#if RESPONSE_PARSER_SUPPORT #include "../Helpers/HTTPResponseParser.h" #endif @@ -1648,7 +1648,7 @@ int http_authenticate(const String& logIdentifier, // Generate event with the HTTP return code // e.g. http#hostname=401 eventQueue.addMove(strformat(F("http#%s=%d"), host.c_str(), httpCode)); -#ifdef RESPONSE_PARSER_SUPPORT +#if RESPONSE_PARSER_SUPPORT eventFromResponse(host, httpCode, uri, http); #endif } From 56e2acf6c8d5a462c057535b3cdc54d9ec1193e4 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Sat, 4 Jan 2025 14:54:44 +0100 Subject: [PATCH 13/24] more universal json event approach --- src/Custom-sample.h | 2 +- src/src/CustomBuild/define_plugin_sets.h | 6 +- src/src/Helpers/HTTPResponseParser.cpp | 496 ++++++++++++++--------- src/src/Helpers/HTTPResponseParser.h | 20 +- 4 files changed, 315 insertions(+), 209 deletions(-) diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 3c7a9c34fc..2674172ddf 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -27,7 +27,7 @@ #define FEATURE_ARDUINO_OTA 1 // enables the Arduino OTA capabilities #define FEATURE_THINGSPEAK_EVENT 0 // Generates an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) #define FEATURE_OPENMETEO_EVENT 0 // Generates an event with the response of a open-meteo request (https://open-meteo.com/en/docs) -#define FEATURE_INVERTER_EVENT 0 // Generates an event with the response of an inverter example URL: http://192.168.1.199/solar_api/v1/GetInverterRealtimeData.cgi?Scope=System +#define FEATURE_JSON_EVENT 0 // Generates an event with the with the values of a JSON repsonse of an HTTP call. Keys are stored in json.keys one key per line (e.g.: Body.Data.DAY_ENERGY.Values.1) // #define FEATURE_SD 1 // Enable SD card support // #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 46243076c3..016b8b65fc 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3636,11 +3636,11 @@ To create/register a plugin, you have to : #endif #endif - #ifndef FEATURE_INVERTER_EVENT - #define FEATURE_INVERTER_EVENT 0 + #ifndef FEATURE_JSON_EVENT + #define FEATURE_JSON_EVENT 0 #endif - #if FEATURE_THINGSPEAK_EVENT || FEATURE_OPENMETEO_EVENT || FEATURE_INVERTER_EVENT + #if FEATURE_THINGSPEAK_EVENT || FEATURE_OPENMETEO_EVENT || FEATURE_JSON_EVENT #define RESPONSE_PARSER_SUPPORT 1 #else #define RESPONSE_PARSER_SUPPORT 0 diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index a0dd2395c6..9e9fda22f0 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -1,243 +1,337 @@ -#include "../Helpers/Networking.h" -#include "../Globals/EventQueue.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringProvider.h" +#include "../Helpers/HTTPResponseParser.h" + +#if RESPONSE_PARSER_SUPPORT +# include "../Helpers/Networking.h" +# include "../../ESPEasy_common.h" +# include "../../_Plugin_Helper.h" +# include "../Globals/EventQueue.h" +# include "../Helpers/StringConverter.h" +# include "../Helpers/StringProvider.h" + +#if FEATURE_JSON_EVENT +# include "../Helpers/ESPEasy_Storage.h" +# include "../WebServer/LoadFromFS.h" + +# include +#endif // if FEATURE_JSON_EVENT -#include void eventFromResponse(const String& host, const int& httpCode, const String& uri, HTTPClient& http) { - - // -------------------------------------------------------------------------------------------Thingspeak -#if FEATURE_THINGSPEAK_EVENT - - // Generate event with the response of a - // thingspeak request (https://de.mathworks.com/help/thingspeak/readlastfieldentry.html & - // https://de.mathworks.com/help/thingspeak/readdata.html) - // e.g. command for a specific field: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/fields/5/last.csv" - // command for all fields: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/feeds/last.csv" - // where first eventvalue is the channel number and the second to the nineth event values - // are the field values - // Example of the event: "EVENT: ThingspeakReply=1637928,5,24.2,12,900,..." - // ^ ^ └------┬------┘ - // channel number ┘ | └ received values - // field number (only available for a "single-value-event") - // In rules you can grep the reply by "On ThingspeakReply Do ..." - // ----------------------------------------------------------------------------------------------------------------------------- - // 2024-02-05 - Added the option to get a single value of a field or all values of a channel at a certain time (not only the last entry) - // Examples: - // Single channel: "sendtohttp,api.thingspeak.com,80,channels/1637928/fields/1.csv?end=2024-01-01%2023:59:00&results=1" - // => gets the value of field 1 at (or the last entry before) 23:59:00 of the channel 1637928 - // All channels: "sendtohttp,api.thingspeak.com,80,channels/1637928/feeds.csv?end=2024-01-01%2023:59:00&results=1" - // => gets the value of each field of the channel 1637928 at (or the last entry before) 23:59:00 - // ----------------------------------------------------------------------------------------------------------------------------- - - if ((httpCode == 200) && equals(host, F("api.thingspeak.com")) && - (uri.endsWith(F("/last.csv")) || ((uri.indexOf(F("results=1")) >= 0) && (uri.indexOf(F(".csv")) >= 0)))) { - String result = http.getString(); - - result.replace(' ', '_'); // if using a single field with a certain time, the result contains a space and would break the code - const int posTimestamp = result.lastIndexOf(':'); - - if (posTimestamp >= 0) { - result = parseStringToEndKeepCase(result.substring(posTimestamp), 3); - - if (uri.indexOf(F("fields")) >= 0) { // when there is a single field call add the field number - // before the value - result = parseStringKeepCase(uri, 4, '/').substring(0, 1) + "," + result; // since the field number is always the fourth part of the - // url and is always a single digit, we can use this to - // extact the fieldnumber + if ((httpCode == 200)) { + // -------------------------------------------------------------------------------------------Thingspeak + # if FEATURE_THINGSPEAK_EVENT + + // Generate event with the response of a + // thingspeak request (https://de.mathworks.com/help/thingspeak/readlastfieldentry.html & + // https://de.mathworks.com/help/thingspeak/readdata.html) + // e.g. command for a specific field: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/fields/5/last.csv" + // command for all fields: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/feeds/last.csv" + // where first eventvalue is the channel number and the second to the nineth event values + // are the field values + // Example of the event: "EVENT: ThingspeakReply=1637928,5,24.2,12,900,..." + // ^ ^ └------┬------┘ + // channel number ┘ | └ received values + // field number (only available for a "single-value-event") + // In rules you can grep the reply by "On ThingspeakReply Do ..." + // ----------------------------------------------------------------------------------------------------------------------------- + // 2024-02-05 - Added the option to get a single value of a field or all values of a channel at a certain time (not only the last entry) + // Examples: + // Single channel: "sendtohttp,api.thingspeak.com,80,channels/1637928/fields/1.csv?end=2024-01-01%2023:59:00&results=1" + // => gets the value of field 1 at (or the last entry before) 23:59:00 of the channel 1637928 + // All channels: "sendtohttp,api.thingspeak.com,80,channels/1637928/feeds.csv?end=2024-01-01%2023:59:00&results=1" + // => gets the value of each field of the channel 1637928 at (or the last entry before) 23:59:00 + // ----------------------------------------------------------------------------------------------------------------------------- + + if (equals(host, F("api.thingspeak.com")) && + (uri.endsWith(F("/last.csv")) || ((uri.indexOf(F("results=1")) >= 0) && (uri.indexOf(F(".csv")) >= 0)))) { + String result = http.getString(); + + result.replace(' ', '_'); // if using a single field with a certain time, the result contains a space and would break the code + const int posTimestamp = result.lastIndexOf(':'); + + if (posTimestamp >= 0) { + result = parseStringToEndKeepCase(result.substring(posTimestamp), 3); + + if (uri.indexOf(F("fields")) >= 0) { // when there is a single field call add the field + // number + // before the value + result = parseStringKeepCase(uri, 4, '/').substring(0, 1) + "," + result; // since the field number is always the fourth part of + // the + // url and is always a single digit, we can use this to + // extact the fieldnumber + } + eventQueue.addMove(strformat( + F("ThingspeakReply=%s,%s"), + parseStringKeepCase(uri, 2, '/').c_str(), + result.c_str())); } - eventQueue.addMove(strformat( - F("ThingspeakReply=%s,%s"), - parseStringKeepCase(uri, 2, '/').c_str(), - result.c_str())); - } - } -#endif // if FEATURE_THINGSPEAK_EVENT - - // ------------------------------------------------------------------------------------------- OpenMeteo -#if FEATURE_OPENMETEO_EVENT - - // Generate an event with the response of an open-meteo request. - // Example command: - // sendtohttp,api.open-meteo.com,80,"/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1" - // No need for an api key and it is free (daily requests are limited to 10,000 in the free version) - // Visit the URL (https://open-meteo.com/en/docs) and build your personal URL by selecting the location and values you want to receive. - // Supported variable kinds are current, hourly, daily! - // In rules you can grep the reply by the kind of weather variables with "On Openmeteo# Do ..." - // e.g. "On Openmeteo#current Do ..." - // Note: hourly and daily results are arrays which can become very long. - // Best to make seperate calls. Especially for hourly results. - - // define limits -# define WEATHER_KEYS_MAX 10 -# define URI_MAX_LENGTH 5000 - - if ((httpCode == 200) && equals(host, F("api.open-meteo.com"))) { - const String str = http.getString(); - - if (str.length() > URI_MAX_LENGTH) { - addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); } + # endif // if FEATURE_THINGSPEAK_EVENT + + // ------------------------------------------------------------------------------------------- OpenMeteo + # if FEATURE_OPENMETEO_EVENT + + // Generate an event with the response of an open-meteo request. + // Example command: + // sendtohttp,api.open-meteo.com,80,"/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,weather_code,wind_speed_10m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1" + // No need for an api key and it is free (daily requests are limited to 10,000 in the free version) + // Visit the URL (https://open-meteo.com/en/docs) and build your personal URL by selecting the location and values you want to receive. + // Supported variable kinds are current, hourly, daily! + // In rules you can grep the reply by the kind of weather variables with "On Openmeteo# Do ..." + // e.g. "On Openmeteo#current Do ..." + // Note: hourly and daily results are arrays which can become very long. + // Best to make seperate calls. Especially for hourly results. + + // define limits + # define WEATHER_KEYS_MAX 10 + + if (equals(host, F("api.open-meteo.com"))) { + const String str = http.getString(); + + if (str.length() > URI_MAX_LENGTH) { + addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); + } - auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { - // Extract the parameters from the URL - int start = url.indexOf(eventName + '='); - - if (start == -1) { - return; // No parameters found for the given eventName - } - start += eventName.length() + 1; - const int end = url.indexOf('&', start); - const String params = (end == -1) ? url.substring(start) : url.substring(start, end); - - if (!params.isEmpty()) { - String keys[WEATHER_KEYS_MAX]; - int keyCount = 0; - int startIndex = 0; - int commaIndex = params.indexOf(','); - - // Split and add keys to the array - while (commaIndex != -1) { - if (keyCount >= WEATHER_KEYS_MAX) { - // Stop adding keys if array is full - addLog(LOG_LEVEL_ERROR, - strformat(F( - "More than %d keys in the URL, this could cause instabilities or crashes! Try to split up the calls.."), - WEATHER_KEYS_MAX)); - break; - } - String key = params.substring(startIndex, commaIndex); - keys[keyCount++] = key; - startIndex = commaIndex + 1; - commaIndex = params.indexOf(',', startIndex); - } + auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { + // Extract the parameters from the URL + int start = url.indexOf(eventName + '='); - // Add the last key - if (keyCount < WEATHER_KEYS_MAX) { - const String lastKey = params.substring(startIndex); - keys[keyCount++] = lastKey; + if (start == -1) { + return; // No parameters found for the given eventName } + start += eventName.length() + 1; + const int end = url.indexOf('&', start); + const String params = (end == -1) ? url.substring(start) : url.substring(start, end); + + if (!params.isEmpty()) { + String keys[WEATHER_KEYS_MAX]; + int keyCount = 0; + int startIndex = 0; + int commaIndex = params.indexOf(','); + + // Split and add keys to the array + while (commaIndex != -1) { + if (keyCount >= WEATHER_KEYS_MAX) { + // Stop adding keys if array is full + addLog(LOG_LEVEL_ERROR, + strformat(F( + "More than %d keys in the URL, this could cause instabilities or crashes! Try to split up the calls.."), + WEATHER_KEYS_MAX)); + break; + } + String key = params.substring(startIndex, commaIndex); + keys[keyCount++] = key; + startIndex = commaIndex + 1; + commaIndex = params.indexOf(',', startIndex); + } - String csv; - const int startStringIndex = str.indexOf(strformat(F("\"%s\":"), - eventName.c_str())) + - eventName.length() + 4; + // Add the last key + if (keyCount < WEATHER_KEYS_MAX) { + const String lastKey = params.substring(startIndex); + keys[keyCount++] = lastKey; + } - // example( },"current":{"time":... ) we want to start after current":{ + String csv; + const int startStringIndex = str.indexOf(strformat(F("\"%s\":"), + eventName.c_str())) + + eventName.length() + 4; - const int endStringIndex = str.indexOf('}', startStringIndex); + // example( },"current":{"time":... ) we want to start after current":{ - // ...and want to end before } + const int endStringIndex = str.indexOf('}', startStringIndex); - for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration - { - String key = keys[i]; - String value; - int startIndex = str.indexOf(strformat(F("%s\":"), key.c_str()), startStringIndex); + // ...and want to end before } - if (startIndex == -1) { - // Handle case where key is not found - value = F("-256"); // Placeholder value - } else { - int endIndex = 0; + for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration + { + String key = keys[i]; + String value; + int startIndex = str.indexOf(strformat(F("%s\":"), key.c_str()), startStringIndex); - if (!equals(eventName, F("current"))) { - // In daily and hourly, the values are stored in an array - startIndex += key.length() + 3; // Move index past the key - endIndex = str.indexOf(']', startIndex); + if (startIndex == -1) { + // Handle case where key is not found + value = F("-256"); // Placeholder value } else { - startIndex += key.length() + 2; // Move index past the key - endIndex = str.indexOf(',', startIndex); + int endIndex = 0; + + if (!equals(eventName, F("current"))) { + // In daily and hourly, the values are stored in an array + startIndex += key.length() + 3; // Move index past the key + endIndex = str.indexOf(']', startIndex); + } else { + startIndex += key.length() + 2; // Move index past the key + endIndex = str.indexOf(',', startIndex); + } + + // Find the index of the next comma + if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) { + endIndex = str.indexOf('}', startIndex); // If no comma is found or comma comes after }, + // take the rest of the string + } + + value = str.substring(startIndex, endIndex); + value.trim(); // Remove any surrounding whitespace } - // Find the index of the next comma - if ((endIndex == -1) || (endIndex > str.indexOf("}", startIndex))) { - endIndex = str.indexOf('}', startIndex); // If no comma is found or comma comes after }, - // take the rest of the string + if (!csv.isEmpty()) { + csv += ','; } - - value = str.substring(startIndex, endIndex); - value.trim(); // Remove any surrounding whitespace + csv += value; } - - if (!csv.isEmpty()) { - csv += ','; - } - csv += value; + eventQueue.addMove(strformat(F("OpenMeteo#%s=%s"), eventName.c_str(), csv.c_str())); } - eventQueue.addMove(strformat(F("OpenMeteo#%s=%s"), eventName.c_str(), csv.c_str())); - } - }; + }; + + processAndQueueParams(uri, str, F("current")); + processAndQueueParams(uri, str, F("hourly")); + processAndQueueParams(uri, str, F("daily")); + } + + # endif // if FEATURE_OMETEO_EVENT + + // ------------------------------------------------------------------------------------------- JSONevent + # if FEATURE_JSON_EVENT + + if (uri.endsWith(F("&json"))) { + const String message = http.getString(); + + if (message.length() > URI_MAX_LENGTH) { + addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); + } + + DynamicJsonDocument*root = nullptr; + uint16_t lastJsonMessageLength = 512; + + // Cleanup lambda to deallocate resources + auto cleanupJSON = [&root]() { + if (root != nullptr) { + root->clear(); + delete root; + root = nullptr; + } + }; + + // Check if root needs resizing or cleanup + if ((root != nullptr) && (message.length() * 1.5 > lastJsonMessageLength)) { + cleanupJSON(); + } + + // Resize lastJsonMessageLength if needed + if (message.length() * 1.5 > lastJsonMessageLength) { + lastJsonMessageLength = message.length() * 1.5; + } - processAndQueueParams(uri, str, F("current")); - processAndQueueParams(uri, str, F("hourly")); - processAndQueueParams(uri, str, F("daily")); + // Allocate memory for root if needed + if (root == nullptr) { + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + root = new (std::nothrow) DynamicJsonDocument(lastJsonMessageLength); + } + + if (root != nullptr) { + // Parse the JSON + DeserializationError error = deserializeJson(*root, message); + + if (!error) { + // Process the keys from the file + readAndProcessJsonKeys(root); + } else { + Serial.println("Failed to parse JSON"); + } + + // Cleanup JSON resources + cleanupJSON(); + } + } + # endif // if FEATURE_JSON_EVENT } +} -#endif // if FEATURE_OMETEO_EVENT +// ------------------------------------------------------------------------------------------- JSONevent Key processing + # if FEATURE_JSON_EVENT +void readAndProcessJsonKeys(DynamicJsonDocument*root) { + // Open the `json.keys` file + const String fileName = strformat( + # ifdef ESP8266 + F("json.keys") + # else // ifdef ESP8266 + F("/json.keys") + # endif // ifdef ESP8266 + ); + + if (!fileExists(fileName)) { + addLogMove(LOG_LEVEL_ERROR, strformat(F("%s does not exist!"), fileName.c_str())); + return; + } + File keyFile = ESPEASY_FS.open(fileName, "r"); - // ------------------------------------------------------------------------------------------- Inverter -#if FEATURE_INVERTER_EVENT + int keyCount = 0; + int successfullyProcessedCount = 0; // Counter for successfully processed keys + String csvOutput; // Collect values as a CSV string - if ((httpCode == 200) && (uri.endsWith(F("GetInverterRealtimeData.cgi?Scope=System")))) - { - DynamicJsonDocument *root = nullptr; - uint16_t lastJsonMessageLength = 512; + while (keyFile.available()) { + String key = keyFile.readStringUntil('\n'); + key.trim(); // Remove extra spaces or newlines - const String message = http.getString(); + if (key.isEmpty()) { + continue; + } - // Cleanup lambda to deallocate resources - auto cleanupJSON = [&root]() { - if (root != nullptr) { - root->clear(); - delete root; - root = nullptr; - } - }; + keyCount++; - // Check if root needs resizing or cleanup - if ((root != nullptr) && (message.length() * 1.5 > lastJsonMessageLength)) { - cleanupJSON(); + if (keyCount > MAX_KEYS) { + addLogMove(LOG_LEVEL_ERROR, strformat(F("Warning: More than %d keys in %s"), MAX_KEYS, fileName.c_str())); } - // Resize lastJsonMessageLength if needed - if (message.length() * 1.5 > lastJsonMessageLength) { - lastJsonMessageLength = message.length() * 1.5; + // Process the key and navigate the JSON + JsonVariant value = *root; + size_t start = 0, end; + + while ((end = key.indexOf('.', start)) != -1) { + String part = key.substring(start, end); + value = value[part]; + + if (value.isNull()) { + break; // Key path is invalid + } + start = end + 1; } - // Allocate memory for root if needed - if (root == nullptr) { -# ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; -# endif // ifdef USE_SECOND_HEAP - root = new (std::nothrow) DynamicJsonDocument(lastJsonMessageLength); // Dynamic allocation + // Handle the last part of the key + if (!value.isNull()) { + String lastPart = key.substring(start); + value = value[lastPart]; + successfullyProcessedCount++; } - if (root != nullptr) { - // Parse the JSON - DeserializationError error = deserializeJson(*root, message); - - if (!error) { - int dayEnergy = (*root)["Body"]["Data"]["DAY_ENERGY"]["Values"]["1"].as(); - int pac = (*root)["Body"]["Data"]["PAC"]["Values"]["1"].as(); - int totalEnergy = (*root)["Body"]["Data"]["TOTAL_ENERGY"]["Values"]["1"].as(); - int yearEnergy = (*root)["Body"]["Data"]["YEAR_ENERGY"]["Values"]["1"].as(); - - addLog(LOG_LEVEL_INFO, strformat(F("Inverter: Day Energy: %d, PAC: %d, Total Energy: %d, Year Energy: %d"), - dayEnergy, - pac, - totalEnergy, - yearEnergy)); - eventQueue.addMove(strformat(F("InverterReply=%d,%d,%d,%d"), dayEnergy, pac, totalEnergy, yearEnergy)); + // 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 += String(value.as()); + } else if (value.is()) { + csvOutput += String(value.as()); } else { - Serial.println("Failed to parse JSON"); + csvOutput += "unknown"; } + } else { + csvOutput += "null"; // Indicate missing value + } - // Cleanup JSON resources - cleanupJSON(); + // Add a comma unless it's the last key + if (keyFile.peek() != -1) { + csvOutput += ","; } } -#endif // ifdef FEATURE_INVERTER_EVENT + keyFile.close(); + + // Log the results + addLog(LOG_LEVEL_INFO, strformat(F("Successfully processed %d out of %d keys"), successfullyProcessedCount, keyCount)); + eventQueue.addMove(strformat(F("JsonReply=%s"), csvOutput.c_str())); } + + # endif // if FEATURE_JSON_EVENT +#endif // if RESPONSE_PARSER_SUPPORT diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index a23adbc5be..6a0d5686b3 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -3,16 +3,28 @@ #include "../../ESPEasy_common.h" #include "../../_Plugin_Helper.h" +#include "../Helpers/Networking.h" -#ifdef PLUGIN_BUILD_MINIMAL_OTA +#include + +#ifdef ESP8266 # include -#endif // ifdef ESP8266 +#endif // ifdef ESP8266 # + +#define MAX_KEYS 20 // Maximum number of keys allowed in json.keys +#define URI_MAX_LENGTH 5000 +/** + * @brief Reads and processes keys from a json.keys file and navigates the JSON document. + */ +void readAndProcessJsonKeys(DynamicJsonDocument*root); -// Function declarations +/** + * @brief Processes the response of an HTTP request, extracts the JSON, and processes it using keys from `json.keys`. + */ void eventFromResponse(const String& host, const int & httpCode, const String& uri, HTTPClient & http); -#endif // ifndef HELPERS_HTTPRESPONSEPARSER_H +#endif // HELPERS_HTTPRESPONSEPARSER_H From 171cbebbefba71397f04f13f7bdab71767e0518b Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:09:40 +0100 Subject: [PATCH 14/24] Update HTTPResponseParser.h --- src/src/Helpers/HTTPResponseParser.h | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index 6a0d5686b3..12e8275a3f 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -3,16 +3,18 @@ #include "../../ESPEasy_common.h" #include "../../_Plugin_Helper.h" -#include "../Helpers/Networking.h" -#include +#if RESPONSE_PARSER_SUPPORT +# include "../Helpers/Networking.h" -#ifdef ESP8266 -# include -#endif // ifdef ESP8266 # +# include -#define MAX_KEYS 20 // Maximum number of keys allowed in json.keys -#define URI_MAX_LENGTH 5000 +# ifdef ESP8266 +# include +# endif // ifdef ESP8266 # + +# define MAX_KEYS 20 // Maximum number of keys allowed in json.keys +# define URI_MAX_LENGTH 5000 /** * @brief Reads and processes keys from a json.keys file and navigates the JSON document. @@ -26,5 +28,5 @@ void eventFromResponse(const String& host, const int & httpCode, const String& uri, HTTPClient & http); - +#endif // RESPONSE_PARSER_SUPPORT #endif // HELPERS_HTTPRESPONSEPARSER_H From 6d2215b44f2fc98ab40fdeb37b9a195723b91955 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:52:57 +0100 Subject: [PATCH 15/24] added variable decimals for floating point numbers + fix typo --- src/src/Helpers/HTTPResponseParser.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 9e9fda22f0..7982c46b18 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -8,12 +8,12 @@ # include "../Helpers/StringConverter.h" # include "../Helpers/StringProvider.h" -#if FEATURE_JSON_EVENT -# include "../Helpers/ESPEasy_Storage.h" -# include "../WebServer/LoadFromFS.h" +# if FEATURE_JSON_EVENT +# include "../Helpers/ESPEasy_Storage.h" +# include "../WebServer/LoadFromFS.h" -# include -#endif // if FEATURE_JSON_EVENT +# include +# endif // if FEATURE_JSON_EVENT void eventFromResponse(const String& host, const int& httpCode, const String& uri, HTTPClient& http) { @@ -192,7 +192,7 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // ------------------------------------------------------------------------------------------- JSONevent # if FEATURE_JSON_EVENT - if (uri.endsWith(F("&json"))) { + if (uri.endsWith(F("?json"))) { const String message = http.getString(); if (message.length() > URI_MAX_LENGTH) { @@ -310,7 +310,18 @@ void readAndProcessJsonKeys(DynamicJsonDocument*root) { if (value.is()) { csvOutput += String(value.as()); } else if (value.is()) { - csvOutput += String(value.as()); + csvOutput += String(value.as(), 16); + + // remove trailing zeros and decimal point + if (csvOutput.lastIndexOf('.') != -1) { + while (csvOutput.endsWith("0")) { + csvOutput.remove(csvOutput.length() - 1); + } + + if (csvOutput.endsWith(".")) { + csvOutput.remove(csvOutput.length() - 1); + } + } } else if (value.is()) { csvOutput += String(value.as()); } else { From fe8786fab533d07cecf13a25bbf111c373a7a6d5 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Sat, 4 Jan 2025 20:09:12 +0100 Subject: [PATCH 16/24] some changes to the json-event - add array output to the json parser - add grouping of keys for individual requests --- src/Custom-sample.h | 2 +- src/src/Helpers/HTTPResponseParser.cpp | 88 +++++++++++++++++++++----- src/src/Helpers/HTTPResponseParser.h | 8 ++- 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 2674172ddf..24340c457b 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -27,7 +27,7 @@ #define FEATURE_ARDUINO_OTA 1 // enables the Arduino OTA capabilities #define FEATURE_THINGSPEAK_EVENT 0 // Generates an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) #define FEATURE_OPENMETEO_EVENT 0 // Generates an event with the response of a open-meteo request (https://open-meteo.com/en/docs) -#define FEATURE_JSON_EVENT 0 // Generates an event with the with the values of a JSON repsonse of an HTTP call. Keys are stored in json.keys one key per line (e.g.: Body.Data.DAY_ENERGY.Values.1) +#define FEATURE_JSON_EVENT 0 // Generates an event with the values of a JSON repsonse of an HTTP call. Keys are stored in json.keys one key per line (e.g.: Body.Data.DAY_ENERGY.Values.1) // #define FEATURE_SD 1 // Enable SD card support // #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 7982c46b18..35afd858c0 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -192,7 +192,22 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // ------------------------------------------------------------------------------------------- JSONevent # if FEATURE_JSON_EVENT - if (uri.endsWith(F("?json"))) { + if ((uri.indexOf(F("#json")) != -1) || (uri.indexOf(F("?json")) != -1)) { + int numJson = 0; // Default value + + // Check if the URL ends with a number + if ((uri.length() > 0) && isDigit(uri.charAt(uri.length() - 1))) { + // Find the position of the last non-digit character + int i = uri.length() - 1; + + while (i >= 0 && isDigit(uri.charAt(i))) { + i--; + } + + // Extract the number from the string + numJson = uri.substring(i + 1).toInt(); + } + const String message = http.getString(); if (message.length() > URI_MAX_LENGTH) { @@ -235,9 +250,9 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur if (!error) { // Process the keys from the file - readAndProcessJsonKeys(root); + readAndProcessJsonKeys(root, numJson); } else { - Serial.println("Failed to parse JSON"); + addLog(LOG_LEVEL_INFO, strformat(F("Parsing JSON failed"))); } // Cleanup JSON resources @@ -250,7 +265,20 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // ------------------------------------------------------------------------------------------- JSONevent Key processing # if FEATURE_JSON_EVENT -void readAndProcessJsonKeys(DynamicJsonDocument*root) { +void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { + // function to clean up float values + auto cleanUpFloat = [](String& valueStr) { + if (valueStr.lastIndexOf('.') != -1) { + while (valueStr.endsWith("0")) { + valueStr.remove(valueStr.length() - 1); + } + + if (valueStr.endsWith(".")) { + valueStr.remove(valueStr.length() - 1); + } + } + }; + // Open the `json.keys` file const String fileName = strformat( # ifdef ESP8266 @@ -269,6 +297,7 @@ void readAndProcessJsonKeys(DynamicJsonDocument*root) { int keyCount = 0; int successfullyProcessedCount = 0; // Counter for successfully processed keys String csvOutput; // Collect values as a CSV string + bool firstValue = true; // track the first value while (keyFile.available()) { String key = keyFile.readStringUntil('\n'); @@ -277,18 +306,32 @@ void readAndProcessJsonKeys(DynamicJsonDocument*root) { 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) { addLogMove(LOG_LEVEL_ERROR, strformat(F("Warning: More than %d keys in %s"), MAX_KEYS, fileName.c_str())); } + // addLog(LOG_LEVEL_INFO, strformat(F("Parsing key: %s"), key.c_str())); + // Process the key and navigate the JSON JsonVariant value = *root; size_t start = 0, end; - while ((end = key.indexOf('.', start)) != -1) { + while ((end = key.indexOf('.', start)) != (unsigned int)-1) { String part = key.substring(start, end); value = value[part]; @@ -310,20 +353,30 @@ void readAndProcessJsonKeys(DynamicJsonDocument*root) { if (value.is()) { csvOutput += String(value.as()); } else if (value.is()) { - csvOutput += String(value.as(), 16); + csvOutput += String(value.as(), decimals); + cleanUpFloat(csvOutput); + } 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(); - // remove trailing zeros and decimal point - if (csvOutput.lastIndexOf('.') != -1) { - while (csvOutput.endsWith("0")) { - csvOutput.remove(csvOutput.length() - 1); + for (JsonVariant element : array) { + if (!csvOutput.isEmpty()) { + csvOutput += ","; // Add a separator between array elements } - if (csvOutput.endsWith(".")) { - csvOutput.remove(csvOutput.length() - 1); + if (element.is()) { + csvOutput += String(element.as()); + } else if (element.is()) { + csvOutput += String(element.as(), decimals); + cleanUpFloat(csvOutput); + } else if (element.is()) { + csvOutput += String(element.as()); + } else { + csvOutput += "unknown"; } } - } else if (value.is()) { - csvOutput += String(value.as()); } else { csvOutput += "unknown"; } @@ -333,7 +386,10 @@ void readAndProcessJsonKeys(DynamicJsonDocument*root) { // Add a comma unless it's the last key if (keyFile.peek() != -1) { - csvOutput += ","; + if (!firstValue) { + csvOutput += ","; // Add a comma before the value if it's not the first + } + firstValue = false; // After the first value, set it to false } } @@ -341,7 +397,7 @@ void readAndProcessJsonKeys(DynamicJsonDocument*root) { // Log the results addLog(LOG_LEVEL_INFO, strformat(F("Successfully processed %d out of %d keys"), successfullyProcessedCount, keyCount)); - eventQueue.addMove(strformat(F("JsonReply=%s"), csvOutput.c_str())); + eventQueue.addMove(strformat(F("JsonReply%s=%s"), (numJson != 0) ? String(numJson).c_str() : "", csvOutput.c_str())); } # endif // if FEATURE_JSON_EVENT diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index 12e8275a3f..46660a825e 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -15,11 +15,17 @@ # define MAX_KEYS 20 // Maximum number of keys allowed in json.keys # define URI_MAX_LENGTH 5000 +# ifdef ESP32 +int decimals = 16; +# else // if ESPEASY_RULES_FLOAT_TYPE double +int decimals = 6; +# endif // if ESPEASY_RULES_FLOAT_TYPE double /** * @brief Reads and processes keys from a json.keys file and navigates the JSON document. */ -void readAndProcessJsonKeys(DynamicJsonDocument*root); +void readAndProcessJsonKeys(DynamicJsonDocument*root, + int numJson); /** * @brief Processes the response of an HTTP request, extracts the JSON, and processes it using keys from `json.keys`. From 1500f3b1515a2b33ac125578f81a1a33a5ac8adf Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Sat, 4 Jan 2025 20:45:33 +0100 Subject: [PATCH 17/24] made the eventQueue even more complex :P --- src/src/Helpers/HTTPResponseParser.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 35afd858c0..5dbfae3cf7 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -252,7 +252,7 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // Process the keys from the file readAndProcessJsonKeys(root, numJson); } else { - addLog(LOG_LEVEL_INFO, strformat(F("Parsing JSON failed"))); + addLog(LOG_LEVEL_INFO, F("Parsing JSON failed")); } // Cleanup JSON resources @@ -397,7 +397,10 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { // Log the results addLog(LOG_LEVEL_INFO, strformat(F("Successfully processed %d out of %d keys"), successfullyProcessedCount, keyCount)); - eventQueue.addMove(strformat(F("JsonReply%s=%s"), (numJson != 0) ? String(numJson).c_str() : "", csvOutput.c_str())); + eventQueue.addMove(strformat(F("JsonReply%s%s=%s"), + (numJson != 0) ? "#" : "", + (numJson != 0) ? String(numJson).c_str() : "", + csvOutput.c_str())); } # endif // if FEATURE_JSON_EVENT From 0bf7b6607bbcd84a680fc042b9d46cc6bc69c68e Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:49:04 +0100 Subject: [PATCH 18/24] minor changes -changed variable name for the decimals to decimalsJP --- src/src/Helpers/HTTPResponseParser.cpp | 29 +++++++++++++------------- src/src/Helpers/HTTPResponseParser.h | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 5dbfae3cf7..32ef6042e6 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -297,7 +297,6 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { int keyCount = 0; int successfullyProcessedCount = 0; // Counter for successfully processed keys String csvOutput; // Collect values as a CSV string - bool firstValue = true; // track the first value while (keyFile.available()) { String key = keyFile.readStringUntil('\n'); @@ -353,29 +352,34 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { if (value.is()) { csvOutput += String(value.as()); } else if (value.is()) { - csvOutput += String(value.as(), decimals); + csvOutput += String(value.as(), decimalsJP); cleanUpFloat(csvOutput); } 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(); + 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 (!csvOutput.isEmpty()) { - csvOutput += ","; // Add a separator between array elements - } - if (element.is()) { csvOutput += String(element.as()); } else if (element.is()) { - csvOutput += String(element.as(), decimals); + csvOutput += String(element.as(), decimalsJP); cleanUpFloat(csvOutput); } else if (element.is()) { csvOutput += String(element.as()); } else { csvOutput += "unknown"; } + + // Add a comma unless it's the last element + currentIndex++; + + if (currentIndex < arraySize) { + csvOutput += ","; + } } } else { csvOutput += "unknown"; @@ -386,10 +390,7 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { // Add a comma unless it's the last key if (keyFile.peek() != -1) { - if (!firstValue) { - csvOutput += ","; // Add a comma before the value if it's not the first - } - firstValue = false; // After the first value, set it to false + csvOutput += ","; // Add a comma after the value } } @@ -398,8 +399,8 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { // Log the results 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) ? "#" : "", - (numJson != 0) ? String(numJson).c_str() : "", + numJson != 0 ? "#" : "", + toStringNoZero(numJson).c_str(), csvOutput.c_str())); } diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index 46660a825e..f7eac74a2f 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -16,9 +16,9 @@ # define MAX_KEYS 20 // Maximum number of keys allowed in json.keys # define URI_MAX_LENGTH 5000 # ifdef ESP32 -int decimals = 16; +int decimalsJP = 16; # else // if ESPEASY_RULES_FLOAT_TYPE double -int decimals = 6; +int decimalsJP = 7; # endif // if ESPEASY_RULES_FLOAT_TYPE double /** From c7a23c2bc044e5c3a791106e42d164dca644b3b4 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:00:46 +0100 Subject: [PATCH 19/24] moved variable to cpp file --- src/src/Helpers/HTTPResponseParser.cpp | 11 +++++++---- src/src/Helpers/HTTPResponseParser.h | 5 ----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 32ef6042e6..adad145ff6 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -138,10 +138,6 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // example( },"current":{"time":... ) we want to start after current":{ - const int endStringIndex = str.indexOf('}', startStringIndex); - - // ...and want to end before } - for (int i = 0; i < keyCount; i++) // Use keyCount to limit the iteration { String key = keys[i]; @@ -265,7 +261,14 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // ------------------------------------------------------------------------------------------- JSONevent Key processing # if FEATURE_JSON_EVENT + void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { +# ifdef ESP32 + int decimalsJP = 16; +# else // if ESPEASY_RULES_FLOAT_TYPE double + int decimalsJP = 7; +# endif // if ESPEASY_RULES_FLOAT_TYPE double + // function to clean up float values auto cleanUpFloat = [](String& valueStr) { if (valueStr.lastIndexOf('.') != -1) { diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index f7eac74a2f..b74570ba8f 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -15,11 +15,6 @@ # define MAX_KEYS 20 // Maximum number of keys allowed in json.keys # define URI_MAX_LENGTH 5000 -# ifdef ESP32 -int decimalsJP = 16; -# else // if ESPEASY_RULES_FLOAT_TYPE double -int decimalsJP = 7; -# endif // if ESPEASY_RULES_FLOAT_TYPE double /** * @brief Reads and processes keys from a json.keys file and navigates the JSON document. From 2b571a48acb96eadd7881c517f5faad015531506 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:53:43 +0100 Subject: [PATCH 20/24] updates - add: log error for json parsing - change: decimals for float and double reduced by 1 (6,15) - inceased json buffer --- src/src/CustomBuild/define_plugin_sets.h | 2 +- src/src/Helpers/HTTPResponseParser.cpp | 38 +++++++++++++----------- src/src/Helpers/HTTPResponseParser.h | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 016b8b65fc..faf01549d6 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3637,7 +3637,7 @@ To create/register a plugin, you have to : #endif #ifndef FEATURE_JSON_EVENT - #define FEATURE_JSON_EVENT 0 + #define FEATURE_JSON_EVENT 1 #endif #if FEATURE_THINGSPEAK_EVENT || FEATURE_OPENMETEO_EVENT || FEATURE_JSON_EVENT diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index adad145ff6..078b7c7fd9 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -88,8 +88,9 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur if (equals(host, F("api.open-meteo.com"))) { const String str = http.getString(); - if (str.length() > URI_MAX_LENGTH) { - addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); + if (str.length() > RESPONSE_MAX_LENGTH) { + addLog(LOG_LEVEL_ERROR, + strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), RESPONSE_MAX_LENGTH)); } auto processAndQueueParams = [](const String& url, const String& str, const String& eventName) { @@ -206,8 +207,9 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur const String message = http.getString(); - if (message.length() > URI_MAX_LENGTH) { - addLog(LOG_LEVEL_ERROR, strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), URI_MAX_LENGTH)); + if (message.length() > RESPONSE_MAX_LENGTH) { + addLog(LOG_LEVEL_ERROR, + strformat(F("Response exceeds %d characters which could cause instabilities or crashes!"), RESPONSE_MAX_LENGTH)); } DynamicJsonDocument*root = nullptr; @@ -223,13 +225,13 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur }; // Check if root needs resizing or cleanup - if ((root != nullptr) && (message.length() * 1.5 > lastJsonMessageLength)) { + if ((root != nullptr) && (message.length() * 2 > lastJsonMessageLength)) { cleanupJSON(); } // Resize lastJsonMessageLength if needed - if (message.length() * 1.5 > lastJsonMessageLength) { - lastJsonMessageLength = message.length() * 1.5; + if (message.length() * 2 > lastJsonMessageLength) { + lastJsonMessageLength = message.length() * 2; } // Allocate memory for root if needed @@ -248,7 +250,7 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur // Process the keys from the file readAndProcessJsonKeys(root, numJson); } else { - addLog(LOG_LEVEL_INFO, F("Parsing JSON failed")); + addLog(LOG_LEVEL_ERROR, strformat(F("Parsing JSON failed: %s"), error.c_str())); } // Cleanup JSON resources @@ -263,11 +265,13 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur # if FEATURE_JSON_EVENT void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { -# ifdef ESP32 - int decimalsJP = 16; -# else // if ESPEASY_RULES_FLOAT_TYPE double - int decimalsJP = 7; -# endif // if ESPEASY_RULES_FLOAT_TYPE double +// # ifdef 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 + +int nr_decimals = ESPEASY_FLOAT_NR_DECIMALS; // function to clean up float values auto cleanUpFloat = [](String& valueStr) { @@ -327,8 +331,6 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { addLogMove(LOG_LEVEL_ERROR, strformat(F("Warning: More than %d keys in %s"), MAX_KEYS, fileName.c_str())); } - // addLog(LOG_LEVEL_INFO, strformat(F("Parsing key: %s"), key.c_str())); - // Process the key and navigate the JSON JsonVariant value = *root; size_t start = 0, end; @@ -355,7 +357,7 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { if (value.is()) { csvOutput += String(value.as()); } else if (value.is()) { - csvOutput += String(value.as(), decimalsJP); + csvOutput += String(value.as(), nr_decimals); cleanUpFloat(csvOutput); } else if (value.is()) { csvOutput += String(value.as()); @@ -369,8 +371,8 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { if (element.is()) { csvOutput += String(element.as()); } else if (element.is()) { - csvOutput += String(element.as(), decimalsJP); - cleanUpFloat(csvOutput); + csvOutput += String(value.as(), nr_decimals); + cleanUpFloat(csvOutput); } else if (element.is()) { csvOutput += String(element.as()); } else { diff --git a/src/src/Helpers/HTTPResponseParser.h b/src/src/Helpers/HTTPResponseParser.h index b74570ba8f..beb097ff60 100644 --- a/src/src/Helpers/HTTPResponseParser.h +++ b/src/src/Helpers/HTTPResponseParser.h @@ -14,7 +14,7 @@ # endif // ifdef ESP8266 # # define MAX_KEYS 20 // Maximum number of keys allowed in json.keys -# define URI_MAX_LENGTH 5000 +# define RESPONSE_MAX_LENGTH 5000 /** * @brief Reads and processes keys from a json.keys file and navigates the JSON document. From 6083741db212a64e7e7698e069bb558084279302 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:15:06 +0100 Subject: [PATCH 21/24] Update HTTPResponseParser.cpp unsing double for double now --- src/src/Helpers/HTTPResponseParser.cpp | 43 ++++++++++++-------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 078b7c7fd9..c02dd24bb4 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -12,7 +12,11 @@ # include "../Helpers/ESPEasy_Storage.h" # include "../WebServer/LoadFromFS.h" +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +# define ARDUINOJSON_USE_DOUBLE 1 +# endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE # include + # endif // if FEATURE_JSON_EVENT @@ -265,26 +269,11 @@ void eventFromResponse(const String& host, const int& httpCode, const String& ur # if FEATURE_JSON_EVENT void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { -// # ifdef 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 - -int nr_decimals = ESPEASY_FLOAT_NR_DECIMALS; - - // function to clean up float values - auto cleanUpFloat = [](String& valueStr) { - if (valueStr.lastIndexOf('.') != -1) { - while (valueStr.endsWith("0")) { - valueStr.remove(valueStr.length() - 1); - } - - if (valueStr.endsWith(".")) { - valueStr.remove(valueStr.length() - 1); - } - } - }; +# 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 const String fileName = strformat( @@ -356,9 +345,12 @@ int nr_decimals = ESPEASY_FLOAT_NR_DECIMALS; if (!value.isNull()) { if (value.is()) { csvOutput += String(value.as()); + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (value.is()) { + csvOutput += doubleToString(value.as(), nr_decimals, 1); + # endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (value.is()) { - csvOutput += String(value.as(), nr_decimals); - cleanUpFloat(csvOutput); + csvOutput += floatToString(value.as(), nr_decimals, 1); } else if (value.is()) { csvOutput += String(value.as()); } else if (value.is()) { @@ -370,9 +362,12 @@ int nr_decimals = ESPEASY_FLOAT_NR_DECIMALS; for (JsonVariant element : array) { if (element.is()) { csvOutput += String(element.as()); + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (element.is()) { + csvOutput += doubleToString(element.as(), nr_decimals, 1); + # endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (element.is()) { - csvOutput += String(value.as(), nr_decimals); - cleanUpFloat(csvOutput); + csvOutput += floatToString(element.as(), nr_decimals, 1); } else if (element.is()) { csvOutput += String(element.as()); } else { From 89a029fff064100519bd90eea35423ec8baf47c2 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:15:41 +0100 Subject: [PATCH 22/24] Update define_plugin_sets.h --- src/src/CustomBuild/define_plugin_sets.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index faf01549d6..016b8b65fc 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3637,7 +3637,7 @@ To create/register a plugin, you have to : #endif #ifndef FEATURE_JSON_EVENT - #define FEATURE_JSON_EVENT 1 + #define FEATURE_JSON_EVENT 0 #endif #if FEATURE_THINGSPEAK_EVENT || FEATURE_OPENMETEO_EVENT || FEATURE_JSON_EVENT From 57569ca410a5b0550ac0f4a0ceaa266ba5149d82 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:32:24 +0100 Subject: [PATCH 23/24] Update HTTPResponseParser.cpp --- src/src/Helpers/HTTPResponseParser.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index c02dd24bb4..9fd1f8a2d7 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -345,12 +345,13 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { if (!value.isNull()) { if (value.is()) { csvOutput += String(value.as()); - # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (value.is()) { csvOutput += doubleToString(value.as(), nr_decimals, 1); - # endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (value.is()) { csvOutput += floatToString(value.as(), nr_decimals, 1); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (value.is()) { csvOutput += String(value.as()); } else if (value.is()) { @@ -365,9 +366,10 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (element.is()) { csvOutput += doubleToString(element.as(), nr_decimals, 1); - # endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (element.is()) { csvOutput += floatToString(element.as(), nr_decimals, 1); + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (element.is()) { csvOutput += String(element.as()); } else { From bd6523af73c8c5e6e0e8bd159d3ddf9806544425 Mon Sep 17 00:00:00 2001 From: chromoxdor <33860956+chromoxdor@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:49:14 +0100 Subject: [PATCH 24/24] Update HTTPResponseParser.cpp - There is no difference between is() and is() as a value check. Keeping -#define ARDUINOJSON_USE_DOUBLE 1 does not seem to be needed (only for serializeJson) --- src/src/Helpers/HTTPResponseParser.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/src/Helpers/HTTPResponseParser.cpp b/src/src/Helpers/HTTPResponseParser.cpp index 9fd1f8a2d7..4781760cfe 100644 --- a/src/src/Helpers/HTTPResponseParser.cpp +++ b/src/src/Helpers/HTTPResponseParser.cpp @@ -12,9 +12,6 @@ # include "../Helpers/ESPEasy_Storage.h" # include "../WebServer/LoadFromFS.h" -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -# define ARDUINOJSON_USE_DOUBLE 1 -# endif // ifdef FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE # include # endif // if FEATURE_JSON_EVENT @@ -345,11 +342,10 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { if (!value.isNull()) { if (value.is()) { csvOutput += String(value.as()); - # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } else if (value.is()) { - csvOutput += doubleToString(value.as(), nr_decimals, 1); - # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (value.is()) { + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + csvOutput += doubleToString(value.as(), nr_decimals, 1); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE csvOutput += floatToString(value.as(), nr_decimals, 1); # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (value.is()) { @@ -363,11 +359,10 @@ void readAndProcessJsonKeys(DynamicJsonDocument *root, int numJson) { for (JsonVariant element : array) { if (element.is()) { csvOutput += String(element.as()); - # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } else if (element.is()) { - csvOutput += doubleToString(element.as(), nr_decimals, 1); - # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (element.is()) { + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + csvOutput += doubleToString(element.as(), nr_decimals, 1); + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE csvOutput += floatToString(element.as(), nr_decimals, 1); # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } else if (element.is()) {