From 4cad6f888f7286e8fb91df1ba71cbe7122eda005 Mon Sep 17 00:00:00 2001 From: Magnus Persson Date: Mon, 14 Mar 2022 18:55:12 +0100 Subject: [PATCH] Added http get option to push --- html/config.htm | 24 +++++++-- html/config.min.htm | 2 +- html/format.htm | 8 ++- html/format.min.htm | 4 +- html/test.htm | 1 + html/test.min.htm | 2 +- src/config.cpp | 4 ++ src/config.hpp | 17 +++++- src/pushtarget.cpp | 84 ++++++++++++++++++++++++++--- src/pushtarget.hpp | 8 +-- src/resources.hpp | 3 ++ src/templating.cpp | 23 +++++++- src/templating.hpp | 15 ++++-- src/webserver.cpp | 16 ++++++ src_docs/source/advanced.rst | 6 +++ src_docs/source/api.rst | 8 +++ src_docs/source/configuration.rst | 14 ++++- src_docs/source/data.rst | 37 ++++++++++--- src_docs/source/images/config2.png | Bin 25874 -> 28382 bytes src_docs/source/releases.rst | 8 +-- test/config.json | 2 + test/format.json | 1 + test/status.json | 1 + 23 files changed, 249 insertions(+), 39 deletions(-) diff --git a/html/config.htm b/html/config.htm index e6e402c..2e8a759 100644 --- a/html/config.htm +++ b/html/config.htm @@ -175,7 +175,7 @@
- +
@@ -184,7 +184,7 @@
- +
@@ -193,8 +193,6 @@
-
-
@@ -204,6 +202,22 @@
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
@@ -617,12 +631,14 @@ function checkHeader(input) { else $("#gravity-format-p").click(); $("#ota-url").val(cfg["ota-url"]); $("#token").val(cfg["token"]); + $("#token2").val(cfg["token2"]); $("#http-push").val(cfg["http-push"]); $("#http-push-h1").val(cfg["http-push-h1"]); $("#http-push-h2").val(cfg["http-push-h2"]); $("#http-push2").val(cfg["http-push2"]); $("#http-push2-h1").val(cfg["http-push2-h1"]); $("#http-push2-h2").val(cfg["http-push2-h2"]); + $("#http-push3").val(cfg["http-push3"]); $("#brewfather-push").val(cfg["brewfather-push"]); $("#influxdb2-push").val(cfg["influxdb2-push"]); $("#influxdb2-org").val(cfg["influxdb2-org"]); diff --git a/html/config.min.htm b/html/config.min.htm index 4c71ec7..cca456a 100644 --- a/html/config.min.htm +++ b/html/config.min.htm @@ -1 +1 @@ -Beer Gravity Monitor

Temperature Format:




Gravity Format:


(C) Copyright 2021-22 Magnus Persson
\ No newline at end of file +Beer Gravity Monitor

Temperature Format:




Gravity Format:


(C) Copyright 2021-22 Magnus Persson
\ No newline at end of file diff --git a/html/format.htm b/html/format.htm index f9b8ce8..4561a76 100644 --- a/html/format.htm +++ b/html/format.htm @@ -74,6 +74,7 @@ + @@ -81,8 +82,9 @@

(C) Copyright 2021-22 Magnus Persson
\ No newline at end of file +Beer Gravity Monitor


(C) Copyright 2021-22 Magnus Persson
\ No newline at end of file diff --git a/html/test.htm b/html/test.htm index f4d7230..b11d6e1 100644 --- a/html/test.htm +++ b/html/test.htm @@ -91,6 +91,7 @@ testHttp( id, "http-1" ); testHttp( id, "http-2" ); + testHttp( id, "http-3" ); testHttp( id, "brewfather" ); testInfluxdb( id ); testMqtt( id ); diff --git a/html/test.min.htm b/html/test.min.htm index 407d881..5c789ea 100644 --- a/html/test.min.htm +++ b/html/test.min.htm @@ -1 +1 @@ -Beer Gravity Monitor


(C) Copyright 2021-22 Magnus Persson
\ No newline at end of file +Beer Gravity Monitor


(C) Copyright 2021-22 Magnus Persson
\ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index fcad595..e6fd4c7 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -74,12 +74,14 @@ void Config::createJson(DynamicJsonDocument& doc) { doc[PARAM_TEMPFORMAT] = String(getTempFormat()); doc[PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl(); doc[PARAM_TOKEN] = getToken(); + doc[PARAM_TOKEN2] = getToken2(); doc[PARAM_PUSH_HTTP] = getHttpUrl(); doc[PARAM_PUSH_HTTP_H1] = getHttpHeader(0); doc[PARAM_PUSH_HTTP_H2] = getHttpHeader(1); doc[PARAM_PUSH_HTTP2] = getHttp2Url(); doc[PARAM_PUSH_HTTP2_H1] = getHttp2Header(0); doc[PARAM_PUSH_HTTP2_H2] = getHttp2Header(1); + doc[PARAM_PUSH_HTTP3] = getHttp3Url(); doc[PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl(); doc[PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg(); doc[PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket(); @@ -215,6 +217,7 @@ bool Config::loadFile() { setBrewfatherPushUrl(doc[PARAM_PUSH_BREWFATHER]); if (!doc[PARAM_TOKEN].isNull()) setToken(doc[PARAM_TOKEN]); + if (!doc[PARAM_TOKEN2].isNull()) setToken2(doc[PARAM_TOKEN2]); if (!doc[PARAM_PUSH_HTTP].isNull()) setHttpUrl(doc[PARAM_PUSH_HTTP]); if (!doc[PARAM_PUSH_HTTP_H1].isNull()) setHttpHeader(doc[PARAM_PUSH_HTTP_H1], 0); @@ -225,6 +228,7 @@ bool Config::loadFile() { setHttp2Header(doc[PARAM_PUSH_HTTP2_H1], 0); if (!doc[PARAM_PUSH_HTTP2_H2].isNull()) setHttp2Header(doc[PARAM_PUSH_HTTP2_H2], 1); + if (!doc[PARAM_PUSH_HTTP3].isNull()) setHttp3Url(doc[PARAM_PUSH_HTTP3]); if (!doc[PARAM_PUSH_INFLUXDB2].isNull()) setInfluxDb2PushUrl(doc[PARAM_PUSH_INFLUXDB2]); diff --git a/src/config.hpp b/src/config.hpp index 48cd427..68486a1 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -109,11 +109,13 @@ class Config { String _brewfatherPushUrl = ""; String _token = ""; + String _token2 = ""; String _httpUrl = ""; String _httpHeader[2] = {"Content-Type: application/json", ""}; String _http2Url = ""; String _http2Header[2] = {"Content-Type: application/json", ""}; + String _http3Url = ""; String _influxDb2Url = ""; String _influxDb2Org = ""; @@ -192,6 +194,11 @@ class Config { _token = s; _saveNeeded = true; } + const char* getToken2() { return _token2.c_str(); } + void setToken2(String s) { + _token2 = s; + _saveNeeded = true; + } // Standard HTTP const char* getHttpUrl() { return _httpUrl.c_str(); } @@ -220,6 +227,14 @@ class Config { bool isHttp2Active() { return _http2Url.length() ? true : false; } bool isHttp2SSL() { return _http2Url.startsWith("https://"); } + const char* getHttp3Url() { return _http3Url.c_str(); } + void setHttp3Url(String s) { + _http3Url = s; + _saveNeeded = true; + } + bool isHttp3Active() { return _http3Url.length() ? true : false; } + bool isHttp3SSL() { return _http3Url.startsWith("https://"); } + // InfluxDB2 const char* getInfluxDb2PushUrl() { return _influxDb2Url.c_str(); } void setInfluxDb2PushUrl(String s) { @@ -345,7 +360,7 @@ class Config { } bool isBLEActive() { return _colorBLE.length() ? true : false; } bool isWifiPushActive() { - return (isHttpActive() || isHttp2Active() || isBrewfatherActive() || isInfluxDb2Active() || isMqttActive()) ? true : false; + return (isHttpActive() || isHttp2Active() || isHttp3Active() || isBrewfatherActive() || isInfluxDb2Active() || isMqttActive()) ? true : false; } const RawGyroData& getGyroCalibration() { return _gyroCalibration; } diff --git a/src/pushtarget.cpp b/src/pushtarget.cpp index 4f24151..cd917a0 100644 --- a/src/pushtarget.cpp +++ b/src/pushtarget.cpp @@ -52,16 +52,22 @@ void PushTarget::sendAll(float angle, float gravitySG, float corrGravitySG, if (myConfig.isHttpActive()) { LOG_PERF_START("push-http"); - sendHttp(engine, myConfig.isHttpSSL(), 0); + sendHttpPost(engine, myConfig.isHttpSSL(), 0); LOG_PERF_STOP("push-http"); } if (myConfig.isHttp2Active()) { LOG_PERF_START("push-http2"); - sendHttp(engine, myConfig.isHttp2SSL(), 1); + sendHttpPost(engine, myConfig.isHttp2SSL(), 1); LOG_PERF_STOP("push-http2"); } + if (myConfig.isHttp3Active()) { + LOG_PERF_START("push-http3"); + sendHttpGet(engine, myConfig.isHttp3SSL()); + LOG_PERF_STOP("push-http3"); + } + if (myConfig.isInfluxDb2Active()) { LOG_PERF_START("push-influxdb2"); sendInfluxDb2(engine); @@ -178,9 +184,9 @@ void PushTarget::addHttpHeader(HTTPClient& http, String header) { } // -// Send data to http target +// Send data to http target using POST // -void PushTarget::sendHttp(TemplatingEngine& engine, bool isSecure, int index) { +void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure, int index) { #if !defined(PUSH_DISABLE_LOGGING) Log.notice(F("PUSH: Sending values to http (%s)" CR), index ? "http2" : "http"); @@ -248,12 +254,12 @@ void PushTarget::sendHttp(TemplatingEngine& engine, bool isSecure, int index) { if (_lastCode == 200) { _lastSuccess = true; - Log.notice(F("PUSH: HTTP push successful, response=%d" CR), + Log.notice(F("PUSH: HTTP post successful, response=%d" CR), _lastCode); } else { ErrorFileLog errLog; errLog.addEntry( - "PUSH: HTTP push failed response=" + String(_lastCode) + + "PUSH: HTTP post failed response=" + String(_lastCode) + String(index == 0 ? " (http)" : " (http2)")); } @@ -268,7 +274,71 @@ void PushTarget::sendHttp(TemplatingEngine& engine, bool isSecure, int index) { } // -// Send data to http target +// Send data to http target using GET +// +void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) { +#if !defined(PUSH_DISABLE_LOGGING) + Log.notice(F("PUSH: Sending values to http3" CR)); +#endif + _lastCode = 0; + _lastSuccess = false; + + String serverPath; + + serverPath = myConfig.getHttp3Url(); + serverPath += engine.create(TemplatingEngine::TEMPLATE_HTTP3); + +#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING) + Log.verbose(F("PUSH: url %s." CR), serverPath.c_str()); +#endif + + if (isSecure) { + Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR)); + _wifiSecure.setInsecure(); + +#if defined (ESP8266) + String host = serverPath.substring(8); // remove the prefix or the probe will fail, it needs a pure host name. + int idx = host.indexOf("/"); + if (idx!=-1) + host = host.substring(0, idx); + + if (_wifiSecure.probeMaxFragmentLength(host, 443, 512)) { + Log.notice(F("PUSH: HTTP server supports smaller SSL buffer." CR)); + _wifiSecure.setBufferSizes(512, 512); + } +#endif + + _httpSecure.begin(_wifiSecure, serverPath); + _httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000); + _lastCode = _httpSecure.GET(); + } else { + _http.begin(_wifi, serverPath); + _http.setTimeout(myHardwareConfig.getPushTimeout() * 1000); + _lastCode = _http.GET(); + } + + if (_lastCode == 200) { + _lastSuccess = true; + Log.notice(F("PUSH: HTTP get successful, response=%d" CR), + _lastCode); + } else { + ErrorFileLog errLog; + errLog.addEntry( + "PUSH: HTTP get failed response=" + String(_lastCode)); + } + + if (isSecure) { + _httpSecure.end(); + _wifiSecure.stop(); + } else { + _http.end(); + _wifi.stop(); + } + tcp_cleanup(); +} + +// +// Send data to mqtt target // void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) { #if !defined(PUSH_DISABLE_LOGGING) diff --git a/src/pushtarget.hpp b/src/pushtarget.hpp index 8f2456c..2c237c2 100644 --- a/src/pushtarget.hpp +++ b/src/pushtarget.hpp @@ -42,7 +42,8 @@ class PushTarget { int _lastCode; bool _lastSuccess; - void sendHttp(TemplatingEngine& engine, bool isSecure, int index); + void sendHttpPost(TemplatingEngine& engine, bool isSecure, int index); + void sendHttpGet(TemplatingEngine& engine, bool isSecure); void addHttpHeader(HTTPClient& http, String header); public: @@ -50,8 +51,9 @@ class PushTarget { float runTime); void sendBrewfather(TemplatingEngine& engine); - void sendHttp1(TemplatingEngine& engine, bool isSecure) { sendHttp(engine, isSecure, 0); } - void sendHttp2(TemplatingEngine& engine, bool isSecure) { sendHttp(engine, isSecure, 1); } + void sendHttp1(TemplatingEngine& engine, bool isSecure) { sendHttpPost(engine, isSecure, 0); } + void sendHttp2(TemplatingEngine& engine, bool isSecure) { sendHttpPost(engine, isSecure, 1); } + void sendHttp3(TemplatingEngine& engine, bool isSecure) { sendHttpGet(engine, isSecure); } void sendInfluxDb2(TemplatingEngine& engine); void sendMqtt(TemplatingEngine& engine, bool isSecure); int getLastCode() { return _lastCode; } diff --git a/src/resources.hpp b/src/resources.hpp index 4c284de..5a90b61 100644 --- a/src/resources.hpp +++ b/src/resources.hpp @@ -34,12 +34,14 @@ SOFTWARE. #define PARAM_RUNTIME_AVERAGE "runtime-average" #define PARAM_PUSH_BREWFATHER "brewfather-push" #define PARAM_TOKEN "token" +#define PARAM_TOKEN2 "token2" #define PARAM_PUSH_HTTP "http-push" #define PARAM_PUSH_HTTP_H1 "http-push-h1" #define PARAM_PUSH_HTTP_H2 "http-push-h2" #define PARAM_PUSH_HTTP2 "http-push2" #define PARAM_PUSH_HTTP2_H1 "http-push2-h1" #define PARAM_PUSH_HTTP2_H2 "http-push2-h2" +#define PARAM_PUSH_HTTP3 "http-push3" #define PARAM_PUSH_INFLUXDB2 "influxdb2-push" #define PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org" #define PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket" @@ -81,6 +83,7 @@ SOFTWARE. #define PARAM_HW_PUSH_TIMEOUT "push-timeout" #define PARAM_FORMAT_HTTP1 "http-1" #define PARAM_FORMAT_HTTP2 "http-2" +#define PARAM_FORMAT_HTTP3 "http-3" #define PARAM_FORMAT_BREWFATHER "brewfather" #define PARAM_FORMAT_INFLUXDB "influxdb" #define PARAM_FORMAT_MQTT "mqtt" diff --git a/src/templating.cpp b/src/templating.cpp index 9e79b25..9a91971 100644 --- a/src/templating.cpp +++ b/src/templating.cpp @@ -30,7 +30,7 @@ SOFTWARE. #include #endif -// Use iSpindle format for compatibility +// Use iSpindle format for compatibility, HTTP POST const char iSpindleFormat[] PROGMEM = "{" "\"name\" : \"${mdns}\", " @@ -48,6 +48,22 @@ const char iSpindleFormat[] PROGMEM = "\"run-time\": ${run-time} " "}"; +// Format for an HTTP GET +const char iHttpGetFormat[] PROGMEM = + "?name=${mdns}" + "&id=${id}" + "&token=${token2}" + "&interval=${sleep-interval}" + "&temperature=${temp}" + "&temp-units=${temp-unit}" + "&gravity=${gravity}" + "&angle=${angle}" + "&battery=${battery}" + "&rssi=${rssi}" + "&corr-gravity=${corr-gravity}" + "&gravity-unit=${gravity-unit}" + "&run-time=${run-time}"; + const char brewfatherFormat[] PROGMEM = "{" "\"name\": \"${mdns}\"," @@ -89,6 +105,7 @@ void TemplatingEngine::initialize(float angle, float gravitySG, float corrGravit setVal(TPL_MDNS, myConfig.getMDNS()); setVal(TPL_ID, myConfig.getID()); setVal(TPL_TOKEN, myConfig.getToken()); + setVal(TPL_TOKEN2, myConfig.getToken2()); // Temperature if (myConfig.isTempC()) { @@ -151,6 +168,10 @@ const String& TemplatingEngine::create(TemplatingEngine::Templates idx) { baseTemplate = String(iSpindleFormat); fname = TPL_FNAME_HTTP2; break; + case TEMPLATE_HTTP3: + baseTemplate = String(iHttpGetFormat); + fname = TPL_FNAME_HTTP3; + break; case TEMPLATE_BREWFATHER: baseTemplate = String(brewfatherFormat); //fname = TPL_FNAME_BREWFATHER; diff --git a/src/templating.hpp b/src/templating.hpp index fa87cd1..5a40ceb 100644 --- a/src/templating.hpp +++ b/src/templating.hpp @@ -35,6 +35,7 @@ SOFTWARE. #define TPL_MDNS "${mdns}" #define TPL_ID "${id}" #define TPL_TOKEN "${token}" +#define TPL_TOKEN2 "${token2}" #define TPL_SLEEP_INTERVAL "${sleep-interval}" #define TPL_TEMP "${temp}" #define TPL_TEMP_C "${temp-c}" @@ -55,11 +56,13 @@ SOFTWARE. #define TPL_FNAME_HTTP1 "/http-1.tpl" #define TPL_FNAME_HTTP2 "/http-2.tpl" +#define TPL_FNAME_HTTP3 "/http-3.tpl" // #define TPL_FNAME_BREWFATHER "/brewfather.tpl" #define TPL_FNAME_INFLUXDB "/influxdb.tpl" #define TPL_FNAME_MQTT "/mqtt.tpl" extern const char iSpindleFormat[] PROGMEM; +extern const char iHttpGetFormat[] PROGMEM; extern const char brewfatherFormat[] PROGMEM; extern const char influxDbFormat[] PROGMEM; extern const char mqttFormat[] PROGMEM; @@ -72,7 +75,7 @@ class TemplatingEngine { String val; }; - KeyVal items[20] = {{TPL_MDNS, ""}, {TPL_ID, ""}, + KeyVal items[21] = {{TPL_MDNS, ""}, {TPL_ID, ""}, {TPL_SLEEP_INTERVAL, ""}, {TPL_TEMP, ""}, {TPL_TEMP_C, ""}, {TPL_TEMP_F, ""}, {TPL_TEMP_UNITS, ""}, {TPL_BATTERY, ""}, @@ -81,7 +84,8 @@ class TemplatingEngine { {TPL_GRAVITY, ""}, {TPL_GRAVITY_G, ""}, {TPL_GRAVITY_P, ""}, {TPL_GRAVITY_CORR, ""}, {TPL_GRAVITY_CORR_G, ""}, {TPL_GRAVITY_CORR_P, ""}, - {TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""}}; + {TPL_GRAVITY_UNIT, ""}, {TPL_TOKEN, ""}, + {TPL_TOKEN2, ""} }; char buffer[20]; String baseTemplate; @@ -128,9 +132,10 @@ class TemplatingEngine { enum Templates { TEMPLATE_HTTP1 = 0, TEMPLATE_HTTP2 = 1, - TEMPLATE_BREWFATHER = 2, - TEMPLATE_INFLUX = 3, - TEMPLATE_MQTT = 4 + TEMPLATE_HTTP3 = 2, + TEMPLATE_BREWFATHER = 3, + TEMPLATE_INFLUX = 4, + TEMPLATE_MQTT = 5 }; void initialize(float angle, float gravitySG, float corrGravitySG, diff --git a/src/webserver.cpp b/src/webserver.cpp index 7ca4b73..2f84b32 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -285,6 +285,7 @@ void WebServerHandler::webHandleStatus() { doc[PARAM_RSSI] = WiFi.RSSI(); doc[PARAM_SLEEP_INTERVAL] = myConfig.getSleepInterval(); doc[PARAM_TOKEN] = myConfig.getToken(); + doc[PARAM_TOKEN2] = myConfig.getToken2(); doc[PARAM_APP_VER] = CFG_APPVER; doc[PARAM_MDNS] = myConfig.getMDNS(); @@ -413,6 +414,8 @@ void WebServerHandler::webHandleConfigPush() { if (_server->hasArg(PARAM_TOKEN)) myConfig.setToken(_server->arg(PARAM_TOKEN).c_str()); + if (_server->hasArg(PARAM_TOKEN2)) + myConfig.setToken2(_server->arg(PARAM_TOKEN2).c_str()); if (_server->hasArg(PARAM_PUSH_HTTP)) myConfig.setHttpUrl(_server->arg(PARAM_PUSH_HTTP).c_str()); if (_server->hasArg(PARAM_PUSH_HTTP_H1)) @@ -425,6 +428,8 @@ void WebServerHandler::webHandleConfigPush() { myConfig.setHttp2Header(_server->arg(PARAM_PUSH_HTTP2_H1).c_str(), 0); if (_server->hasArg(PARAM_PUSH_HTTP2_H2)) myConfig.setHttp2Header(_server->arg(PARAM_PUSH_HTTP2_H2).c_str(), 1); + if (_server->hasArg(PARAM_PUSH_HTTP3)) + myConfig.setHttp3Url(_server->arg(PARAM_PUSH_HTTP3).c_str()); if (_server->hasArg(PARAM_PUSH_BREWFATHER)) myConfig.setBrewfatherPushUrl(_server->arg(PARAM_PUSH_BREWFATHER).c_str()); if (_server->hasArg(PARAM_PUSH_INFLUXDB2)) @@ -710,6 +715,8 @@ void WebServerHandler::webHandleConfigFormatWrite() { success = writeFile(TPL_FNAME_HTTP1, _server->arg(PARAM_FORMAT_HTTP1)); } else if (_server->hasArg(PARAM_FORMAT_HTTP2)) { success = writeFile(TPL_FNAME_HTTP2, _server->arg(PARAM_FORMAT_HTTP2)); + } else if (_server->hasArg(PARAM_FORMAT_HTTP3)) { + success = writeFile(TPL_FNAME_HTTP3, _server->arg(PARAM_FORMAT_HTTP3)); } else if (_server->hasArg(PARAM_FORMAT_INFLUXDB)) { success = writeFile(TPL_FNAME_INFLUXDB, _server->arg(PARAM_FORMAT_INFLUXDB)); @@ -774,6 +781,9 @@ void WebServerHandler::webHandleTestPush() { } else if (!type.compareTo(PARAM_FORMAT_HTTP2) && myConfig.isHttp2Active()) { push.sendHttp2(engine, myConfig.isHttp2SSL()); enabled = true; + } else if (!type.compareTo(PARAM_FORMAT_HTTP3) && myConfig.isHttp3Active()) { + push.sendHttp3(engine, myConfig.isHttp3SSL()); + enabled = true; } else if (!type.compareTo(PARAM_FORMAT_INFLUXDB) && myConfig.isInfluxDb2Active()) { push.sendInfluxDb2(engine); enabled = true; @@ -868,6 +878,12 @@ void WebServerHandler::webHandleConfigFormatRead() { else doc[PARAM_FORMAT_HTTP2] = urlencode(String(&iSpindleFormat[0])); + s = readFile(TPL_FNAME_HTTP3); + if (s.length()) + doc[PARAM_FORMAT_HTTP3] = urlencode(s); + else + doc[PARAM_FORMAT_HTTP3] = urlencode(String(&iHttpGetFormat[0])); + /*s = readFile(TPL_FNAME_BREWFATHER); if (s.length()) doc[PARAM_FORMAT_BREWFATHER] = urlencode(s); diff --git a/src_docs/source/advanced.rst b/src_docs/source/advanced.rst index 49028dc..618c75a 100644 --- a/src_docs/source/advanced.rst +++ b/src_docs/source/advanced.rst @@ -35,6 +35,12 @@ These are the format keys available for use in the format. * - ${mdns} - Name of the device - gravmon2 + * - ${token} + - Token + - any value + * - ${token2} + - Token 2 + - any value * - ${id} - Unique id of the device - e422a3 diff --git a/src_docs/source/api.rst b/src_docs/source/api.rst index 187bc70..6cd1478 100644 --- a/src_docs/source/api.rst +++ b/src_docs/source/api.rst @@ -26,12 +26,14 @@ Other parameters are the same as in the configuration guide. "ble": "color", "brewfather-push": "http://log.brewfather.net/stream?id=Qwerty", "token": "token", + "token2": "token2", "http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry", "http-push-h1": "header: value", "http-push-h2": "header: value", "http-push2": "http://192.168.1.50/ispindel", "http-push2-h1": "header: value", "http-push2-h2": "header: value", + "http-push3": "http://192.168.1.50/ispindel", "influxdb2-push": "http://192.168.1.50:8086", "influxdb2-org": "org", "influxdb2-bucket": "bucket_id", @@ -103,6 +105,7 @@ Other parameters are the same as in the configuration guide. "temp-format": "C", "sleep-mode": false, "token": "token", + "token2": "token2", "rssi": -56, "app-ver": "0.0.0", "mdns": "gravmon", @@ -211,8 +214,11 @@ Payload should be in standard format used for posting a form. Such as as: `id=va .. code-block:: id=ee1bfc + token= + token2= http-push=http://192.168.1.50/ispindel http-push2= + http-push3= http-push-h1= http-push-h2= http-push2-h1= @@ -332,8 +338,10 @@ The requests package converts the json to standard form post format. url = "http://" + host + "/api/config/push" json = { "id": id, "token": "", + "token2": "", "http-push": "http://192.168.1.1/ispindel", "http-push2": "", + "http-push3": "", "http-push-h1": "", "http-push-h2": "", "http-push2-h1": "" diff --git a/src_docs/source/configuration.rst b/src_docs/source/configuration.rst index 05649db..cde4c59 100644 --- a/src_docs/source/configuration.rst +++ b/src_docs/source/configuration.rst @@ -92,13 +92,13 @@ Push Settings When enabling SSL this will not validate the root CA of the remote service, this is a design decision based on two aspects. Enabling CA validation will take 3-4s extra on each connection which means way less battery life, so the decision is to prioritize battery life over security. The data transmitted is not really that sensitive anyway so I belive this is a good balance. -* **HTTP URL 1:** +* **HTTP 1 (POST):** Endpoint to send data via http. Default format used Format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`. If you add the prefix `https://` then the device will use SSL when sending data. -* **HTTP URL 2:** +* **HTTP 2 (POST):** Endpoint to send data via http. Default format used :ref:`data-formats-ispindle`. You can customize the format using :ref:`format-editor`. @@ -109,6 +109,16 @@ Push Settings The token is included in the iSpindle JSON format and will be used for both HTTP targets. If you need to have 2 different tokens please use the :ref:`format-editor` to customize the data format. +* **HTTP 3 (GET):** + + Endpoint to send data via http. This is using an HTTP GET request instead of a post. This means that the values are appended to the URL like; http://endpoint?param=value¶m2=value2. You can customize the format using :ref:`format-editor`. + + If you add the prefix `https://` then the device will use SSL when sending data. + +* **Token 2:** + + The token is included in the default format for the HTTP GET url but can be used for any of the formats. For HTTP GET use can use this for an authorization token with for instance ubidots or blynk http api. + * **Brewfather URL:** Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather` diff --git a/src_docs/source/data.rst b/src_docs/source/data.rst index d16d875..55f1557 100644 --- a/src_docs/source/data.rst +++ b/src_docs/source/data.rst @@ -5,8 +5,8 @@ Data Formats .. _data-formats-ispindle: -iSpindle format -=============== +HTTP Post, iSpindle format +========================== This is the format used for standard http posts. @@ -75,6 +75,25 @@ This is the format for Brewfather. See: `Brewfather API docs ,id=,token=&interval=300&temperature=20.1&temp-units=& + gravity=$1.004&angle=45.5&battery=3.96&rssi=-18&corr-gravity=1.004&gravity-unit=&run-time=2.1 + +This is the format template used to create the data above. + +.. code-block:: + + ?name=${mdns}&id=${id}&token=${token2}&interval=${sleep-interval}&temperature=${temp}& + temp-units=${temp-unit}&gravity=${gravity}&angle=${angle}&battery=${battery}&rssi=${rssi}& + corr-gravity=${corr-gravity}&gravity-unit=${gravity-unit}&run-time=${run-time} + + Influx DB v2 ============ @@ -82,14 +101,17 @@ This is the format for InfluxDB v2 .. code-block:: - measurement,host=,device=,temp-format=,gravity-format=SG,gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18 + measurement,host=,device=,temp-format=,gravity-format=SG, + gravity=1.0004,corr-gravity=1.0004,angle=45.45,temp=20.1,battery=3.96,rssi=-18 This is the format template used to create the json above. .. code-block:: - measurement,host=${mdns},device=${id},temp-format=${temp-unit},gravity-format=${gravity-unit} gravity=${gravity},corr-gravity=${corr-gravity},angle=${angle},temp=${temp},battery=${battery},rssi=${rssi} + measurement,host=${mdns},device=${id},temp-format=${temp-unit}, + gravity-format=${gravity-unit} gravity=${gravity},corr-gravity=${corr-gravity}, + angle=${angle},temp=${temp},battery=${battery},rssi=${rssi} .. _data-formats-mqtt: @@ -130,7 +152,10 @@ This is a format template that is compatible with v0.6. Just replace the `topic` .. code-block:: - topic:{"name":"gravmon","ID":"${id}","token":"gravmon","interval": ${sleep-interval},"temperature": ${temp},"temp-units": "${temp-unit}","gravity":${gravity},"angle": ${angle},"battery":${battery},"rssi": ${rssi},"corr-gravity":${corr-gravity},"gravity-unit": "${gravity-unit}","run-time": ${run-time}}| + topic:{"name":"gravmon", "ID":"${id}", "token":"gravmon", "interval": ${sleep-interval}, + "temperature": ${temp}, "temp-units": "${temp-unit}", "gravity":${gravity}, + "angle": ${angle}, "battery":${battery}, "rssi": ${rssi}, "corr-gravity":${corr-gravity}, + "gravity-unit": "${gravity-unit}", "run-time": ${run-time}}| version.json @@ -147,7 +172,7 @@ they can be uploaded manually afterwards. "version":"0.7.0", "html": [ "index.min.htm", - "device.min.htm", + "test.min.htm", "config.min.htm", "format.min.htm", "calibration.min.htm", diff --git a/src_docs/source/images/config2.png b/src_docs/source/images/config2.png index ca0a5850f26dd39eb1f368acc68bbf27a770397d..d654575b041f074711476c0fbb89d35bab31cdcc 100644 GIT binary patch literal 28382 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 diff --git a/src_docs/source/releases.rst b/src_docs/source/releases.rst index 96257bc..c3d8641 100644 --- a/src_docs/source/releases.rst +++ b/src_docs/source/releases.rst @@ -5,18 +5,18 @@ Releases v0.9.0 ------ +* Added one http push target that uses HTTP GET. This can be used with ubidots or blynk api's. * Added function to test push targets from configuration page. It will send data and show the return code as a first step. +* Experimental release of firmware using an esp32 instead of esp8266 * Merged index and device pages into one so that all the needed information is available on the index page. * Removed api for device (/api/device), it's now merged into the /api/status api. * Test function in format editor now uses real data and not fake. * Split push configuration into two sections to make it fit better on smaller devices * Updated WifiManager and DoubleReset libraries -* Experimental release of firmware using an esp32 instead of esp8266 * Updated esp32 target with littlefs support * Updated esp32 target with BLE send support (it will simulate a tilt) -* Corrected PIN for voltage read on ESP32 * Mounted esp32 d1 mini mounted to a iSpindle PCB 4.0 (CherryPhilip) which worked fine. - +* BUG: Corrected PIN for voltage read on ESP32 * BUG: If using plato and not gravity formula was defined the value was set to null. v0.8.0 @@ -29,7 +29,7 @@ v0.8.0 * Added instructions for how to configure integration with Brewspy * Added instructions for how to configure integration with Thingspeak * Added option to do a factory reset via API. -* Logging the runtime, how long a measurement take (last 10 are stored). This can be +* Added logging of the runtime, how long a measurement take (last 10 are stored). This can be used to check how good the wifi connection is and estimate the lifetime when on battery. Check the device page in the UI for this information. * Refactored code to free up more RAM to make SSL more stable. diff --git a/test/config.json b/test/config.json index 11c5f6b..b751976 100644 --- a/test/config.json +++ b/test/config.json @@ -11,6 +11,8 @@ "http-push2-h1": "Second", "http-push2-h2": "First", "token": "mytoken", + "token2": "mytoken2", + "http-push3": "http://192.168.1.10/ispindel", "influxdb2-push": "http://192.168.1.10:8086", "influxdb2-org": "hello", "influxdb2-bucket": "spann", diff --git a/test/format.json b/test/format.json index e2c7c21..c016523 100644 --- a/test/format.json +++ b/test/format.json @@ -2,6 +2,7 @@ "id": "7376ef", "http-1": "%7B%22name%22%20%3A%20%22gravmon%22%2C%20%22ID%22%3A%20%22%24%7Bid%7D%22%2C%20%22token%22%20%3A%20%22gravmon%22%2C%20%22interval%22%3A%20%24%7Bsleep%2Dinterval%7D%2C%20%22temperature%22%3A%20%24%7Btemp%7D%2C%20%22temp%2Dunits%22%3A%20%22%24%7Btemp%2Dunit%7D%22%2C%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%22angle%22%3A%20%24%7Bangle%7D%2C%20%22battery%22%3A%20%24%7Bbattery%7D%2C%20%22rssi%22%3A%20%24%7Brssi%7D%2C%20%22corr%2Dgravity%22%3A%20%24%7Bcorr%2Dgravity%7D%2C%20%22gravity%2Dunit%22%3A%20%22%24%7Bgravity%2Dunit%7D%22%2C%20%22run%2Dtime%22%3A%20%24%7Brun%2Dtime%7D%20%7D", "http-2": "%7B%22name%22%20%3A%20%22gravmon%22%2C%20%22ID%22%3A%20%22%24%7Bid%7D%22%2C%20%22token%22%20%3A%20%22gravmon%22%2C%20%22interval%22%3A%20%24%7Bsleep%2Dinterval%7D%2C%20%22temperature%22%3A%20%24%7Btemp%7D%2C%20%22temp%2Dunits%22%3A%20%22%24%7Btemp%2Dunit%7D%22%2C%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%22angle%22%3A%20%24%7Bangle%7D%2C%20%22battery%22%3A%20%24%7Bbattery%7D%2C%20%22rssi%22%3A%20%24%7Brssi%7D%2C%20%22corr%2Dgravity%22%3A%20%24%7Bcorr%2Dgravity%7D%2C%20%22gravity%2Dunit%22%3A%20%22%24%7Bgravity%2Dunit%7D%22%2C%20%22run%2Dtime%22%3A%20%24%7Brun%2Dtime%7D%20%7D", + "http-3": "%7B%22name%22%20%3A%20%22gravmon%22%2C%20%22ID%22%3A%20%22%24%7Bid%7D%22%2C%20%22token%22%20%3A%20%22gravmon%22%2C%20%22interval%22%3A%20%24%7Bsleep%2Dinterval%7D%2C%20%22temperature%22%3A%20%24%7Btemp%7D%2C%20%22temp%2Dunits%22%3A%20%22%24%7Btemp%2Dunit%7D%22%2C%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%20%22angle%22%3A%20%24%7Bangle%7D%2C%20%22battery%22%3A%20%24%7Bbattery%7D%2C%20%22rssi%22%3A%20%24%7Brssi%7D%2C%20%22corr%2Dgravity%22%3A%20%24%7Bcorr%2Dgravity%7D%2C%20%22gravity%2Dunit%22%3A%20%22%24%7Bgravity%2Dunit%7D%22%2C%20%22run%2Dtime%22%3A%20%24%7Brun%2Dtime%7D%20%7D", "influxdb": "measurement%2Chost%3D%24%7Bmdns%7D%2Cdevice%3D%24%7Bid%7D%2Ctemp%2Dformat%3D%24%7Btemp%2Dunit%7D%2Cgravity%2Dformat%3D%24%7Bgravity%2Dunit%7D%20gravity%3D%24%7Bgravity%7D%2Ccorr%2Dgravity%3D%24%7Bcorr%2Dgravity%7D%2Cangle%3D%24%7Bangle%7D%2Ctemp%3D%24%7Btemp%7D%2Cbattery%3D%24%7Bbattery%7D%2Crssi%3D%24%7Brssi%7D%0A", "mqtt": "ispindel%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Bangle%7D%7Cispindel%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7Cispindel%2F%24%7Bmdns%7D%2Ftemp%5Funits%3A%24%7Btemp%2Dunit%7D%7Cispindel%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7Cispindel%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7Cispindel%2F%24%7Bmdns%7D%2Finterval%3A%24%7Bsleep%2Dinterval%7D%7Cispindel%2F%24%7Bmdns%7D%2FRSSI%3A%24%7Brssi%7D%7C" } \ No newline at end of file diff --git a/test/status.json b/test/status.json index 91e9e33..960b710 100644 --- a/test/status.json +++ b/test/status.json @@ -5,6 +5,7 @@ "gravity-format": "G", "temp-c": 12, "temp-f": 32, + "sleep-interval": 300, "battery": 3.81, "temp-format": "C", "sleep-mode": false,