|
|
@ -8,16 +8,24 @@ Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com> |
|
|
|
|
|
|
|
#if THINGSPEAK_SUPPORT
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
#include "broker.h"
|
|
|
|
#include "thingspeak.h"
|
|
|
|
#include "libs/URL.h"
|
|
|
|
#include "libs/SecureClientHelpers.h"
|
|
|
|
#include "libs/AsyncClientHelpers.h"
|
|
|
|
|
|
|
|
#if THINGSPEAK_USE_ASYNC
|
|
|
|
#include <ESPAsyncTCP.h>
|
|
|
|
#if SECURE_CLIENT != SECURE_CLIENT_NONE
|
|
|
|
|
|
|
|
#if THINGSPEAK_SECURE_CLIENT_INCLUDE_CA
|
|
|
|
#include "static/thingspeak_client_trusted_root_ca.h"
|
|
|
|
#else
|
|
|
|
#include <ESP8266WiFi.h>
|
|
|
|
#include "static/digicert_high_assurance_pem.h"
|
|
|
|
#define _tspk_client_trusted_root_ca _ssl_digicert_high_assurance_ev_root_ca
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define THINGSPEAK_DATA_BUFFER_SIZE 256
|
|
|
|
#endif // SECURE_CLIENT != SECURE_CLIENT_NONE
|
|
|
|
|
|
|
|
const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM = |
|
|
|
"POST %s HTTP/1.1\r\n" |
|
|
@ -37,19 +45,32 @@ bool _tspk_flush = false; |
|
|
|
unsigned long _tspk_last_flush = 0; |
|
|
|
unsigned char _tspk_tries = THINGSPEAK_TRIES; |
|
|
|
|
|
|
|
class AsyncThingspeak : public AsyncClient |
|
|
|
{ |
|
|
|
public: |
|
|
|
#if THINGSPEAK_USE_ASYNC
|
|
|
|
|
|
|
|
class AsyncThingspeak : public AsyncClient { |
|
|
|
public: |
|
|
|
|
|
|
|
URL address; |
|
|
|
AsyncThingspeak(const String& _url) : address(_url) { }; |
|
|
|
|
|
|
|
bool connect() { |
|
|
|
#if ASYNC_TCP_SSL_ENABLED && THINGSPEAK_USE_SSL
|
|
|
|
return AsyncClient::connect(address.host.c_str(), address.port, true); |
|
|
|
#else
|
|
|
|
return AsyncClient::connect(address.host.c_str(), address.port); |
|
|
|
#endif
|
|
|
|
} |
|
|
|
|
|
|
|
bool connect(const String& url) { |
|
|
|
address = url; |
|
|
|
return connect(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
AsyncThingspeak * _tspk_client; |
|
|
|
AsyncThingspeak* _tspk_client = nullptr; |
|
|
|
AsyncClientState _tspk_state = AsyncClientState::Disconnected; |
|
|
|
|
|
|
|
#if THINGSPEAK_USE_ASYNC
|
|
|
|
bool _tspk_connecting = false; |
|
|
|
bool _tspk_connected = false; |
|
|
|
#endif
|
|
|
|
#endif // THINGSPEAK_USE_ASYNC == 1
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
@ -105,7 +126,10 @@ void _tspkConfigure() { |
|
|
|
_tspk_enabled = false; |
|
|
|
setSetting("tspkEnabled", 0); |
|
|
|
} |
|
|
|
if (_tspk_enabled && !_tspk_client) _tspkInitClient(getSetting("tspkAddress", THINGSPEAK_ADDRESS)); |
|
|
|
|
|
|
|
#if THINGSPEAK_USE_ASYNC
|
|
|
|
if (_tspk_enabled && !_tspk_client) _tspkInitClient(getSetting("tspkAddress", THINGSPEAK_ADDRESS)); |
|
|
|
#endif
|
|
|
|
} |
|
|
|
|
|
|
|
#if THINGSPEAK_USE_ASYNC
|
|
|
@ -129,8 +153,7 @@ void _tspkInitClient(const String& _url) { |
|
|
|
_tspk_data = ""; |
|
|
|
_tspk_client_ts = 0; |
|
|
|
_tspk_last_flush = millis(); |
|
|
|
_tspk_connected = false; |
|
|
|
_tspk_connecting = false; |
|
|
|
_tspk_state = AsyncClientState::Disconnected; |
|
|
|
_tspk_client_state = tspk_state_t::NONE; |
|
|
|
}, nullptr); |
|
|
|
|
|
|
@ -201,27 +224,26 @@ void _tspkInitClient(const String& _url) { |
|
|
|
|
|
|
|
_tspk_client->onConnect([](void * arg, AsyncClient * client) { |
|
|
|
|
|
|
|
_tspk_connected = true; |
|
|
|
_tspk_connecting = false; |
|
|
|
AsyncThingspeak* _tspk_client = reinterpret_cast<AsyncThingspeak*>(client); |
|
|
|
_tspk_state = AsyncClientState::Disconnected; |
|
|
|
|
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%u\n"), _tspk_client->address.host.c_str(), _tspk_client->address.port); |
|
|
|
AsyncThingspeak* tspk_client = reinterpret_cast<AsyncThingspeak*>(client); |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%u\n"), tspk_client->address.host.c_str(), tspk_client->address.port); |
|
|
|
|
|
|
|
#if THINGSPEAK_USE_SSL
|
|
|
|
uint8_t fp[20] = {0}; |
|
|
|
sslFingerPrintArray(THINGSPEAK_FINGERPRINT, fp); |
|
|
|
SSL * ssl = _tspk_client->getSSL(); |
|
|
|
SSL * ssl = tspk_client->getSSL(); |
|
|
|
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) { |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n")); |
|
|
|
} |
|
|
|
#endif
|
|
|
|
|
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), _tspk_client->address.path.c_str(), _tspk_data.c_str()); |
|
|
|
char headers[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + _tspk_client->address.path.length() + _tspk_client->address.host.length() + 1]; |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), tspk_client->address.path.c_str(), _tspk_data.c_str()); |
|
|
|
char headers[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + tspk_client->address.path.length() + tspk_client->address.host.length() + 1]; |
|
|
|
snprintf_P(headers, sizeof(headers), |
|
|
|
THINGSPEAK_REQUEST_TEMPLATE, |
|
|
|
_tspk_client->address.path.c_str(), |
|
|
|
_tspk_client->address.host.c_str(), |
|
|
|
tspk_client->address.path.c_str(), |
|
|
|
tspk_client->address.host.c_str(), |
|
|
|
_tspk_data.length() |
|
|
|
); |
|
|
|
|
|
|
@ -232,22 +254,16 @@ void _tspkInitClient(const String& _url) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void _tspkPost() { |
|
|
|
void _tspkPost(const String& address) { |
|
|
|
|
|
|
|
if (_tspk_connected || _tspk_connecting) return; |
|
|
|
if (_tspk_state != AsyncClientState::Disconnected) return; |
|
|
|
|
|
|
|
_tspk_client_ts = millis(); |
|
|
|
|
|
|
|
#if THINGSPEAK_USE_SSL
|
|
|
|
bool connected = _tspk_client->connect(_tspk_host.c_str(), _tspk_port, THINGSPEAK_USE_SSL); |
|
|
|
#else
|
|
|
|
_tspk_client->address = URL(getSetting("tspkAddress", THINGSPEAK_ADDRESS)); |
|
|
|
bool connected = _tspk_client->connect(_tspk_client->address.host.c_str(), _tspk_client->address.port); |
|
|
|
#endif
|
|
|
|
|
|
|
|
_tspk_connecting = connected; |
|
|
|
_tspk_state = _tspk_client->connect(address) |
|
|
|
? AsyncClientState::Connecting |
|
|
|
: AsyncClientState::Disconnected; |
|
|
|
|
|
|
|
if (!connected) { |
|
|
|
if (_tspk_state == AsyncClientState::Disconnected) { |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n")); |
|
|
|
_tspk_client->close(true); |
|
|
|
} |
|
|
@ -256,55 +272,95 @@ void _tspkPost() { |
|
|
|
|
|
|
|
#else // THINGSPEAK_USE_ASYNC
|
|
|
|
|
|
|
|
void _tspkPost() { |
|
|
|
#if THINGSPEAK_USE_SSL && (SECURE_CLIENT == SECURE_CLIENT_BEARSSL)
|
|
|
|
|
|
|
|
SecureClientConfig _tspk_sc_config { |
|
|
|
"THINGSPEAK", |
|
|
|
[]() -> int { |
|
|
|
return getSetting("tspkScCheck", THINGSPEAK_SECURE_CLIENT_CHECK); |
|
|
|
}, |
|
|
|
[]() -> PGM_P { |
|
|
|
return _tspk_client_trusted_root_ca; |
|
|
|
}, |
|
|
|
[]() -> String { |
|
|
|
return getSetting("tspkFP", THINGSPEAK_FINGERPRINT); |
|
|
|
}, |
|
|
|
[]() -> uint16_t { |
|
|
|
return getSetting("tspkScMFLN", THINGSPEAK_SECURE_CLIENT_MFLN); |
|
|
|
}, |
|
|
|
true |
|
|
|
}; |
|
|
|
|
|
|
|
#if THINGSPEAK_USE_SSL
|
|
|
|
WiFiClientSecure _tspk_client; |
|
|
|
#else
|
|
|
|
WiFiClient _tspk_client; |
|
|
|
#endif
|
|
|
|
#endif // THINGSPEAK_USE_SSL && SECURE_CLIENT_BEARSSL
|
|
|
|
|
|
|
|
if (_tspk_client.connect(_tspk_host.c_str(), _tspk_port)) { |
|
|
|
void _tspkPost(WiFiClient* client, const URL& url) { |
|
|
|
|
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%u\n"), _tspk_host.c_str(), _tspk_port); |
|
|
|
if (!client->connect(url.host.c_str(), url.port)) { |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!_tspk_client.verify(THINGSPEAK_FINGERPRINT, _tspk_host.c_str())) { |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n")); |
|
|
|
} |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%u\n"), url.host.c_str(), url.port); |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), url.path.c_str(), _tspk_data.c_str()); |
|
|
|
|
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), _tspk_client.path.c_str(), _tspk_data.c_str()); |
|
|
|
char headers[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + _tspk_client.path.length() + _tspk_client.host.lengh() + 1]; |
|
|
|
snprintf_P(headers, sizeof(headers), |
|
|
|
THINGSPEAK_REQUEST_TEMPLATE, |
|
|
|
_tspk_client.path.c_str(), |
|
|
|
_tspk_client.host.c_str(), |
|
|
|
_tspk_data.length() |
|
|
|
); |
|
|
|
char headers[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + url.path.length() + url.host.length() + 1]; |
|
|
|
snprintf_P(headers, sizeof(headers), |
|
|
|
THINGSPEAK_REQUEST_TEMPLATE, |
|
|
|
url.path.c_str(), |
|
|
|
url.host.c_str(), |
|
|
|
_tspk_data.length() |
|
|
|
); |
|
|
|
|
|
|
|
_tspk_client.print(headers); |
|
|
|
_tspk_client.print(_tspk_data); |
|
|
|
client->print(headers); |
|
|
|
client->print(_tspk_data); |
|
|
|
|
|
|
|
nice_delay(100); |
|
|
|
nice_delay(100); |
|
|
|
|
|
|
|
String response = _tspk_client.readString(); |
|
|
|
int pos = response.indexOf("\r\n\r\n"); |
|
|
|
unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0; |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %u\n"), code); |
|
|
|
_tspk_client.stop(); |
|
|
|
const auto response = client->readString(); |
|
|
|
int pos = response.indexOf("\r\n\r\n"); |
|
|
|
|
|
|
|
_tspk_last_flush = millis(); |
|
|
|
if ((0 == code) && _tspk_tries) { |
|
|
|
_tspk_flush = true; |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing %u more time(s)\n"), _tspk_tries); |
|
|
|
} else { |
|
|
|
_tspkClearQueue(); |
|
|
|
} |
|
|
|
unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0; |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %u\n"), code); |
|
|
|
|
|
|
|
return; |
|
|
|
client->stop(); |
|
|
|
|
|
|
|
_tspk_last_flush = millis(); |
|
|
|
if ((0 == code) && _tspk_tries) { |
|
|
|
_tspk_flush = true; |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing %u more time(s)\n"), _tspk_tries); |
|
|
|
} else { |
|
|
|
_tspkClearQueue(); |
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n")); |
|
|
|
} |
|
|
|
|
|
|
|
void _tspkPost(const String& address) { |
|
|
|
|
|
|
|
const URL url(address); |
|
|
|
|
|
|
|
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
|
|
|
|
if (url.protocol == "https") { |
|
|
|
const int check = _ota_sc_config.on_check(); |
|
|
|
if (!ntpSynced() && (check == SECURE_CLIENT_CHECK_CA)) { |
|
|
|
DEBUG_MSG_P(PSTR("[THINGSPEAK] Time not synced! Cannot use CA validation\n")); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
auto client = std::make_unique<SecureClient>(_tspk_sc_config); |
|
|
|
if (!client->beforeConnected()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_tspkPost(&client->get(), url); |
|
|
|
return; |
|
|
|
} |
|
|
|
#endif
|
|
|
|
|
|
|
|
if (url.protocol == "http") { |
|
|
|
auto client = std::make_unique<WiFiClient>(); |
|
|
|
_tspkPost(client.get(), url); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
@ -333,11 +389,14 @@ void _tspkFlush() { |
|
|
|
|
|
|
|
if (!_tspk_flush) return; |
|
|
|
if (millis() - _tspk_last_flush < THINGSPEAK_MIN_INTERVAL) return; |
|
|
|
if (_tspk_connected || _tspk_connecting) return; |
|
|
|
|
|
|
|
#if THINGSPEAK_USE_ASYNC
|
|
|
|
if (_tspk_state != AsyncClientState::Disconnected) return; |
|
|
|
#endif
|
|
|
|
|
|
|
|
_tspk_last_flush = millis(); |
|
|
|
_tspk_flush = false; |
|
|
|
_tspk_data.reserve(THINGSPEAK_DATA_BUFFER_SIZE); |
|
|
|
_tspk_data.reserve(tspkDataBufferSize); |
|
|
|
|
|
|
|
// Walk the fields, numbered 1...THINGSPEAK_FIELDS
|
|
|
|
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) { |
|
|
@ -354,7 +413,7 @@ void _tspkFlush() { |
|
|
|
_tspk_data.concat("&api_key="); |
|
|
|
_tspk_data.concat(getSetting<String>("tspkKey", THINGSPEAK_APIKEY)); |
|
|
|
--_tspk_tries; |
|
|
|
_tspkPost(); |
|
|
|
_tspkPost(getSetting("tspkAddress", THINGSPEAK_ADDRESS)); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|