446 lines
12 KiB
C++
446 lines
12 KiB
C++
/*
|
|
MIT License
|
|
|
|
Copyright (c) 2021-22 Magnus
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
#if defined(ESP8266)
|
|
#include <ESP8266HTTPClient.h>
|
|
#include <ESP8266WiFi.h>
|
|
#else // defined (ESP32)
|
|
#include <HTTPClient.h>
|
|
#include <WiFi.h>
|
|
#endif
|
|
|
|
#include <config.hpp>
|
|
#include <gyro.hpp>
|
|
#include <helper.hpp>
|
|
#include <main.hpp>
|
|
#include <tempsensor.hpp>
|
|
#include <wifi.hpp>
|
|
|
|
// tcp cleanup, to avoid memory crash.
|
|
struct tcp_pcb;
|
|
extern struct tcp_pcb* tcp_tw_pcbs;
|
|
extern "C" void tcp_abort(struct tcp_pcb* pcb);
|
|
void tcp_cleanup() {
|
|
while (tcp_tw_pcbs) tcp_abort(tcp_tw_pcbs);
|
|
}
|
|
|
|
void checkResetReason() {
|
|
#if defined(ESP8266)
|
|
rst_info* _rinfo;
|
|
_rinfo = ESP.getResetInfoPtr();
|
|
|
|
Log.notice(F("HELP: Last reset cause %d" CR), _rinfo->exccause);
|
|
|
|
if (_rinfo->exccause > 0) {
|
|
char s[120];
|
|
snprintf(&s[0], sizeof(s),
|
|
"HELP: Exception (%d) reason=%d epc1=0x%08x epc2=0x%08x "
|
|
"epc3=0x%08x execvaddr=0x%08x depc=0x%08x",
|
|
_rinfo->exccause, _rinfo->reason, _rinfo->epc1, _rinfo->epc2,
|
|
_rinfo->epc3, _rinfo->excvaddr, _rinfo->depc);
|
|
writeErrorLog(&s[0]);
|
|
}
|
|
#else // defined (ESP32)
|
|
#warning "Todo: Handle reset reasons on ESP32"
|
|
#endif
|
|
}
|
|
|
|
void writeErrorLog(const char* format, ...) {
|
|
File f = LittleFS.open(ERR_FILENAME, "a");
|
|
|
|
if (f && f.size() > ERR_FILEMAXSIZE) {
|
|
f.close();
|
|
LittleFS.remove(ERR_FILENAME2);
|
|
LittleFS.rename(ERR_FILENAME, ERR_FILENAME2);
|
|
f = LittleFS.open(ERR_FILENAME, "a");
|
|
}
|
|
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
char buf[120];
|
|
vsnprintf(&buf[0], sizeof(buf), format, arg);
|
|
va_end(arg);
|
|
Log.errorln(&buf[0]);
|
|
|
|
if (f) {
|
|
#if defined(ESP8266)
|
|
f.write(&buf[0], strlen(&buf[0]));
|
|
#else // ESP32
|
|
f.write((unsigned char*)&buf[0], strlen(&buf[0]));
|
|
#endif
|
|
f.println();
|
|
f.close();
|
|
} else {
|
|
Log.warning(F("HELP: Failed to open error log." CR));
|
|
}
|
|
}
|
|
|
|
double convertToPlato(double sg) {
|
|
if (sg) return 259 - (259 / sg);
|
|
return 0;
|
|
}
|
|
|
|
double convertToSG(double plato) { return 259 / (259 - plato); }
|
|
|
|
float convertCtoF(float c) { return (c * 1.8) + 32.0; }
|
|
|
|
float convertFtoC(float f) { return (f - 32.0) / 1.8; }
|
|
|
|
FloatHistoryLog::FloatHistoryLog(String fName) {
|
|
_fName = fName;
|
|
|
|
File runFile = LittleFS.open(_fName, "r");
|
|
if (runFile) {
|
|
for (int i = 0; i < 10; i++) {
|
|
_runTime[i] = runFile.readStringUntil('\n').toFloat();
|
|
if (_runTime[i]) {
|
|
_average += _runTime[i];
|
|
_count++;
|
|
}
|
|
}
|
|
runFile.close();
|
|
_average = _average / _count;
|
|
}
|
|
}
|
|
|
|
void FloatHistoryLog::addEntry(float time) {
|
|
for (int i = (10 - 1); i > 0; i--) {
|
|
_runTime[i] = _runTime[i - 1];
|
|
}
|
|
_runTime[0] = time;
|
|
save();
|
|
}
|
|
|
|
void FloatHistoryLog::save() {
|
|
File runFile = LittleFS.open(_fName, "w");
|
|
if (runFile) {
|
|
for (int i = 0; i < 10; i++) {
|
|
runFile.println(_runTime[i], 2);
|
|
}
|
|
runFile.close();
|
|
}
|
|
}
|
|
|
|
void printHeap(String prefix) {
|
|
#if defined(ESP8266)
|
|
Log.notice(
|
|
F("%s: Free-heap %d kb, Heap-rag %d %%, Max-block %d kb Stack=%d b." CR),
|
|
prefix.c_str(), ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
|
|
ESP.getMaxFreeBlockSize() / 1024, ESP.getFreeContStack());
|
|
// Log.notice(F("%s: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
|
|
// prefix.c_str(), ESP.getFreeHeap() / 1024,
|
|
// ESP.getHeapFragmentation(), ESP.getFreeSketchSpace() / 1024);
|
|
#else // defined (ESP32)
|
|
Log.verbose(F("HELP: Heap %d kb, FreeSketch %d kb." CR),
|
|
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024);
|
|
#endif
|
|
}
|
|
|
|
void deepSleep(int t) {
|
|
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
|
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t);
|
|
#endif
|
|
uint32_t wake = t * 1000000;
|
|
ESP.deepSleep(wake);
|
|
}
|
|
|
|
void printBuildOptions() {
|
|
Log.notice(F("Build options: %s (%s) LOGLEVEL %d "
|
|
#ifdef SKIP_SLEEPMODE
|
|
"SKIP_SLEEP "
|
|
#endif
|
|
#ifdef COLLECT_PERFDATA
|
|
"PERFDATA "
|
|
#endif
|
|
CR),
|
|
CFG_APPVER, CFG_GITREV, LOG_LEVEL);
|
|
}
|
|
|
|
SerialDebug::SerialDebug(const uint32_t serialSpeed) {
|
|
// Start serial with auto-detected rate (default to defined BAUD)
|
|
//Serial.flush();
|
|
Serial.begin(serialSpeed);
|
|
Serial.println("Serial connection established");
|
|
|
|
getLog()->begin(LOG_LEVEL, &Serial, true);
|
|
getLog()->setPrefix(printTimestamp);
|
|
getLog()->notice(F("SDBG: Serial logging started at %u." CR), serialSpeed);
|
|
}
|
|
|
|
void printTimestamp(Print* _logOutput, int _logLevel) {
|
|
char c[12];
|
|
snprintf(c, sizeof(c), "%10lu ", millis());
|
|
_logOutput->print(c);
|
|
}
|
|
|
|
void BatteryVoltage::read() {
|
|
// The analog pin can only handle 3.3V maximum voltage so we need to reduce
|
|
// the voltage (from max 5V)
|
|
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
|
|
int v = analogRead(PIN_A0);
|
|
|
|
// An ESP8266 has a ADC range of 0-1023 and a maximum voltage of 3.3V
|
|
// An ESP32 has an ADC range of 0-4095 and a maximum voltage of 3.3V
|
|
|
|
#if defined(ESP8266)
|
|
_batteryLevel = ((3.3 / 1023) * v) * factor;
|
|
#else // defined (ESP32)
|
|
_batteryLevel = ((3.3 / 4095) * v) * factor;
|
|
#endif
|
|
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
|
Log.verbose(
|
|
F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR),
|
|
factor, v, _batteryLevel);
|
|
#endif
|
|
}
|
|
|
|
#if defined(COLLECT_PERFDATA)
|
|
|
|
PerfLogging myPerfLogging;
|
|
|
|
void PerfLogging::clear() {
|
|
// Clear the measurements
|
|
if (first == 0) return;
|
|
|
|
PerfEntry* pe = first;
|
|
|
|
do {
|
|
pe->max = 0;
|
|
pe->start = 0;
|
|
pe->end = 0;
|
|
pe->mA = 0;
|
|
pe->V = 0;
|
|
pe = pe->next;
|
|
} while (pe != 0);
|
|
}
|
|
|
|
void PerfLogging::start(const char* key) {
|
|
PerfEntry* pe = add(key);
|
|
pe->start = millis();
|
|
}
|
|
|
|
void PerfLogging::stop(const char* key) {
|
|
PerfEntry* pe = find(key);
|
|
|
|
if (pe != 0) {
|
|
pe->end = millis();
|
|
|
|
uint32_t t = pe->end - pe->start;
|
|
|
|
if (t > pe->max) pe->max = t;
|
|
}
|
|
}
|
|
|
|
void PerfLogging::print() {
|
|
PerfEntry* pe = first;
|
|
|
|
while (pe != 0) {
|
|
Log.notice(F("PERF: %s %ums" CR), pe->key, pe->max);
|
|
pe = pe->next;
|
|
}
|
|
}
|
|
|
|
void PerfLogging::pushInflux() {
|
|
if (!myConfig.isInfluxDb2Active()) return;
|
|
|
|
if (myConfig.isInfluxSSL()) {
|
|
Log.warning(
|
|
F("PERF: InfluxDB2 with SSL is not supported when pushing performance "
|
|
"data, skipping" CR));
|
|
return;
|
|
}
|
|
|
|
WiFiClient wifi;
|
|
HTTPClient http;
|
|
String serverPath =
|
|
String(myConfig.getInfluxDb2PushUrl()) +
|
|
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
|
|
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
|
|
|
http.begin(wifi, serverPath);
|
|
|
|
// Create body for influxdb2, format used
|
|
// key,host=mdns value=0.0
|
|
String body;
|
|
body.reserve(500);
|
|
|
|
// Create the payload with performance data.
|
|
// ------------------------------------------------------------------------------------------
|
|
PerfEntry* pe = first;
|
|
char buf[150];
|
|
snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
|
|
myConfig.getID());
|
|
body += &buf[0];
|
|
|
|
while (pe != 0) {
|
|
if (pe->max) {
|
|
if (pe->next)
|
|
snprintf(&buf[0], sizeof(buf), "%s=%u,", pe->key, pe->max);
|
|
else
|
|
snprintf(&buf[0], sizeof(buf), "%s=%u", pe->key, pe->max);
|
|
|
|
body += &buf[0];
|
|
}
|
|
pe = pe->next;
|
|
}
|
|
|
|
// Create the payload with debug data for validating sensor stability
|
|
// ------------------------------------------------------------------------------------------
|
|
snprintf(&buf[0], sizeof(buf), "\ndebug,host=%s,device=%s ",
|
|
myConfig.getMDNS(), myConfig.getID());
|
|
body += &buf[0];
|
|
#if defined(ESP8266)
|
|
snprintf(&buf[0], sizeof(buf),
|
|
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp="
|
|
"%.2f,heap=%d,heap-frag=%d,heap-max=%d,stack=%d",
|
|
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
|
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
|
myGyro.getSensorTempC(),
|
|
myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(),
|
|
ESP.getHeapFragmentation(), ESP.getMaxFreeBlockSize(),
|
|
ESP.getFreeContStack());
|
|
#else // defined (ESP32)
|
|
snprintf(&buf[0], sizeof(buf),
|
|
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp="
|
|
"%.2f,heap=%d,heap-frag=%d,heap-max=%d",
|
|
myGyro.getAngle(), myGyro.getLastGyroData().ax,
|
|
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
|
|
myGyro.getSensorTempC(),
|
|
myTempSensor.getTempC(myConfig.isGyroTemp()), ESP.getFreeHeap(), 0,
|
|
ESP.getMaxAllocHeap());
|
|
#endif
|
|
|
|
body += &buf[0];
|
|
|
|
#if LOG_LEVEL == 6 && !defined(HELPER_DISABLE_LOGGING)
|
|
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
|
|
Log.verbose(F("PERF: data %s." CR), body.c_str());
|
|
#endif
|
|
|
|
// Send HTTP POST request
|
|
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
|
http.addHeader(F("Authorization"), auth.c_str());
|
|
http.setTimeout(myAdvancedConfig.getPushTimeout());
|
|
int httpResponseCode = http.POST(body);
|
|
|
|
if (httpResponseCode == 204) {
|
|
#if !defined(HELPER_DISABLE_LOGGING)
|
|
Log.notice(
|
|
F("PERF: InfluxDB2 push performance data successful, response=%d" CR),
|
|
httpResponseCode);
|
|
#endif
|
|
} else {
|
|
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR),
|
|
httpResponseCode);
|
|
}
|
|
|
|
http.end();
|
|
wifi.stop();
|
|
tcp_cleanup();
|
|
}
|
|
|
|
#endif // COLLECT_PERFDATA
|
|
|
|
char* convertFloatToString(float f, char* buffer, int dec) {
|
|
dtostrf(f, 6, dec, buffer);
|
|
return buffer;
|
|
}
|
|
|
|
float reduceFloatPrecision(float f, int dec) {
|
|
char buffer[5];
|
|
dtostrf(f, 6, dec, &buffer[0]);
|
|
return atof(&buffer[0]);
|
|
}
|
|
|
|
// urlencode
|
|
// https://circuits4you.com/2019/03/21/esp8266-url-encode-decode-example/
|
|
String urlencode(String str) {
|
|
String encodedString;
|
|
encodedString.reserve(str.length() * 2);
|
|
encodedString = "";
|
|
char c;
|
|
char code0;
|
|
char code1;
|
|
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
|
c = str.charAt(i);
|
|
if (isalnum(c)) {
|
|
encodedString += c;
|
|
} else {
|
|
code1 = (c & 0xf) + '0';
|
|
if ((c & 0xf) > 9) {
|
|
code1 = (c & 0xf) - 10 + 'A';
|
|
}
|
|
c = (c >> 4) & 0xf;
|
|
code0 = c + '0';
|
|
if (c > 9) {
|
|
code0 = c - 10 + 'A';
|
|
}
|
|
encodedString += '%';
|
|
encodedString += code0;
|
|
encodedString += code1;
|
|
}
|
|
}
|
|
// Log.verbose(F("HELP: encode=%s" CR), encodedString.c_str());
|
|
return encodedString;
|
|
}
|
|
|
|
unsigned char h2int(char c) {
|
|
if (c >= '0' && c <= '9') {
|
|
return ((unsigned char)c - '0');
|
|
}
|
|
if (c >= 'a' && c <= 'f') {
|
|
return ((unsigned char)c - 'a' + 10);
|
|
}
|
|
if (c >= 'A' && c <= 'F') {
|
|
return ((unsigned char)c - 'A' + 10);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
String urldecode(String str) {
|
|
String encodedString;
|
|
encodedString.reserve(str.length());
|
|
encodedString = "";
|
|
char c;
|
|
char code0;
|
|
char code1;
|
|
for (int i = 0; i < static_cast<int>(str.length()); i++) {
|
|
c = str.charAt(i);
|
|
if (c == '%') {
|
|
i++;
|
|
code0 = str.charAt(i);
|
|
i++;
|
|
code1 = str.charAt(i);
|
|
c = (h2int(code0) << 4) | h2int(code1);
|
|
encodedString += c;
|
|
} else {
|
|
encodedString += c;
|
|
}
|
|
}
|
|
|
|
// Log.verbose(F("HELP: decode=%s" CR), encodedString.c_str());
|
|
return encodedString;
|
|
}
|
|
|
|
// EOF
|