Files
ESPEasy/src/_CPlugin_Helper.h
T
Gijs Noorlander 77b39b748c [LoRa/TTN] Output packed raw format for uploading to TTN
This does allow uploading even more parameters than the 4 supported by ESPeasy, since encoder and decoder can be tailored to the specific plugin.
For example the GPS plugin can now upload 7 parameters in only 20 bytes.

Still to do: Why can the decoder not handle encoding per 24 bit? (lat/lon do fit perfectly fine in 24 bit).
2019-08-15 02:12:13 +02:00

1028 lines
33 KiB
C++

#ifndef CPLUGIN_HELPER_H
#define CPLUGIN_HELPER_H CPLUGIN_HELPER_H
// These element classes should be defined as class, to be used as template.
/*********************************************************************************************\
* MQTT_queue_element for all MQTT base controllers
\*********************************************************************************************/
class MQTT_queue_element {
public:
MQTT_queue_element() : controller_idx(0), _retained(false) {}
MQTT_queue_element(int ctrl_idx,
const String& topic, const String& payload, boolean retained) :
controller_idx(ctrl_idx), _topic(topic), _payload(payload), _retained(retained)
{}
size_t getSize() const {
return sizeof(this) + _topic.length() + _payload.length();
}
int controller_idx;
String _topic;
String _payload;
boolean _retained;
};
/*********************************************************************************************\
* Simple queue element, only storing controller index and some String
\*********************************************************************************************/
class simple_queue_element_string_only {
public:
simple_queue_element_string_only() : controller_idx(0) {}
simple_queue_element_string_only(int ctrl_idx, const String& req) :
controller_idx(ctrl_idx), txt(req) {}
size_t getSize() const {
return sizeof(this) + txt.length();
}
int controller_idx;
String txt;
};
/*********************************************************************************************\
* C001_queue_element for queueing requests for C001.
\*********************************************************************************************/
#define C001_queue_element simple_queue_element_string_only
/*********************************************************************************************\
* C003_queue_element for queueing requests for C003 Nodo Telnet.
\*********************************************************************************************/
#define C003_queue_element simple_queue_element_string_only
/*********************************************************************************************\
* C004_queue_element for queueing requests for C004 ThingSpeak.
* Typical use case for Thingspeak is to only send values every N seconds/minutes.
* So we just store everything needed to recreate the event when the time is ready.
\*********************************************************************************************/
class C004_queue_element {
public:
C004_queue_element() : controller_idx(0), TaskIndex(0), idx(0), sensorType(0) {}
C004_queue_element(const struct EventStruct *event) :
controller_idx(event->ControllerIndex),
TaskIndex(event->TaskIndex),
idx(event->idx),
sensorType(event->sensorType) {
if (sensorType == SENSOR_TYPE_STRING) {
txt = event->String2;
}
}
size_t getSize() const {
return sizeof(this) + txt.length();
}
int controller_idx;
byte TaskIndex;
int idx;
byte sensorType;
String txt;
};
/*********************************************************************************************\
* C007_queue_element for queueing requests for C007 Emoncms
\*********************************************************************************************/
class C007_queue_element {
public:
C007_queue_element() : controller_idx(0), TaskIndex(0), idx(0), sensorType(0) {}
C007_queue_element(const struct EventStruct *event) :
controller_idx(event->ControllerIndex),
TaskIndex(event->TaskIndex),
idx(event->idx),
sensorType(event->sensorType) {}
size_t getSize() const {
return sizeof(this);
}
int controller_idx;
byte TaskIndex;
int idx;
byte sensorType;
};
/*********************************************************************************************\
* Base class for controllers that only send a single value per request and thus needs to
* keep track of the number of values already sent.
\*********************************************************************************************/
class queue_element_single_value_base {
public:
queue_element_single_value_base() : controller_idx(0), TaskIndex(0), idx(0), valuesSent(0) {}
queue_element_single_value_base(const struct EventStruct *event, byte value_count) :
controller_idx(event->ControllerIndex),
TaskIndex(event->TaskIndex),
idx(event->idx),
valuesSent(0),
valueCount(value_count) {}
bool checkDone(bool succesfull) const {
if (succesfull) { ++valuesSent; }
return valuesSent >= valueCount || valuesSent >= VARS_PER_TASK;
}
size_t getSize() const {
size_t total = sizeof(this);
for (int i = 0; i < VARS_PER_TASK; ++i) {
total += txt[i].length();
}
return total;
}
String txt[VARS_PER_TASK];
int controller_idx;
byte TaskIndex;
int idx;
mutable byte valuesSent; // Value must be set by const function checkDone()
byte valueCount;
};
/*********************************************************************************************\
* C008_queue_element for queueing requests for 008: Generic HTTP
* Using queue_element_single_value_base
\*********************************************************************************************/
#define C008_queue_element queue_element_single_value_base
/*********************************************************************************************\
* C009_queue_element for queueing requests for C009: FHEM HTTP.
\*********************************************************************************************/
class C009_queue_element {
public:
C009_queue_element() : controller_idx(0), TaskIndex(0), idx(0), sensorType(0) {}
C009_queue_element(const struct EventStruct *event) :
controller_idx(event->ControllerIndex),
TaskIndex(event->TaskIndex),
idx(event->idx),
sensorType(event->sensorType) {}
size_t getSize() const {
size_t total = sizeof(this);
for (int i = 0; i < VARS_PER_TASK; ++i) {
total += txt[i].length();
}
return total;
}
String txt[VARS_PER_TASK];
int controller_idx;
byte TaskIndex;
int idx;
byte sensorType;
};
/*********************************************************************************************\
* C010_queue_element for queueing requests for 010: Generic UDP
* Using queue_element_single_value_base
\*********************************************************************************************/
#define C010_queue_element queue_element_single_value_base
/*********************************************************************************************\
* C011_queue_element for queueing requests for 011: Generic HTTP Advanced
\*********************************************************************************************/
#define C011_queue_element simple_queue_element_string_only
/*********************************************************************************************\
* C012_queue_element for queueing requests for 012: Blynk
* Using queue_element_single_value_base
\*********************************************************************************************/
#define C012_queue_element queue_element_single_value_base
/*********************************************************************************************\
* C015_queue_element for queueing requests for 015: Blynk
* Using queue_element_single_value_base
\*********************************************************************************************/
// #define C015_queue_element queue_element_single_value_base
class C015_queue_element {
public:
C015_queue_element() : controller_idx(0), TaskIndex(0), idx(0), valuesSent(0) {}
C015_queue_element(const struct EventStruct *event, byte value_count) :
controller_idx(event->ControllerIndex),
TaskIndex(event->TaskIndex),
idx(event->idx),
valuesSent(0),
valueCount(value_count) {}
bool checkDone(bool succesfull) const {
if (succesfull) { ++valuesSent; }
return valuesSent >= valueCount || valuesSent >= VARS_PER_TASK;
}
size_t getSize() const {
size_t total = sizeof(this);
for (int i = 0; i < VARS_PER_TASK; ++i) {
total += txt[i].length();
}
return total;
}
String txt[VARS_PER_TASK];
int vPin[VARS_PER_TASK] = {0};
int controller_idx;
byte TaskIndex;
int idx;
mutable byte valuesSent; // Value must be set by const function checkDone()
byte valueCount;
};
/*********************************************************************************************\
* C016_queue_element for queueing requests for C016: Cached HTTP.
\*********************************************************************************************/
class C016_queue_element {
public:
C016_queue_element() : timestamp(0), controller_idx(0), TaskIndex(0), sensorType(0) {}
C016_queue_element(const struct EventStruct *event, byte value_count, unsigned long unixTime) :
timestamp(unixTime),
controller_idx(event->ControllerIndex),
TaskIndex(event->TaskIndex),
sensorType(event->sensorType),
valueCount(value_count)
{
const byte BaseVarIndex = TaskIndex * VARS_PER_TASK;
for (byte i = 0; i < VARS_PER_TASK; ++i) {
if (i < value_count) {
values[i] = UserVar[BaseVarIndex + i];
} else {
values[i] = 0.0;
}
}
}
size_t getSize() const {
return sizeof(this);
}
float values[VARS_PER_TASK];
unsigned long timestamp; // Unix timestamp
byte controller_idx;
byte TaskIndex;
byte sensorType;
byte valueCount;
};
/*********************************************************************************************\
* C017_queue_element for queueing requests for C017: Zabbix Trapper Protocol.
\*********************************************************************************************/
class C017_queue_element {
public:
C017_queue_element() : controller_idx(0), TaskIndex(0), idx(0), sensorType(0) {}
C017_queue_element(const struct EventStruct *event) :
controller_idx(event->ControllerIndex),
TaskIndex(event->TaskIndex),
idx(event->idx),
sensorType(event->sensorType) {}
size_t getSize() const {
size_t total = sizeof(this);
for (int i = 0; i < VARS_PER_TASK; ++i) {
total += txt[i].length();
}
return total;
}
String txt[VARS_PER_TASK];
int controller_idx;
byte TaskIndex;
int idx;
byte sensorType;
};
/*********************************************************************************************\
* C018_queue_element for queueing requests for C018: TTN/RN2483
\*********************************************************************************************/
class C018_queue_element {
public:
C018_queue_element() {}
C018_queue_element(const struct EventStruct *event, byte value_count, uint8_t sampleSetCount, const String& raw_packed) :
controller_idx(event->ControllerIndex)
{
packed.reserve(32);
packed += LoRa_addInt(Settings.TaskDeviceNumber[event->TaskIndex], PackedData_uint8);
packed += LoRa_addInt(event->idx, PackedData_uint16);
packed += LoRa_addInt(sampleSetCount, PackedData_uint8);
packed += LoRa_addInt(value_count, PackedData_uint8);
if (raw_packed.length() > 0) {
packed += raw_packed;
} else {
const byte BaseVarIndex = event->TaskIndex * VARS_PER_TASK;
switch (event->sensorType)
{
case SENSOR_TYPE_LONG:
{
unsigned long longval = (unsigned long)UserVar[BaseVarIndex] + ((unsigned long)UserVar[BaseVarIndex + 1] << 16);
packed += LoRa_addInt(longval, PackedData_uint32);
break;
}
default:
for (byte i = 0; i < value_count && i < VARS_PER_TASK; ++i) {
// For now, just store the floats as an int32 by multiplying the value with 10000.
packed += LoRa_addFloat(value_count, PackedData_int32_1e4);
}
break;
}
}
}
size_t getSize() const {
return sizeof(this);
}
int controller_idx = 0;
String packed;
};
/*********************************************************************************************\
* ControllerDelayHandlerStruct
\*********************************************************************************************/
template<class T>
struct ControllerDelayHandlerStruct {
ControllerDelayHandlerStruct() :
lastSend(0),
minTimeBetweenMessages(CONTROLLER_DELAY_QUEUE_DELAY_DFLT),
max_queue_depth(CONTROLLER_DELAY_QUEUE_DEPTH_DFLT),
attempt(0),
max_retries(CONTROLLER_DELAY_QUEUE_RETRY_DFLT),
delete_oldest(false),
must_check_reply(false) {}
void configureControllerSettings(const ControllerSettingsStruct& settings) {
minTimeBetweenMessages = settings.MinimalTimeBetweenMessages;
max_queue_depth = settings.MaxQueueDepth;
max_retries = settings.MaxRetry;
delete_oldest = settings.DeleteOldest;
must_check_reply = settings.MustCheckReply;
// Set some sound limits when not configured
if (max_queue_depth == 0) { max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; }
if (max_retries == 0) { max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; }
if (minTimeBetweenMessages == 0) { minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; }
// No less than 10 msec between messages.
if (minTimeBetweenMessages < 10) { minTimeBetweenMessages = 10; }
}
bool queueFull(const T& element) const {
if (sendQueue.size() >= max_queue_depth) { return true; }
// Number of elements is not exceeding the limit, check memory
int freeHeap = ESP.getFreeHeap();
if (freeHeap > 5000) { return false; // Memory is not an issue.
}
#ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
String log = "Controller-";
log += element.controller_idx + 1;
log += " : Memory used: ";
log += getQueueMemorySize();
log += " bytes ";
log += sendQueue.size();
log += " items ";
log += freeHeap;
log += " free";
addLog(LOG_LEVEL_DEBUG, log);
}
#endif // ifndef BUILD_NO_DEBUG
return true;
}
// Try to add to the queue, if permitted by "delete_oldest"
// Return false when no item was added.
bool addToQueue(const T& element) {
if (delete_oldest) {
// Force add to the queue.
// If max buffer is reached, the oldest in the queue (first to be served) will be removed.
while (queueFull(element)) {
sendQueue.pop_front();
}
sendQueue.emplace_back(element);
return true;
}
if (!queueFull(element)) {
sendQueue.emplace_back(element);
return true;
}
#ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
String log = get_formatted_Controller_number(element.controller_idx);
log += " : queue full";
addLog(LOG_LEVEL_DEBUG, log);
}
#endif // ifndef BUILD_NO_DEBUG
return false;
}
// Get the next element.
// Remove front element when max_retries is reached.
T* getNext() {
if (sendQueue.empty()) { return NULL; }
if (attempt > max_retries) {
sendQueue.pop_front();
attempt = 0;
if (sendQueue.empty()) { return NULL; }
}
return &sendQueue.front();
}
// Mark as processed and return time to schedule for next process.
// Return 0 when nothing to process.
// @param remove_from_queue indicates whether the elements should be removed from the queue.
unsigned long markProcessed(bool remove_from_queue) {
if (sendQueue.empty()) { return 0; }
if (remove_from_queue) {
sendQueue.pop_front();
attempt = 0;
} else {
++attempt;
}
lastSend = millis();
return getNextScheduleTime();
}
unsigned long getNextScheduleTime() const {
if (sendQueue.empty()) { return 0; }
unsigned long nextTime = lastSend + minTimeBetweenMessages;
if (timePassedSince(nextTime) > 0) {
nextTime = millis();
}
if (nextTime == 0) { nextTime = 1; // Just to make sure it will be executed
}
return nextTime;
}
size_t getQueueMemorySize() const {
size_t totalSize = 0;
for (auto it = sendQueue.begin(); it != sendQueue.end(); ++it) {
totalSize += it->getSize();
}
return totalSize;
}
std::list<T> sendQueue;
unsigned long lastSend;
unsigned int minTimeBetweenMessages;
byte max_queue_depth;
byte attempt;
byte max_retries;
bool delete_oldest;
bool must_check_reply;
};
ControllerDelayHandlerStruct<MQTT_queue_element> MQTTDelayHandler;
// Uncrustify must not be used on macros, so turn it off.
// *INDENT-OFF*
// This macro defines the code needed to create the 'process_c##NNN##_delay_queue()'
// function and all needed objects and forward declarations.
// It is a macro to prevent common typo errors.
// This function will perform the (re)scheduling and mark if it is processed (and can be removed)
// The controller itself must implement the 'do_process_c004_delay_queue' function to actually
// send the data.
// Its return value must state whether it can be marked 'Processed'.
// N.B. some controllers only can send one value per iteration, so a returned "false" can mean it
// was still successful. The controller should keep track of the last value sent
// in the element stored in the queue.
#define DEFINE_Cxxx_DELAY_QUEUE_MACRO(NNN, M) \
ControllerDelayHandlerStruct<C##NNN##_queue_element>C##NNN##_DelayHandler; \
bool do_process_c##NNN##_delay_queue(int controller_number, \
const C##NNN##_queue_element & element, \
ControllerSettingsStruct & ControllerSettings); \
void process_c##NNN##_delay_queue() { \
C##NNN##_queue_element *element(C##NNN##_DelayHandler.getNext()); \
if (element == NULL) return; \
MakeControllerSettings (ControllerSettings); \
LoadControllerSettings(element->controller_idx, ControllerSettings); \
C##NNN##_DelayHandler.configureControllerSettings(ControllerSettings); \
if (!WiFiConnected(10)) { \
scheduleNextDelayQueue(TIMER_C##NNN##_DELAY_QUEUE, C##NNN##_DelayHandler.getNextScheduleTime()); \
return; \
} \
START_TIMER; \
C##NNN##_DelayHandler.markProcessed(do_process_c##NNN##_delay_queue(M, *element, ControllerSettings)); \
STOP_TIMER(C##NNN##_DELAY_QUEUE); \
scheduleNextDelayQueue(TIMER_C##NNN##_DELAY_QUEUE, C##NNN##_DelayHandler.getNextScheduleTime()); \
}
// Define the function wrappers to handle the calling to Cxxx_DelayHandler etc.
// If someone knows how to add leading zeros in macros, please be my guest :)
#ifdef USES_C001
DEFINE_Cxxx_DELAY_QUEUE_MACRO(001, 1)
#endif // ifdef USES_C001
#ifdef USES_C003
DEFINE_Cxxx_DELAY_QUEUE_MACRO(003, 3)
#endif // ifdef USES_C003
#ifdef USES_C004
DEFINE_Cxxx_DELAY_QUEUE_MACRO(004, 4)
#endif // ifdef USES_C004
#ifdef USES_C007
DEFINE_Cxxx_DELAY_QUEUE_MACRO(007, 7)
#endif // ifdef USES_C007
#ifdef USES_C008
DEFINE_Cxxx_DELAY_QUEUE_MACRO(008, 8)
#endif // ifdef USES_C008
#ifdef USES_C009
DEFINE_Cxxx_DELAY_QUEUE_MACRO(009, 9)
#endif // ifdef USES_C009
#ifdef USES_C010
DEFINE_Cxxx_DELAY_QUEUE_MACRO(010, 10)
#endif // ifdef USES_C010
#ifdef USES_C011
DEFINE_Cxxx_DELAY_QUEUE_MACRO(011, 11)
#endif // ifdef USES_C011
#ifdef USES_C012
DEFINE_Cxxx_DELAY_QUEUE_MACRO(012, 12)
#endif // ifdef USES_C012
/*
#ifdef USES_C013
DEFINE_Cxxx_DELAY_QUEUE_MACRO(013, 13)
#endif
*/
/*
#ifdef USES_C014
DEFINE_Cxxx_DELAY_QUEUE_MACRO(014, 14)
#endif
*/
#ifdef USES_C015
DEFINE_Cxxx_DELAY_QUEUE_MACRO(015, 15)
#endif // ifdef USES_C015
#ifdef USES_C016
DEFINE_Cxxx_DELAY_QUEUE_MACRO(016, 16)
#endif // ifdef USES_C016
#ifdef USES_C017
DEFINE_Cxxx_DELAY_QUEUE_MACRO(017, 17)
#endif // ifdef USES_C017
#ifdef USES_C018
DEFINE_Cxxx_DELAY_QUEUE_MACRO(018, 18)
#endif
/*
#ifdef USES_C019
DEFINE_Cxxx_DELAY_QUEUE_MACRO(019, 19)
#endif
*/
/*
#ifdef USES_C020
DEFINE_Cxxx_DELAY_QUEUE_MACRO(020, 20)
#endif
*/
// When extending this, also extend in Scheduler.ino:
// void process_interval_timer(unsigned long id, unsigned long lasttimer)
// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again.
// *INDENT-ON*
/*********************************************************************************************\
* Helper functions used in a number of controllers
\*********************************************************************************************/
bool safeReadStringUntil(Stream & input,
String & str,
char terminator,
unsigned int maxSize = 1024,
unsigned int timeout = 1000)
{
int c;
const unsigned long start = millis();
const unsigned long timer = start + timeout;
unsigned long backgroundtasks_timer = start + 10;
str = "";
do {
// read character
if (input.available()) {
c = input.read();
if (c >= 0) {
// found terminator, we're ok
if (c == terminator) {
return true;
}
// found character, add to string
else {
str += char(c);
// string at max size?
if (str.length() >= maxSize) {
addLog(LOG_LEVEL_ERROR, F("Not enough bufferspace to read all input data!"));
return false;
}
}
}
// We must run the backgroundtasks every now and then.
if (timeOutReached(backgroundtasks_timer)) {
backgroundtasks_timer += 10;
backgroundtasks();
} else {
delay(0);
}
} else {
delay(0);
}
} while (!timeOutReached(timer));
addLog(LOG_LEVEL_ERROR, F("Timeout while reading input data!"));
return false;
}
bool valid_controller_number(int controller_number) {
if (controller_number < 0) { return false; }
return true;
// return getProtocolIndex(controller_number) <= protocolCount;
}
String get_formatted_Controller_number(int controller_number) {
if (!valid_controller_number(controller_number)) {
return F("C---");
}
String result = F("C");
if (controller_number < 100) { result += '0'; }
if (controller_number < 10) { result += '0'; }
result += controller_number;
return result;
}
String get_auth_header(int controller_index) {
String authHeader = "";
if (controller_index < CONTROLLER_MAX) {
if ((SecuritySettings.ControllerUser[controller_index][0] != 0) &&
(SecuritySettings.ControllerPassword[controller_index][0] != 0))
{
base64 encoder;
String auth = SecuritySettings.ControllerUser[controller_index];
auth += ":";
auth += SecuritySettings.ControllerPassword[controller_index];
authHeader = F("Authorization: Basic ");
authHeader += encoder.encode(auth);
authHeader += F(" \r\n");
}
} else {
addLog(LOG_LEVEL_ERROR, F("Invalid controller index"));
}
return authHeader;
}
String get_user_agent_request_header_field() {
static unsigned int agent_size = 20;
String request;
request.reserve(agent_size);
request = F("User-Agent: ");
request += F("ESP Easy/");
request += BUILD;
request += '/';
request += String(CRCValues.compileDate);
request += ' ';
request += String(CRCValues.compileTime);
request += "\r\n";
agent_size = request.length();
return request;
}
String do_create_http_request(
const String& hostportString,
const String& method, const String& uri,
const String& auth_header, const String& additional_options,
int content_length) {
int estimated_size = hostportString.length() + method.length()
+ uri.length() + auth_header.length()
+ additional_options.length()
+ 42;
if (content_length >= 0) { estimated_size += 25; }
String request;
request.reserve(estimated_size);
request += method;
request += ' ';
if (!uri.startsWith("/")) { request += '/'; }
request += uri;
request += F(" HTTP/1.1");
request += "\r\n";
if (content_length >= 0) {
request += F("Content-Length: ");
request += content_length;
request += "\r\n";
}
request += F("Host: ");
request += hostportString;
request += "\r\n";
request += auth_header;
request += additional_options;
request += get_user_agent_request_header_field();
request += F("Connection: close\r\n");
request += "\r\n";
#ifndef BUILD_NO_DEBUG
addLog(LOG_LEVEL_DEBUG, request);
#endif // ifndef BUILD_NO_DEBUG
return request;
}
String do_create_http_request(
const String& hostportString,
const String& method, const String& uri) {
return do_create_http_request(hostportString, method, uri,
"", // auth_header
"", // additional_options
-1 // content_length
);
}
String do_create_http_request(
int controller_number, ControllerSettingsStruct& ControllerSettings,
const String& method, const String& uri,
int content_length) {
const bool defaultport = ControllerSettings.Port == 0 || ControllerSettings.Port == 80;
return do_create_http_request(
defaultport ? ControllerSettings.getHost() : ControllerSettings.getHostPortString(),
method,
uri,
"", // auth_header
"", // additional_options
content_length);
}
String create_http_request_auth(
int controller_number, int controller_index, ControllerSettingsStruct& ControllerSettings,
const String& method, const String& uri,
int content_length) {
const bool defaultport = ControllerSettings.Port == 0 || ControllerSettings.Port == 80;
return do_create_http_request(
defaultport ? ControllerSettings.getHost() : ControllerSettings.getHostPortString(),
method,
uri,
get_auth_header(controller_index),
"", // additional_options
content_length);
}
String create_http_get_request(int controller_number, ControllerSettingsStruct& ControllerSettings,
const String& uri) {
return do_create_http_request(controller_number, ControllerSettings, F("GET"), uri, -1);
}
String create_http_request_auth(int controller_number, int controller_index, ControllerSettingsStruct& ControllerSettings,
const String& method, const String& uri) {
return create_http_request_auth(controller_number, controller_index, ControllerSettings, method, uri, -1);
}
#ifndef BUILD_NO_DEBUG
void log_connecting_to(const String& prefix, int controller_number, ControllerSettingsStruct& ControllerSettings) {
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
String log = prefix;
log += get_formatted_Controller_number(controller_number);
log += F(" connecting to ");
log += ControllerSettings.getHostPortString();
addLog(LOG_LEVEL_DEBUG, log);
}
}
#endif // ifndef BUILD_NO_DEBUG
void log_connecting_fail(const String& prefix, int controller_number, ControllerSettingsStruct& ControllerSettings) {
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
String log = prefix;
log += get_formatted_Controller_number(controller_number);
log += F(" connection failed (");
log += connectionFailures;
log += F("/");
log += Settings.ConnectionFailuresThreshold;
log += F(")");
addLog(LOG_LEVEL_ERROR, log);
}
}
bool count_connection_results(bool success, const String& prefix, int controller_number, ControllerSettingsStruct& ControllerSettings) {
if (!success)
{
connectionFailures++;
log_connecting_fail(prefix, controller_number, ControllerSettings);
return false;
}
statusLED(true);
if (connectionFailures) {
connectionFailures--;
}
return true;
}
bool try_connect_host(int controller_number, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings) {
START_TIMER;
if (!WiFiConnected()) return false;
client.setTimeout(ControllerSettings.ClientTimeout);
#ifndef BUILD_NO_DEBUG
log_connecting_to(F("UDP : "), controller_number, ControllerSettings);
#endif // ifndef BUILD_NO_DEBUG
bool success = ControllerSettings.beginPacket(client) != 0;
const bool result = count_connection_results(
success,
F("UDP : "), controller_number, ControllerSettings);
STOP_TIMER(TRY_CONNECT_HOST_UDP);
return result;
}
bool try_connect_host(int controller_number, WiFiClient& client, ControllerSettingsStruct& ControllerSettings) {
START_TIMER;
if (!WiFiConnected()) return false;
// Use WiFiClient class to create TCP connections
client.setTimeout(ControllerSettings.ClientTimeout);
#ifndef BUILD_NO_DEBUG
log_connecting_to(F("HTTP : "), controller_number, ControllerSettings);
#endif // ifndef BUILD_NO_DEBUG
bool success = ControllerSettings.connectToHost(client);
const bool result = count_connection_results(
success,
F("HTTP : "), controller_number, ControllerSettings);
STOP_TIMER(TRY_CONNECT_HOST_TCP);
return result;
}
// Use "client.available() || client.connected()" to read all lines from slow servers.
// See: https://github.com/esp8266/Arduino/pull/5113
// https://github.com/esp8266/Arduino/pull/1829
bool client_available(WiFiClient& client) {
delay(0);
return client.available() || client.connected();
}
bool send_via_http(const String& logIdentifier, WiFiClient& client, const String& postStr, bool must_check_reply) {
bool success = !must_check_reply;
// This will send the request to the server
byte written = client.print(postStr);
// as of 2018/11/01 the print function only returns one byte (upd to 256 chars sent). However if the string sent can be longer than this
// therefore we calculate modulo 256.
// see discussion here https://github.com/letscontrolit/ESPEasy/pull/1979
// and implementation here
// https://github.com/esp8266/Arduino/blob/561426c0c77e9d05708f2c4bf2a956d3552a3706/libraries/ESP8266WiFi/src/include/ClientContext.h#L437-L467
// this needs to be adjusted if the WiFiClient.print method changes.
if (written != (postStr.length() % 256)) {
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
String log = F("HTTP : ");
log += logIdentifier;
log += F(" Error: could not write to client (");
log += written;
log += "/";
log += postStr.length();
log += ")";
addLog(LOG_LEVEL_ERROR, log);
}
success = false;
}
#ifndef BUILD_NO_DEBUG
else {
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
String log = F("HTTP : ");
log += logIdentifier;
log += F(" written to client (");
log += written;
log += "/";
log += postStr.length();
log += ")";
addLog(LOG_LEVEL_DEBUG, log);
}
}
#endif // ifndef BUILD_NO_DEBUG
if (must_check_reply) {
unsigned long timer = millis() + 200;
while (!client_available(client)) {
if (timeOutReached(timer)) { return false; }
delay(1);
}
// Read all the lines of the reply from server and print them to Serial
while (client_available(client) && !success) {
// String line = client.readStringUntil('\n');
String line;
safeReadStringUntil(client, line, '\n');
#ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) {
if (line.length() > 80) {
addLog(LOG_LEVEL_DEBUG_MORE, line.substring(0, 80));
} else {
addLog(LOG_LEVEL_DEBUG_MORE, line);
}
}
#endif // ifndef BUILD_NO_DEBUG
if (line.startsWith(F("HTTP/1.1 2")))
{
success = true;
// Leave this debug info in the build, regardless of the
// BUILD_NO_DEBUG flags.
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
String log = F("HTTP : ");
log += logIdentifier;
log += F(" Success! ");
log += line;
addLog(LOG_LEVEL_DEBUG, log);
}
} else if (line.startsWith(F("HTTP/1.1 4"))) {
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
String log = F("HTTP : ");
log += logIdentifier;
log += F(" Error: ");
log += line;
addLog(LOG_LEVEL_ERROR, log);
}
#ifndef BUILD_NO_DEBUG
addLog(LOG_LEVEL_DEBUG_MORE, postStr);
#endif // ifndef BUILD_NO_DEBUG
}
delay(0);
}
}
#ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
String log = F("HTTP : ");
log += logIdentifier;
log += F(" closing connection");
addLog(LOG_LEVEL_DEBUG, log);
}
#endif // ifndef BUILD_NO_DEBUG
client.flush();
client.stop();
return success;
}
bool send_via_http(int controller_number, WiFiClient& client, const String& postStr, bool must_check_reply) {
return send_via_http(get_formatted_Controller_number(controller_number), client, postStr, must_check_reply);
}
#endif // CPLUGIN_HELPER_H