Applied precommit cpp checks

This commit is contained in:
Magnus Persson 2022-01-07 10:13:08 +01:00
parent 88bd971b73
commit ed53182c29
22 changed files with 3780 additions and 3439 deletions

View File

@ -1,186 +1,203 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "src/calc.h" #include <config.h>
#include <tinyexpr.h> #include <curveFitting.h>
#include <curveFitting.h> #include <tinyexpr.h>
#include "src/helper.h"
#include "src/config.h" #include <calc.hpp>
#include "src/tempsensor.h" #include <helper.hpp>
#include <tempsensor.hpp>
#define FORMULA_MAX_DEVIATION 1.5
#define FORMULA_MAX_DEVIATION 1.5
//
// Use values to derive a formula //
// // Use values to derive a formula
int createFormula(RawFormulaData& fd, char *formulaBuffer, int formulaBufferSize, int order) { //
int createFormula(RawFormulaData &fd, char *formulaBuffer,
int noAngles = 0; int formulaBufferSize, int order) {
int noAngles = 0;
// Check how many valid values we have got
if ( fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0 && fd.a[4] > 0 ) // Check how many valid values we have got
noAngles = 5; if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0 && fd.a[4] > 0)
else if ( fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0 ) noAngles = 5;
noAngles = 4; else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0)
else if ( fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 ) noAngles = 4;
noAngles = 3; else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0)
noAngles = 3;
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Trying to create formula using order = %d, found %d angles" CR), order, noAngles); #if LOG_LEVEL == 6
#endif Log.verbose(
F("CALC: Trying to create formula using order = %d, found %d angles" CR),
if ( !noAngles ) { order, noAngles);
Log.error(F("CALC: Not enough values for deriving formula" CR)); #endif
return ERR_FORMULA_NOTENOUGHVALUES;
} else { if (!noAngles) {
Log.error(F("CALC: Not enough values for deriving formula" CR));
double coeffs[order+1]; return ERR_FORMULA_NOTENOUGHVALUES;
int ret = fitCurve(order, noAngles, fd.a, fd.g, sizeof(coeffs)/sizeof(double), coeffs); } else {
double coeffs[order + 1];
// Returned value is 0 if no error int ret = fitCurve(order, noAngles, fd.a, fd.g,
if ( ret == 0 ) { sizeof(coeffs) / sizeof(double), coeffs);
#if LOG_LEVEL == 6 // Returned value is 0 if no error
Log.verbose(F("CALC: Finshied processing data points." CR)); if (ret == 0) {
#endif #if LOG_LEVEL == 6
Log.verbose(F("CALC: Finshied processing data points." CR));
// Print the formula based on 'order' #endif
if ( order == 4 ) {
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^4+%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4]); // Print the formula based on 'order'
} else if ( order == 3 ) { if (order == 4) {
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1], coeffs[2], coeffs[3]); snprintf(formulaBuffer, formulaBufferSize,
} else if ( order == 2 ) { "%.8f*tilt^4+%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f",
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1], coeffs[2]); coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4]);
} else { // order == 1 } else if (order == 3) {
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt+%.8f", coeffs[0], coeffs[1]); snprintf(formulaBuffer, formulaBufferSize,
} "%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1],
coeffs[2], coeffs[3]);
#if LOG_LEVEL == 6 } else if (order == 2) {
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer ); snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^2+%.8f*tilt+%.8f",
#endif coeffs[0], coeffs[1], coeffs[2]);
} else { // order == 1
bool valid = true; snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt+%.8f", coeffs[0],
coeffs[1]);
for ( int i = 0; i < 5; i++ ) { }
if ( fd.a[i] == 0 && valid )
break; #if LOG_LEVEL == 6
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer);
double g = calculateGravity(fd.a[i], 0, formulaBuffer); #endif
double dev = (g-fd.g[i]) < 0 ? (fd.g[i]-g) : (g-fd.g[i]);
bool valid = true;
// If the deviation is more than 2 degress we mark it as failed.
if ( dev*1000 > FORMULA_MAX_DEVIATION ) for (int i = 0; i < 5; i++) {
valid = false; if (fd.a[i] == 0 && valid) break;
}
double g = calculateGravity(fd.a[i], 0, formulaBuffer);
if ( !valid ) { double dev = (g - fd.g[i]) < 0 ? (fd.g[i] - g) : (g - fd.g[i]);
Log.error(F("CALC: Deviation to large, formula rejected." CR));
return ERR_FORMULA_UNABLETOFFIND; // If the deviation is more than 2 degress we mark it as failed.
} if (dev * 1000 > FORMULA_MAX_DEVIATION) valid = false;
}
Log.info(F("CALC: Found formula '%s'." CR), formulaBuffer);
return 0; if (!valid) {
} Log.error(F("CALC: Deviation to large, formula rejected." CR));
} return ERR_FORMULA_UNABLETOFFIND;
}
Log.error(F("CALC: Internal error finding formula." CR));
return ERR_FORMULA_INTERNAL; Log.info(F("CALC: Found formula '%s'." CR), formulaBuffer);
} return 0;
}
// }
// Calculates gravity according to supplied formula, compatible with iSpindle/Fermentrack formula
// Log.error(F("CALC: Internal error finding formula." CR));
double calculateGravity(double angle, double temp, const char *tempFormula) { return ERR_FORMULA_INTERNAL;
const char* formula = myConfig.getGravityFormula(); }
if ( tempFormula != 0 ) { //
#if LOG_LEVEL == 6 // Calculates gravity according to supplied formula, compatible with
Log.verbose(F("CALC: Using temporary formula." CR)); // iSpindle/Fermentrack formula
#endif //
formula = tempFormula; double calculateGravity(double angle, double temp, const char *tempFormula) {
} const char *formula = myConfig.getGravityFormula();
#if LOG_LEVEL == 6 if (tempFormula != 0) {
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle, temp); #if LOG_LEVEL == 6
Log.verbose(F("CALC: Formula %s." CR), formula); Log.verbose(F("CALC: Using temporary formula." CR));
#endif #endif
formula = tempFormula;
if ( strlen(formula) == 0 ) }
return 0.0;
#if LOG_LEVEL == 6
// Store variable names and pointers. Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle,
te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}}; temp);
Log.verbose(F("CALC: Formula %s." CR), formula);
int err; #endif
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 2, &err); if (strlen(formula) == 0) return 0.0;
if ( expr ) { // Store variable names and pointers.
double g = te_eval(expr); te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}};
te_free(expr);
int err;
#if LOG_LEVEL == 6 // Compile the expression with variables.
Log.verbose(F("CALC: Calculated gravity is %F." CR), g); te_expr *expr = te_compile(formula, vars, 2, &err);
#endif
return g; if (expr) {
} double g = te_eval(expr);
te_free(expr);
Log.error(F("CALC: Failed to parse expression %d." CR), err);
return 0; #if LOG_LEVEL == 6
} Log.verbose(F("CALC: Calculated gravity is %F." CR), g);
#endif
// return g;
// Do a standard gravity temperature correction. This is a simple way to adjust for differnt worth temperatures }
//
double gravityTemperatureCorrection(double gravity, double temp, char tempFormat, double calTemp) { Log.error(F("CALC: Failed to parse expression %d." CR), err);
#if LOG_LEVEL == 6 return 0;
Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, temp %F, calTemp %F." CR), gravity, temp, calTemp); }
#endif
//
if ( tempFormat == 'C') // Do a standard gravity temperature correction. This is a simple way to adjust
temp = convertCtoF(temp); // for differnt worth temperatures
double calTempF = convertCtoF(calTemp); // calTemp is in C //
const char* formula = "gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0.00000000232820948*temp^3)/(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0.00000000232820948*cal^3))"; double gravityTemperatureCorrection(double gravity, double temp,
char tempFormat, double calTemp) {
// Store variable names and pointers. #if LOG_LEVEL == 6
te_variable vars[] = {{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}}; Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, "
"temp %F, calTemp %F." CR),
int err; gravity, temp, calTemp);
// Compile the expression with variables. #endif
te_expr *expr = te_compile(formula, vars, 3, &err);
if (tempFormat == 'C') temp = convertCtoF(temp);
if ( expr ) { double calTempF = convertCtoF(calTemp); // calTemp is in C
double g = te_eval(expr); const char *formula =
te_free(expr); "gravity*((1.00130346-0.000134722124*temp+0.00000204052596*temp^2-0."
"00000000232820948*temp^3)/"
#if LOG_LEVEL == 6 "(1.00130346-0.000134722124*cal+0.00000204052596*cal^2-0."
Log.verbose(F("CALC: Corrected gravity is %F." CR), g); "00000000232820948*cal^3))";
#endif
return g; // Store variable names and pointers.
} te_variable vars[] = {
{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}};
Log.error(F("CALC: Failed to parse expression %d, no correction has been made." CR), err);
return gravity; int err;
} // Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 3, &err);
// EOF
if (expr) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Corrected gravity is %F." CR), g);
#endif
return g;
}
Log.error(
F("CALC: Failed to parse expression %d, no correction has been made." CR),
err);
return gravity;
}
// EOF

View File

@ -1,42 +1,44 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#ifndef SRC_CALC_H_ #ifndef SRC_CALC_HPP_
#define SRC_CALC_H_ #define SRC_CALC_HPP_
// Includes // Includes
#include "src/helper.h" #include <config.hpp>
#include "src/config.h" #include <helper.hpp>
#define ERR_FORMULA_NOTENOUGHVALUES -1 #define ERR_FORMULA_NOTENOUGHVALUES -1
#define ERR_FORMULA_INTERNAL -2 #define ERR_FORMULA_INTERNAL -2
#define ERR_FORMULA_UNABLETOFFIND -3 #define ERR_FORMULA_UNABLETOFFIND -3
// Functions // Functions
double calculateGravity(double angle, double temp, const char *tempFormula = 0); double calculateGravity(double angle, double temp, const char *tempFormula = 0);
double gravityTemperatureCorrection(double gravity, double temp, char tempFormat, double calTemp = 20); double gravityTemperatureCorrection(double gravity, double temp,
int createFormula(RawFormulaData& fd, char *formulaBuffer, int formulaBufferSize, int order); char tempFormat, double calTemp = 20);
int createFormula(RawFormulaData &fd, char *formulaBuffer,
#endif // SRC_CALC_H_ int formulaBufferSize, int order);
// EOF #endif // SRC_CALC_HPP_
// EOF

View File

@ -1,7 +1,7 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -21,296 +21,304 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "src/config.h"
#include "src/helper.h"
#include <LittleFS.h> #include <LittleFS.h>
#include <config.hpp>
#include <helper.hpp>
Config myConfig; Config myConfig;
// //
// Create the config class with default settings. // Create the config class with default settings.
// //
Config::Config() { Config::Config() {
// Assiging default values // Assiging default values
char buf[30]; char buf[30];
snprintf(&buf[0], sizeof(buf) "%6x", (unsigned int) ESP.getChipId()); snprintf(&buf[0], sizeof(buf), "%6x", (unsigned int)ESP.getChipId());
id = String(&buf[0]); id = String(&buf[0]);
snprintf(&buf[0], sizeof(buf), "" WIFI_MDNS "%s", getID()); snprintf(&buf[0], sizeof(buf), "" WIFI_MDNS "%s", getID());
mDNS = String(&buf[0]); mDNS = String(&buf[0]);
#if LOG_LEVEL == 6 && !defined( CFG_DISABLE_LOGGING ) #if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Created config for %s (%s)." CR), id.c_str(), mDNS.c_str() ); Log.verbose(F("CFG : Created config for %s (%s)." CR), id.c_str(),
#endif mDNS.c_str());
#endif
setTempFormat('C'); setTempFormat('C');
setGravityFormat('G'); setGravityFormat('G');
setSleepInterval(900); // 15 minutes setSleepInterval(900); // 15 minutes
setVoltageFactor(1.59); // Conversion factor for battery setVoltageFactor(1.59); // Conversion factor for battery
setTempSensorAdj(0.0); setTempSensorAdj(0.0);
setGravityTempAdj(false); setGravityTempAdj(false);
gyroCalibration = { 0 , 0 , 0 , 0 , 0 , 0 }; gyroCalibration = {0, 0, 0, 0, 0, 0};
formulaData = {{ 0 , 0 , 0 , 0 , 0 } , { 1 , 1 , 1 , 1 , 1 }}; formulaData = {{0, 0, 0, 0, 0}, {1, 1, 1, 1, 1}};
saveNeeded = false; saveNeeded = false;
} }
// //
// Populate the json document with all configuration parameters (used in both web and saving to file) // Populate the json document with all configuration parameters (used in both
// web and saving to file)
// //
void Config::createJson(DynamicJsonDocument& doc) { void Config::createJson(DynamicJsonDocument& doc) {
doc[ CFG_PARAM_MDNS ] = getMDNS(); doc[CFG_PARAM_MDNS] = getMDNS();
doc[ CFG_PARAM_ID ] = getID(); doc[CFG_PARAM_ID] = getID();
doc[ CFG_PARAM_OTA ] = getOtaURL(); doc[CFG_PARAM_OTA] = getOtaURL();
doc[ CFG_PARAM_SSID ] = getWifiSSID(); doc[CFG_PARAM_SSID] = getWifiSSID();
doc[ CFG_PARAM_PASS ] = getWifiPass(); doc[CFG_PARAM_PASS] = getWifiPass();
doc[ CFG_PARAM_TEMPFORMAT ] = String(getTempFormat()); doc[CFG_PARAM_TEMPFORMAT] = String(getTempFormat());
doc[ CFG_PARAM_PUSH_BREWFATHER ] = getBrewfatherPushUrl(); doc[CFG_PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
doc[ CFG_PARAM_PUSH_HTTP ] = getHttpPushUrl(); doc[CFG_PARAM_PUSH_HTTP] = getHttpPushUrl();
doc[ CFG_PARAM_PUSH_HTTP2 ] = getHttpPushUrl2(); doc[CFG_PARAM_PUSH_HTTP2] = getHttpPushUrl2();
doc[ CFG_PARAM_PUSH_INFLUXDB2 ] = getInfluxDb2PushUrl(); doc[CFG_PARAM_PUSH_INFLUXDB2] = getInfluxDb2PushUrl();
doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ] = getInfluxDb2PushOrg(); doc[CFG_PARAM_PUSH_INFLUXDB2_ORG] = getInfluxDb2PushOrg();
doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ] = getInfluxDb2PushBucket(); doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET] = getInfluxDb2PushBucket();
doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ] = getInfluxDb2PushToken(); doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH] = getInfluxDb2PushToken();
doc[ CFG_PARAM_SLEEP_INTERVAL ] = getSleepInterval(); doc[CFG_PARAM_SLEEP_INTERVAL] = getSleepInterval();
// doc[ CFG_PARAM_PUSH_INTERVAL ] = getSleepInterval(); // TODO: @deprecated // doc[ CFG_PARAM_PUSH_INTERVAL ] = getSleepInterval(); //
doc[ CFG_PARAM_VOLTAGEFACTOR ] = getVoltageFactor(); // TODO: @deprecated
doc[ CFG_PARAM_GRAVITY_FORMULA ] = getGravityFormula(); doc[CFG_PARAM_VOLTAGEFACTOR] = getVoltageFactor();
doc[ CFG_PARAM_GRAVITY_FORMAT ] = String(getGravityFormat()); doc[CFG_PARAM_GRAVITY_FORMULA] = getGravityFormula();
doc[ CFG_PARAM_TEMP_ADJ ] = getTempSensorAdj(); doc[CFG_PARAM_GRAVITY_FORMAT] = String(getGravityFormat());
doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ] = isGravityTempAdj(); doc[CFG_PARAM_TEMP_ADJ] = getTempSensorAdj();
doc[CFG_PARAM_GRAVITY_TEMP_ADJ] = isGravityTempAdj();
JsonObject cal = doc.createNestedObject(CFG_PARAM_GYRO_CALIBRATION); JsonObject cal = doc.createNestedObject(CFG_PARAM_GYRO_CALIBRATION);
cal["ax"] = gyroCalibration.ax; cal["ax"] = gyroCalibration.ax;
cal["ay"] = gyroCalibration.ay; cal["ay"] = gyroCalibration.ay;
cal["az"] = gyroCalibration.az; cal["az"] = gyroCalibration.az;
cal["gx"] = gyroCalibration.gx; cal["gx"] = gyroCalibration.gx;
cal["gy"] = gyroCalibration.gy; cal["gy"] = gyroCalibration.gy;
cal["gz"] = gyroCalibration.gz; cal["gz"] = gyroCalibration.gz;
JsonObject cal2 = doc.createNestedObject(CFG_PARAM_FORMULA_DATA); JsonObject cal2 = doc.createNestedObject(CFG_PARAM_FORMULA_DATA);
cal2[ "a1" ] = reduceFloatPrecision(formulaData.a[0], 2); cal2["a1"] = reduceFloatPrecision(formulaData.a[0], 2);
cal2[ "a2" ] = reduceFloatPrecision(formulaData.a[1], 2); cal2["a2"] = reduceFloatPrecision(formulaData.a[1], 2);
cal2[ "a3" ] = reduceFloatPrecision(formulaData.a[2], 2); cal2["a3"] = reduceFloatPrecision(formulaData.a[2], 2);
cal2[ "a4" ] = reduceFloatPrecision(formulaData.a[3], 2); cal2["a4"] = reduceFloatPrecision(formulaData.a[3], 2);
cal2[ "a5" ] = reduceFloatPrecision(formulaData.a[4], 2); cal2["a5"] = reduceFloatPrecision(formulaData.a[4], 2);
cal2[ "g1" ] = reduceFloatPrecision(formulaData.g[0], 4); cal2["g1"] = reduceFloatPrecision(formulaData.g[0], 4);
cal2[ "g2" ] = reduceFloatPrecision(formulaData.g[1], 4); cal2["g2"] = reduceFloatPrecision(formulaData.g[1], 4);
cal2[ "g3" ] = reduceFloatPrecision(formulaData.g[2], 4); cal2["g3"] = reduceFloatPrecision(formulaData.g[2], 4);
cal2[ "g4" ] = reduceFloatPrecision(formulaData.g[3], 4); cal2["g4"] = reduceFloatPrecision(formulaData.g[3], 4);
cal2[ "g5" ] = reduceFloatPrecision(formulaData.g[4], 4); cal2["g5"] = reduceFloatPrecision(formulaData.g[4], 4);
} }
// //
// Save json document to file // Save json document to file
// //
bool Config::saveFile() { bool Config::saveFile() {
if ( !saveNeeded ) { if (!saveNeeded) {
#if LOG_LEVEL == 6 && !defined( CFG_DISABLE_LOGGING ) #if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Skipping save, not needed." CR)); Log.verbose(F("CFG : Skipping save, not needed." CR));
#endif #endif
return true;
}
#if LOG_LEVEL == 6 && !defined( CFG_DISABLE_LOGGING )
Log.verbose(F("CFG : Saving configuration to file." CR));
#endif
File configFile = LittleFS.open(CFG_FILENAME, "w");
if ( !configFile ) {
Log.error(F("CFG : Failed to open file " CFG_FILENAME " for save." CR));
return false;
}
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
createJson(doc);
#if LOG_LEVEL == 6 && !defined( CFG_DISABLE_LOGGING )
serializeJson(doc, Serial);
Serial.print(CR);
#endif
serializeJson(doc, configFile);
configFile.flush();
configFile.close();
saveNeeded = false;
myConfig.debug();
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
return true; return true;
}
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Saving configuration to file." CR));
#endif
File configFile = LittleFS.open(CFG_FILENAME, "w");
if (!configFile) {
Log.error(F("CFG : Failed to open file " CFG_FILENAME " for save." CR));
return false;
}
DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
createJson(doc);
#if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
serializeJson(doc, Serial);
Serial.print(CR);
#endif
serializeJson(doc, configFile);
configFile.flush();
configFile.close();
saveNeeded = false;
myConfig.debug();
Log.notice(F("CFG : Configuration saved to " CFG_FILENAME "." CR));
return true;
} }
// //
// Load config file from disk // Load config file from disk
// //
bool Config::loadFile() { bool Config::loadFile() {
#if LOG_LEVEL == 6 && !defined( CFG_DISABLE_LOGGING ) #if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Loading configuration from file." CR)); Log.verbose(F("CFG : Loading configuration from file." CR));
#endif #endif
if ( !LittleFS.exists(CFG_FILENAME) ) { if (!LittleFS.exists(CFG_FILENAME)) {
Log.error(F("CFG : Configuration file does not exist " CFG_FILENAME "." CR)); Log.error(
return false; F("CFG : Configuration file does not exist " CFG_FILENAME "." CR));
} return false;
}
File configFile = LittleFS.open(CFG_FILENAME, "r"); File configFile = LittleFS.open(CFG_FILENAME, "r");
if ( !configFile ) { if (!configFile) {
Log.error(F("CFG : Failed to open " CFG_FILENAME "." CR)); Log.error(F("CFG : Failed to open " CFG_FILENAME "." CR));
return false; return false;
} }
Log.notice(F("CFG : Size of configuration file=%d bytes." CR), configFile.size() ); Log.notice(F("CFG : Size of configuration file=%d bytes." CR),
configFile.size());
DynamicJsonDocument doc(CFG_JSON_BUFSIZE); DynamicJsonDocument doc(CFG_JSON_BUFSIZE);
DeserializationError err = deserializeJson(doc, configFile); DeserializationError err = deserializeJson(doc, configFile);
#if LOG_LEVEL == 6 #if LOG_LEVEL == 6
serializeJson(doc, Serial); serializeJson(doc, Serial);
Serial.print(CR); Serial.print(CR);
#endif #endif
configFile.close(); configFile.close();
if ( err ) { if (err) {
Log.error(F("CFG : Failed to parse " CFG_FILENAME " file, Err: %s, %d." CR), err.c_str(), doc.capacity()); Log.error(F("CFG : Failed to parse " CFG_FILENAME " file, Err: %s, %d." CR),
return false; err.c_str(), doc.capacity());
} return false;
}
#if LOG_LEVEL == 6 #if LOG_LEVEL == 6
Log.verbose(F("CFG : Parsed configuration file." CR)); Log.verbose(F("CFG : Parsed configuration file." CR));
#endif #endif
if ( !doc[ CFG_PARAM_OTA ].isNull() ) if (!doc[CFG_PARAM_OTA].isNull()) setOtaURL(doc[CFG_PARAM_OTA]);
setOtaURL(doc[ CFG_PARAM_OTA ]); if (!doc[CFG_PARAM_MDNS].isNull()) setMDNS(doc[CFG_PARAM_MDNS]);
if ( !doc[ CFG_PARAM_MDNS ].isNull() ) if (!doc[CFG_PARAM_SSID].isNull()) setWifiSSID(doc[CFG_PARAM_SSID]);
setMDNS(doc[ CFG_PARAM_MDNS ] ); if (!doc[CFG_PARAM_PASS].isNull()) setWifiPass(doc[CFG_PARAM_PASS]);
if ( !doc[ CFG_PARAM_SSID ].isNull() ) if (!doc[CFG_PARAM_TEMPFORMAT].isNull()) {
setWifiSSID(doc[ CFG_PARAM_SSID ]); String s = doc[CFG_PARAM_TEMPFORMAT];
if ( !doc[ CFG_PARAM_PASS ].isNull() ) setTempFormat(s.charAt(0));
setWifiPass(doc[ CFG_PARAM_PASS ]); }
if ( !doc[ CFG_PARAM_TEMPFORMAT ].isNull() ) { if (!doc[CFG_PARAM_PUSH_BREWFATHER].isNull())
String s = doc[ CFG_PARAM_TEMPFORMAT ]; setBrewfatherPushUrl(doc[CFG_PARAM_PUSH_BREWFATHER]);
setTempFormat(s.charAt(0)); if (!doc[CFG_PARAM_PUSH_HTTP].isNull())
} setHttpPushUrl(doc[CFG_PARAM_PUSH_HTTP]);
if ( !doc[ CFG_PARAM_PUSH_BREWFATHER ].isNull() ) if (!doc[CFG_PARAM_PUSH_HTTP2].isNull())
setBrewfatherPushUrl(doc[ CFG_PARAM_PUSH_BREWFATHER ]); setHttpPushUrl2(doc[CFG_PARAM_PUSH_HTTP2]);
if ( !doc[ CFG_PARAM_PUSH_HTTP ].isNull() ) if (!doc[CFG_PARAM_PUSH_INFLUXDB2].isNull())
setHttpPushUrl(doc[ CFG_PARAM_PUSH_HTTP ]); setInfluxDb2PushUrl(doc[CFG_PARAM_PUSH_INFLUXDB2]);
if ( !doc[ CFG_PARAM_PUSH_HTTP2 ].isNull() ) if (!doc[CFG_PARAM_PUSH_INFLUXDB2_ORG].isNull())
setHttpPushUrl2(doc[ CFG_PARAM_PUSH_HTTP2 ]); setInfluxDb2PushOrg(doc[CFG_PARAM_PUSH_INFLUXDB2_ORG]);
if ( !doc[ CFG_PARAM_PUSH_INFLUXDB2 ].isNull() ) if (!doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET].isNull())
setInfluxDb2PushUrl(doc[ CFG_PARAM_PUSH_INFLUXDB2 ]); setInfluxDb2PushBucket(doc[CFG_PARAM_PUSH_INFLUXDB2_BUCKET]);
if ( !doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ].isNull() ) if (!doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH].isNull())
setInfluxDb2PushOrg(doc[ CFG_PARAM_PUSH_INFLUXDB2_ORG ]); setInfluxDb2PushToken(doc[CFG_PARAM_PUSH_INFLUXDB2_AUTH]);
if ( !doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ].isNull() ) if (!doc[CFG_PARAM_SLEEP_INTERVAL].isNull())
setInfluxDb2PushBucket(doc[ CFG_PARAM_PUSH_INFLUXDB2_BUCKET ]); setSleepInterval(doc[CFG_PARAM_SLEEP_INTERVAL].as<int>());
if ( !doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ].isNull() ) if (!doc[CFG_PARAM_PUSH_INTERVAL].isNull()) // TODO: @deprecated
setInfluxDb2PushToken(doc[ CFG_PARAM_PUSH_INFLUXDB2_AUTH ]); setSleepInterval(
if ( !doc[ CFG_PARAM_SLEEP_INTERVAL ].isNull() ) doc[CFG_PARAM_PUSH_INTERVAL].as<int>()); // TODO: @deprecated
setSleepInterval(doc[ CFG_PARAM_SLEEP_INTERVAL ].as<int>()); if (!doc[CFG_PARAM_VOLTAGEFACTOR].isNull())
if ( !doc[ CFG_PARAM_PUSH_INTERVAL ].isNull() ) // TODO: @deprecated setVoltageFactor(doc[CFG_PARAM_VOLTAGEFACTOR].as<float>());
setSleepInterval(doc[ CFG_PARAM_PUSH_INTERVAL ].as<int>()); // TODO: @deprecated if (!doc[CFG_PARAM_GRAVITY_FORMULA].isNull())
if ( !doc[ CFG_PARAM_VOLTAGEFACTOR ].isNull() ) setGravityFormula(doc[CFG_PARAM_GRAVITY_FORMULA]);
setVoltageFactor(doc[ CFG_PARAM_VOLTAGEFACTOR ].as<float>()); if (!doc[CFG_PARAM_GRAVITY_TEMP_ADJ].isNull())
if ( !doc[ CFG_PARAM_GRAVITY_FORMULA ].isNull() ) setGravityTempAdj(doc[CFG_PARAM_GRAVITY_TEMP_ADJ].as<bool>());
setGravityFormula(doc[ CFG_PARAM_GRAVITY_FORMULA ]); if (!doc[CFG_PARAM_GRAVITY_FORMAT].isNull()) {
if ( !doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ].isNull() ) String s = doc[CFG_PARAM_GRAVITY_FORMAT];
setGravityTempAdj(doc[ CFG_PARAM_GRAVITY_TEMP_ADJ ].as<bool>()); setGravityFormat(s.charAt(0));
if ( !doc[ CFG_PARAM_GRAVITY_FORMAT ].isNull() ) { }
String s = doc[ CFG_PARAM_GRAVITY_FORMAT ]; if (!doc[CFG_PARAM_TEMP_ADJ].isNull())
setGravityFormat(s.charAt(0)); setTempSensorAdj(doc[CFG_PARAM_TEMP_ADJ].as<float>());
}
if ( !doc[ CFG_PARAM_TEMP_ADJ ].isNull() )
setTempSensorAdj(doc[ CFG_PARAM_TEMP_ADJ ].as<float>());
if ( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["ax"].isNull() ) if (!doc[CFG_PARAM_GYRO_CALIBRATION]["ax"].isNull())
gyroCalibration.ax = doc[ CFG_PARAM_GYRO_CALIBRATION ]["ax"]; gyroCalibration.ax = doc[CFG_PARAM_GYRO_CALIBRATION]["ax"];
if ( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["ay"].isNull() ) if (!doc[CFG_PARAM_GYRO_CALIBRATION]["ay"].isNull())
gyroCalibration.ay = doc[ CFG_PARAM_GYRO_CALIBRATION ]["ay"]; gyroCalibration.ay = doc[CFG_PARAM_GYRO_CALIBRATION]["ay"];
if ( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["az"].isNull() ) if (!doc[CFG_PARAM_GYRO_CALIBRATION]["az"].isNull())
gyroCalibration.az = doc[ CFG_PARAM_GYRO_CALIBRATION ]["az"]; gyroCalibration.az = doc[CFG_PARAM_GYRO_CALIBRATION]["az"];
if ( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gx"].isNull() ) if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gx"].isNull())
gyroCalibration.gx = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gx"]; gyroCalibration.gx = doc[CFG_PARAM_GYRO_CALIBRATION]["gx"];
if ( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gy"].isNull() ) if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gy"].isNull())
gyroCalibration.gy = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gy"]; gyroCalibration.gy = doc[CFG_PARAM_GYRO_CALIBRATION]["gy"];
if ( !doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"].isNull() ) if (!doc[CFG_PARAM_GYRO_CALIBRATION]["gz"].isNull())
gyroCalibration.gz = doc[ CFG_PARAM_GYRO_CALIBRATION ]["gz"]; gyroCalibration.gz = doc[CFG_PARAM_GYRO_CALIBRATION]["gz"];
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a1" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["a1"].isNull())
formulaData.a[0] = doc[ CFG_PARAM_FORMULA_DATA ][ "a1" ].as<double>(); formulaData.a[0] = doc[CFG_PARAM_FORMULA_DATA]["a1"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a2" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["a2"].isNull())
formulaData.a[1] = doc[ CFG_PARAM_FORMULA_DATA ][ "a2" ].as<double>(); formulaData.a[1] = doc[CFG_PARAM_FORMULA_DATA]["a2"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a3" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["a3"].isNull())
formulaData.a[2] = doc[ CFG_PARAM_FORMULA_DATA ][ "a3" ].as<double>(); formulaData.a[2] = doc[CFG_PARAM_FORMULA_DATA]["a3"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a4" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["a4"].isNull())
formulaData.a[3] = doc[ CFG_PARAM_FORMULA_DATA ][ "a4" ].as<double>(); formulaData.a[3] = doc[CFG_PARAM_FORMULA_DATA]["a4"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a5" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["a5"].isNull())
formulaData.a[4] = doc[ CFG_PARAM_FORMULA_DATA ][ "a5" ].as<double>(); formulaData.a[4] = doc[CFG_PARAM_FORMULA_DATA]["a5"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g1" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["g1"].isNull())
formulaData.g[0] = doc[ CFG_PARAM_FORMULA_DATA ][ "g1" ].as<double>(); formulaData.g[0] = doc[CFG_PARAM_FORMULA_DATA]["g1"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g2" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["g2"].isNull())
formulaData.g[1] = doc[ CFG_PARAM_FORMULA_DATA ][ "g2" ].as<double>(); formulaData.g[1] = doc[CFG_PARAM_FORMULA_DATA]["g2"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g3" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["g3"].isNull())
formulaData.g[2] = doc[ CFG_PARAM_FORMULA_DATA ][ "g3" ].as<double>(); formulaData.g[2] = doc[CFG_PARAM_FORMULA_DATA]["g3"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g4" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["g4"].isNull())
formulaData.g[3] = doc[ CFG_PARAM_FORMULA_DATA ][ "g4" ].as<double>(); formulaData.g[3] = doc[CFG_PARAM_FORMULA_DATA]["g4"].as<double>();
if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g5" ].isNull() ) if (!doc[CFG_PARAM_FORMULA_DATA]["g5"].isNull())
formulaData.g[4] = doc[ CFG_PARAM_FORMULA_DATA ][ "g5" ]; formulaData.g[4] = doc[CFG_PARAM_FORMULA_DATA]["g5"];
myConfig.debug(); myConfig.debug();
saveNeeded = false; // Reset save flag saveNeeded = false; // Reset save flag
Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR)); Log.notice(F("CFG : Configuration file " CFG_FILENAME " loaded." CR));
return true; return true;
} }
// //
// Check if file system can be mounted, if not we format it. // Check if file system can be mounted, if not we format it.
// //
void Config::formatFileSystem() { void Config::formatFileSystem() {
Log.notice(F("CFG : Formating filesystem." CR)); Log.notice(F("CFG : Formating filesystem." CR));
LittleFS.format(); LittleFS.format();
} }
// //
// Check if file system can be mounted, if not we format it. // Check if file system can be mounted, if not we format it.
// //
void Config::checkFileSystem() { void Config::checkFileSystem() {
#if LOG_LEVEL == 6 && !defined( CFG_DISABLE_LOGGING ) #if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Checking if filesystem is valid." CR)); Log.verbose(F("CFG : Checking if filesystem is valid." CR));
#endif #endif
if ( LittleFS.begin() ) { if (LittleFS.begin()) {
Log.notice(F("CFG : Filesystem mounted." CR)); Log.notice(F("CFG : Filesystem mounted." CR));
} else { } else {
Log.error(F("CFG : Unable to mount file system, formatting..." CR)); Log.error(F("CFG : Unable to mount file system, formatting..." CR));
LittleFS.format(); LittleFS.format();
} }
} }
// //
// Dump the configuration to the serial port // Dump the configuration to the serial port
// //
void Config::debug() { void Config::debug() {
#if LOG_LEVEL == 6 && !defined( CFG_DISABLE_LOGGING ) #if LOG_LEVEL == 6 && !defined(CFG_DISABLE_LOGGING)
Log.verbose(F("CFG : Dumping configration " CFG_FILENAME "." CR)); Log.verbose(F("CFG : Dumping configration " CFG_FILENAME "." CR));
Log.verbose(F("CFG : ID; '%s'." CR), getID()); Log.verbose(F("CFG : ID; '%s'." CR), getID());
Log.verbose(F("CFG : WIFI; '%s', '%s'." CR), getWifiSSID(), getWifiPass() ); Log.verbose(F("CFG : WIFI; '%s', '%s'." CR), getWifiSSID(), getWifiPass());
Log.verbose(F("CFG : mDNS; '%s'." CR), getMDNS() ); Log.verbose(F("CFG : mDNS; '%s'." CR), getMDNS());
Log.verbose(F("CFG : Sleep interval; %d." CR), getSleepInterval() ); Log.verbose(F("CFG : Sleep interval; %d." CR), getSleepInterval());
Log.verbose(F("CFG : OTA; '%s'." CR), getOtaURL() ); Log.verbose(F("CFG : OTA; '%s'." CR), getOtaURL());
Log.verbose(F("CFG : Temp Format; %c." CR), getTempFormat() ); Log.verbose(F("CFG : Temp Format; %c." CR), getTempFormat());
Log.verbose(F("CFG : Temp Adj; %F." CR), getTempSensorAdj() ); Log.verbose(F("CFG : Temp Adj; %F." CR), getTempSensorAdj());
Log.verbose(F("CFG : VoltageFactor; %F." CR), getVoltageFactor() ); Log.verbose(F("CFG : VoltageFactor; %F." CR), getVoltageFactor());
Log.verbose(F("CFG : Gravity formula; '%s'." CR), getGravityFormula() ); Log.verbose(F("CFG : Gravity formula; '%s'." CR), getGravityFormula());
Log.verbose(F("CFG : Gravity format; '%c'." CR), getGravityFormat() ); Log.verbose(F("CFG : Gravity format; '%c'." CR), getGravityFormat());
Log.verbose(F("CFG : Gravity temp adj; %s." CR), isGravityTempAdj()?"true":"false" ); Log.verbose(F("CFG : Gravity temp adj; %s." CR),
Log.verbose(F("CFG : Push brewfather; '%s'." CR), getBrewfatherPushUrl() ); isGravityTempAdj() ? "true" : "false");
Log.verbose(F("CFG : Push http; '%s'." CR), getHttpPushUrl() ); Log.verbose(F("CFG : Push brewfather; '%s'." CR), getBrewfatherPushUrl());
Log.verbose(F("CFG : Push http2; '%s'." CR), getHttpPushUrl2() ); Log.verbose(F("CFG : Push http; '%s'." CR), getHttpPushUrl());
Log.verbose(F("CFG : InfluxDb2; '%s', '%s', '%s', '%s'." CR), getInfluxDb2PushUrl(), getInfluxDb2PushOrg(), Log.verbose(F("CFG : Push http2; '%s'." CR), getHttpPushUrl2());
getInfluxDb2PushBucket(), getInfluxDb2PushToken() ); Log.verbose(F("CFG : InfluxDb2; '%s', '%s', '%s', '%s'." CR),
// Log.verbose(F("CFG : Accel offset\t%d\t%d\t%d" CR), gyroCalibration.ax, gyroCalibration.ay, gyroCalibration.az ); getInfluxDb2PushUrl(), getInfluxDb2PushOrg(),
// Log.verbose(F("CFG : Gyro offset \t%d\t%d\t%d" CR), gyroCalibration.gx, gyroCalibration.gy, gyroCalibration.gz ); getInfluxDb2PushBucket(), getInfluxDb2PushToken());
#endif // Log.verbose(F("CFG : Accel offset\t%d\t%d\t%d" CR), gyroCalibration.ax,
// gyroCalibration.ay, gyroCalibration.az ); Log.verbose(F("CFG : Gyro offset
// \t%d\t%d\t%d" CR), gyroCalibration.gx, gyroCalibration.gy,
// gyroCalibration.gz );
#endif
} }
// EOF // EOF

View File

@ -1,230 +0,0 @@
/*
MIT License
Copyright (c) 2021 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.
*/
#ifndef SRC_CONFIG_H_
#define SRC_CONFIG_H_
// Includes
#include <stdlib.h>
#include "src/helper.h"
#include <Arduino.h>
#include <ArduinoJson.h>
// defintions
#define CFG_JSON_BUFSIZE 3192
#define CFG_APPNAME "GravityMon " // Name of firmware
#define CFG_FILENAME "/gravitymon.json" // Name of config file
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
#define WIFI_PORTAL_TIMEOUT 120 // Number of seconds until the config portal is closed
// These are used in API + Savefile
#define CFG_PARAM_ID "id"
#define CFG_PARAM_MDNS "mdns" // Device name
#define CFG_PARAM_OTA "ota-url" // Base URL for OTA
#define CFG_PARAM_SSID "wifi-ssid" // WIFI
#define CFG_PARAM_PASS "wifi-pass" // WIFI
#define CFG_PARAM_PUSH_BREWFATHER "brewfather-push" // URL (brewfather format)
#define CFG_PARAM_PUSH_HTTP "http-push" // URL (iSpindle format)
#define CFG_PARAM_PUSH_HTTP2 "http-push2" // URL (iSpindle format)
#define CFG_PARAM_PUSH_INFLUXDB2 "influxdb2-push" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth" // URL
#define CFG_PARAM_SLEEP_INTERVAL "sleep-interval" // Sleep interval
// TODO: @deprecated setting
#define CFG_PARAM_PUSH_INTERVAL "push-interval" // Time between push
#define CFG_PARAM_TEMPFORMAT "temp-format" // C or F
#define CFG_PARAM_VOLTAGEFACTOR "voltage-factor" // Factor to calculate the battery voltage
#define CFG_PARAM_GRAVITY_FORMULA "gravity-formula" // Formula for calculating gravity
#define CFG_PARAM_GRAVITY_FORMAT "gravity-format" // Gravity format G or P
#define CFG_PARAM_GRAVITY_TEMP_ADJ "gravity-temp-adjustment" // True/False. Adjust gravity for temperature
#define CFG_PARAM_TEMP_ADJ "temp-adjustment-value" // Correction value for temp sensor
#define CFG_PARAM_GYRO_CALIBRATION "gyro-calibration-data" // READ ONLY
#define CFG_PARAM_FORMULA_DATA "formula-calculation-data" // Raw data for the formula calculation
// These are used in API's
#define CFG_PARAM_APP_NAME "app-name"
#define CFG_PARAM_APP_VER "app-ver"
#define CFG_PARAM_ANGLE "angle"
#define CFG_PARAM_GRAVITY "gravity"
#define CFG_PARAM_TEMP_C "temp-c"
#define CFG_PARAM_TEMP_F "temp-f"
#define CFG_PARAM_BATTERY "battery"
#define CFG_PARAM_SLEEP_MODE "sleep-mode"
#define CFG_PARAM_RSSI "rssi"
// Used for holding sensordata or sensoroffsets
struct RawGyroData {
int16_t ax; // Raw Acceleration
int16_t ay;
int16_t az;
int16_t gx; // Raw Position
int16_t gy;
int16_t gz;
int16_t temp; // Only for information (temperature of chip)
};
// Used for holding formulaData (used for calculating formula on device)
struct RawFormulaData {
double a[5];
double g[5];
};
// Main configuration class
class Config {
private:
bool saveNeeded;
// Device configuration
String id;
String mDNS;
String otaURL;
char tempFormat; // C, F
float voltageFactor;
float tempSensorAdj; // This value will be added to the read sensor value
int sleepInterval;
// Wifi Config
String wifiSSID;
String wifiPASS;
// Push target settings
String brewfatherPushUrl; // URL For brewfather
String httpPushUrl; // URL 1 for standard http
String httpPushUrl2; // URL 2 for standard http
String influxDb2Url; // URL for InfluxDB v2
String influxDb2Org; // Organisation for InfluxDB v2
String influxDb2Bucket; // Bucket for InfluxDB v2
String influxDb2Token; // Auth Token for InfluxDB v2
// Gravity and temperature calculations
String gravityFormula;
bool gravityTempAdj; // true, false
char gravityFormat; // G, P
// Gyro calibration data
RawGyroData gyroCalibration; // Holds the gyro calibration constants (6 * int16_t)
RawFormulaData formulaData; // Used for creating formula
void debug();
void formatFileSystem();
public:
Config();
const char* getID() { return id.c_str(); }
const char* getMDNS() { return mDNS.c_str(); }
void setMDNS(String s) { mDNS = s; saveNeeded = true; }
const char* getOtaURL() { return otaURL.c_str(); }
void setOtaURL(String s) { otaURL = s; saveNeeded = true; }
bool isOtaActive() { return otaURL.length() ? true : false; }
const char* getWifiSSID() { return wifiSSID.c_str(); }
void setWifiSSID(String s) { wifiSSID = s; saveNeeded = true; }
const char* getWifiPass() { return wifiPASS.c_str(); }
void setWifiPass(String s) { wifiPASS = s; saveNeeded = true; }
// Brewfather
const char* getBrewfatherPushUrl() { return brewfatherPushUrl.c_str(); }
void setBrewfatherPushUrl(String s) { brewfatherPushUrl = s; saveNeeded = true; }
bool isBrewfatherActive() { return brewfatherPushUrl.length()?true:false; }
// Standard HTTP
const char* getHttpPushUrl() { return httpPushUrl.c_str(); }
void setHttpPushUrl(String s) { httpPushUrl = s; saveNeeded = true; }
bool isHttpActive() { return httpPushUrl.length()?true:false; }
const char* getHttpPushUrl2() { return httpPushUrl2.c_str(); }
void setHttpPushUrl2(String s) { httpPushUrl2 = s; saveNeeded = true; }
bool isHttpActive2() { return httpPushUrl2.length()?true:false; }
// InfluxDB2
const char* getInfluxDb2PushUrl() { return influxDb2Url.c_str(); }
void setInfluxDb2PushUrl(String s) { influxDb2Url = s; saveNeeded = true; }
bool isInfluxDb2Active() { return influxDb2Url.length()?true:false; }
const char* getInfluxDb2PushOrg() { return influxDb2Org.c_str(); }
void setInfluxDb2PushOrg(String s) { influxDb2Org = s; saveNeeded = true; }
const char* getInfluxDb2PushBucket() { return influxDb2Bucket.c_str(); }
void setInfluxDb2PushBucket(String s) { influxDb2Bucket = s; saveNeeded = true; }
const char* getInfluxDb2PushToken() { return influxDb2Token.c_str(); }
void setInfluxDb2PushToken(String s) { influxDb2Token = s; saveNeeded = true; }
int getSleepInterval() { return sleepInterval; }
void setSleepInterval(int v) { sleepInterval = v; saveNeeded = true; }
void setSleepInterval(String s) { sleepInterval = s.toInt(); saveNeeded = true; }
char getTempFormat() { return tempFormat; }
void setTempFormat(char c) { tempFormat = c; saveNeeded = true; }
bool isTempC() { return tempFormat == 'C' ? false : true; }
bool isTempF() { return tempFormat == 'F' ? false : true; }
float getVoltageFactor() { return voltageFactor; }
void setVoltageFactor(float f) { voltageFactor = f; saveNeeded = true; }
void setVoltageFactor(String s) { voltageFactor = s.toFloat(); saveNeeded = true; }
float getTempSensorAdj() { return tempSensorAdj; }
void setTempSensorAdj(float f) { tempSensorAdj = f; saveNeeded = true; }
void setTempSensorAdj(String s) { tempSensorAdj = s.toFloat(); saveNeeded = true; }
const char* getGravityFormula() { return gravityFormula.c_str(); }
void setGravityFormula(String s) { gravityFormula = s; saveNeeded = true; }
bool isGravityTempAdj() { return gravityTempAdj; }
void setGravityTempAdj(bool b) { gravityTempAdj = b; saveNeeded = true; }
char getGravityFormat() { return gravityFormat; }
void setGravityFormat(char c) { gravityFormat = c; saveNeeded = true; }
bool isGravitySG() { return gravityFormat == 'G' ? false : true; }
bool isGravityPlato() { return gravityFormat == 'P' ? false : true; }
const RawGyroData& getGyroCalibration() { return gyroCalibration; }
void setGyroCalibration(const RawGyroData &r) { gyroCalibration = r; saveNeeded = true; }
const RawFormulaData& getFormulaData() { return formulaData; }
void setFormulaData(const RawFormulaData &r) { formulaData = r; saveNeeded = true; }
// IO functions
void createJson(DynamicJsonDocument& doc);
bool saveFile();
bool loadFile();
void checkFileSystem();
bool isSaveNeeded() { return saveNeeded; }
void setSaveNeeded() { saveNeeded = true; }
};
// Global instance created
extern Config myConfig;
#endif // SRC_CONFIG_H_
// EOF

309
src/config.hpp Normal file
View File

@ -0,0 +1,309 @@
/*
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.
*/
#ifndef SRC_CONFIG_HPP_
#define SRC_CONFIG_HPP_
// Includes
#include <Arduino.h>
#include <ArduinoJson.h>
#include <stdlib.h>
#include <helper.hpp>
// defintions
#define CFG_JSON_BUFSIZE 3192
#define CFG_APPNAME "GravityMon " // Name of firmware
#define CFG_FILENAME "/gravitymon.json" // Name of config file
#define WIFI_DEFAULT_SSID "GravityMon" // Name of created SSID
#define WIFI_DEFAULT_PWD "password" // Password for created SSID
#define WIFI_MDNS "gravitymon" // Prefix for MDNS name
#define WIFI_PORTAL_TIMEOUT \
120 // Number of seconds until the config portal is closed
// These are used in API + Savefile
#define CFG_PARAM_ID "id"
#define CFG_PARAM_MDNS "mdns" // Device name
#define CFG_PARAM_OTA "ota-url" // Base URL for OTA
#define CFG_PARAM_SSID "wifi-ssid" // WIFI
#define CFG_PARAM_PASS "wifi-pass" // WIFI
#define CFG_PARAM_PUSH_BREWFATHER "brewfather-push" // URL (brewfather format)
#define CFG_PARAM_PUSH_HTTP "http-push" // URL (iSpindle format)
#define CFG_PARAM_PUSH_HTTP2 "http-push2" // URL (iSpindle format)
#define CFG_PARAM_PUSH_INFLUXDB2 "influxdb2-push" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_ORG "influxdb2-org" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_BUCKET "influxdb2-bucket" // URL
#define CFG_PARAM_PUSH_INFLUXDB2_AUTH "influxdb2-auth" // URL
#define CFG_PARAM_SLEEP_INTERVAL "sleep-interval" // Sleep interval
// TODO: @deprecated setting
#define CFG_PARAM_PUSH_INTERVAL "push-interval" // Time between push
#define CFG_PARAM_TEMPFORMAT "temp-format" // C or F
#define CFG_PARAM_VOLTAGEFACTOR \
"voltage-factor" // Factor to calculate the battery voltage
#define CFG_PARAM_GRAVITY_FORMULA \
"gravity-formula" // Formula for calculating gravity
#define CFG_PARAM_GRAVITY_FORMAT "gravity-format" // Gravity format G or P
#define CFG_PARAM_GRAVITY_TEMP_ADJ \
"gravity-temp-adjustment" // True/False. Adjust gravity for temperature
#define CFG_PARAM_TEMP_ADJ \
"temp-adjustment-value" // Correction value for temp sensor
#define CFG_PARAM_GYRO_CALIBRATION "gyro-calibration-data" // READ ONLY
#define CFG_PARAM_FORMULA_DATA \
"formula-calculation-data" // Raw data for the formula calculation
// These are used in API's
#define CFG_PARAM_APP_NAME "app-name"
#define CFG_PARAM_APP_VER "app-ver"
#define CFG_PARAM_ANGLE "angle"
#define CFG_PARAM_GRAVITY "gravity"
#define CFG_PARAM_TEMP_C "temp-c"
#define CFG_PARAM_TEMP_F "temp-f"
#define CFG_PARAM_BATTERY "battery"
#define CFG_PARAM_SLEEP_MODE "sleep-mode"
#define CFG_PARAM_RSSI "rssi"
// Used for holding sensordata or sensoroffsets
struct RawGyroData {
int16_t ax; // Raw Acceleration
int16_t ay;
int16_t az;
int16_t gx; // Raw Position
int16_t gy;
int16_t gz;
int16_t temp; // Only for information (temperature of chip)
};
// Used for holding formulaData (used for calculating formula on device)
struct RawFormulaData {
double a[5];
double g[5];
};
// Main configuration class
class Config {
private:
bool saveNeeded;
// Device configuration
String id;
String mDNS;
String otaURL;
char tempFormat; // C, F
float voltageFactor;
float tempSensorAdj; // This value will be added to the read sensor value
int sleepInterval;
// Wifi Config
String wifiSSID;
String wifiPASS;
// Push target settings
String brewfatherPushUrl; // URL For brewfather
String httpPushUrl; // URL 1 for standard http
String httpPushUrl2; // URL 2 for standard http
String influxDb2Url; // URL for InfluxDB v2
String influxDb2Org; // Organisation for InfluxDB v2
String influxDb2Bucket; // Bucket for InfluxDB v2
String influxDb2Token; // Auth Token for InfluxDB v2
// Gravity and temperature calculations
String gravityFormula;
bool gravityTempAdj; // true, false
char gravityFormat; // G, P
// Gyro calibration data
RawGyroData
gyroCalibration; // Holds the gyro calibration constants (6 * int16_t)
RawFormulaData formulaData; // Used for creating formula
void debug();
void formatFileSystem();
public:
Config();
const char* getID() { return id.c_str(); }
const char* getMDNS() { return mDNS.c_str(); }
void setMDNS(String s) {
mDNS = s;
saveNeeded = true;
}
const char* getOtaURL() { return otaURL.c_str(); }
void setOtaURL(String s) {
otaURL = s;
saveNeeded = true;
}
bool isOtaActive() { return otaURL.length() ? true : false; }
const char* getWifiSSID() { return wifiSSID.c_str(); }
void setWifiSSID(String s) {
wifiSSID = s;
saveNeeded = true;
}
const char* getWifiPass() { return wifiPASS.c_str(); }
void setWifiPass(String s) {
wifiPASS = s;
saveNeeded = true;
}
// Brewfather
const char* getBrewfatherPushUrl() { return brewfatherPushUrl.c_str(); }
void setBrewfatherPushUrl(String s) {
brewfatherPushUrl = s;
saveNeeded = true;
}
bool isBrewfatherActive() {
return brewfatherPushUrl.length() ? true : false;
}
// Standard HTTP
const char* getHttpPushUrl() { return httpPushUrl.c_str(); }
void setHttpPushUrl(String s) {
httpPushUrl = s;
saveNeeded = true;
}
bool isHttpActive() { return httpPushUrl.length() ? true : false; }
const char* getHttpPushUrl2() { return httpPushUrl2.c_str(); }
void setHttpPushUrl2(String s) {
httpPushUrl2 = s;
saveNeeded = true;
}
bool isHttpActive2() { return httpPushUrl2.length() ? true : false; }
// InfluxDB2
const char* getInfluxDb2PushUrl() { return influxDb2Url.c_str(); }
void setInfluxDb2PushUrl(String s) {
influxDb2Url = s;
saveNeeded = true;
}
bool isInfluxDb2Active() { return influxDb2Url.length() ? true : false; }
const char* getInfluxDb2PushOrg() { return influxDb2Org.c_str(); }
void setInfluxDb2PushOrg(String s) {
influxDb2Org = s;
saveNeeded = true;
}
const char* getInfluxDb2PushBucket() { return influxDb2Bucket.c_str(); }
void setInfluxDb2PushBucket(String s) {
influxDb2Bucket = s;
saveNeeded = true;
}
const char* getInfluxDb2PushToken() { return influxDb2Token.c_str(); }
void setInfluxDb2PushToken(String s) {
influxDb2Token = s;
saveNeeded = true;
}
int getSleepInterval() { return sleepInterval; }
void setSleepInterval(int v) {
sleepInterval = v;
saveNeeded = true;
}
void setSleepInterval(String s) {
sleepInterval = s.toInt();
saveNeeded = true;
}
char getTempFormat() { return tempFormat; }
void setTempFormat(char c) {
tempFormat = c;
saveNeeded = true;
}
bool isTempC() { return tempFormat == 'C' ? false : true; }
bool isTempF() { return tempFormat == 'F' ? false : true; }
float getVoltageFactor() { return voltageFactor; }
void setVoltageFactor(float f) {
voltageFactor = f;
saveNeeded = true;
}
void setVoltageFactor(String s) {
voltageFactor = s.toFloat();
saveNeeded = true;
}
float getTempSensorAdj() { return tempSensorAdj; }
void setTempSensorAdj(float f) {
tempSensorAdj = f;
saveNeeded = true;
}
void setTempSensorAdj(String s) {
tempSensorAdj = s.toFloat();
saveNeeded = true;
}
const char* getGravityFormula() { return gravityFormula.c_str(); }
void setGravityFormula(String s) {
gravityFormula = s;
saveNeeded = true;
}
bool isGravityTempAdj() { return gravityTempAdj; }
void setGravityTempAdj(bool b) {
gravityTempAdj = b;
saveNeeded = true;
}
char getGravityFormat() { return gravityFormat; }
void setGravityFormat(char c) {
gravityFormat = c;
saveNeeded = true;
}
bool isGravitySG() { return gravityFormat == 'G' ? false : true; }
bool isGravityPlato() { return gravityFormat == 'P' ? false : true; }
const RawGyroData& getGyroCalibration() { return gyroCalibration; }
void setGyroCalibration(const RawGyroData& r) {
gyroCalibration = r;
saveNeeded = true;
}
const RawFormulaData& getFormulaData() { return formulaData; }
void setFormulaData(const RawFormulaData& r) {
formulaData = r;
saveNeeded = true;
}
// IO functions
void createJson(DynamicJsonDocument& doc);
bool saveFile();
bool loadFile();
void checkFileSystem();
bool isSaveNeeded() { return saveNeeded; }
void setSaveNeeded() { saveNeeded = true; }
};
// Global instance created
extern Config myConfig;
#endif // SRC_CONFIG_HPP_
// EOF

View File

@ -1,368 +1,409 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "gyro.h" #include <gyro.hpp>
#include "helper.h" #include <helper.hpp>
GyroSensor myGyro; GyroSensor myGyro;
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready #define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
#define SENSOR_MOVING_THREASHOLD 500 #define SENSOR_MOVING_THREASHOLD 500
#define SENSOR_READ_COUNT 50 #define SENSOR_READ_COUNT 50
#define SENSOR_READ_DELAY 3150 // us, empirical, to hold sampling to 200 Hz #define SENSOR_READ_DELAY 3150 // us, empirical, to hold sampling to 200 Hz
#define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing calibration #define GYRO_SHOW_MINMAX // Will calculate the min/max values when doing
//#define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup // calibration
// #define GYRO_CALIBRATE_STARTUP // Will calibrate sensor at startup
//
// Initialize the sensor chip. //
// // Initialize the sensor chip.
bool GyroSensor::setup() { //
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) bool GyroSensor::setup() {
Log.verbose(F("GYRO: Setting up hardware." CR)); #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
#endif Log.verbose(F("GYRO: Setting up hardware." CR));
Wire.begin(D3, D4); #endif
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties Wire.begin(D3, D4);
accelgyro.initialize(); Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having
// compilation difficulties
if( !accelgyro.testConnection() ) { accelgyro.initialize();
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
sensorConnected = false; if (!accelgyro.testConnection()) {
} else { Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
sensorConnected = false;
#if !defined( GYRO_DISABLE_LOGGING ) } else {
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR)); #if !defined(GYRO_DISABLE_LOGGING)
#endif Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
sensorConnected = true; #endif
sensorConnected = true;
// Configure the sensor
accelgyro.setTempSensorEnabled(true); // Configure the sensor
//accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); // Set in .initalize() accelgyro.setTempSensorEnabled(true);
//accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250); // Set in .initalize() // accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); // Set in
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5); // .initalize() accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250); //
#if defined( GYRO_USE_INTERRUPT ) // Set in .initalize()
// Alternative method to read data, let the MPU signal when sampling is done. accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
accelgyro.setRate(17); #if defined(GYRO_USE_INTERRUPT)
accelgyro.setInterruptDrive(1); // Alternative method to read data, let the MPU signal when sampling is
accelgyro.setInterruptMode(1); // done.
accelgyro.setInterruptLatch(0); accelgyro.setRate(17);
accelgyro.setIntDataReadyEnabled(true); accelgyro.setInterruptDrive(1);
#endif accelgyro.setInterruptMode(1);
accelgyro.setInterruptLatch(0);
#if defined ( GYRO_CALIBRATE_STARTUP ) accelgyro.setIntDataReadyEnabled(true);
// Run the calibration at start, useful for testing. #endif
calibrateSensor();
#endif #if defined(GYRO_CALIBRATE_STARTUP)
// Run the calibration at start, useful for testing.
// Once we have calibration values stored we just apply them from the config. calibrateSensor();
calibrationOffset = myConfig.getGyroCalibration(); #endif
applyCalibration();
} // Once we have calibration values stored we just apply them from the
return sensorConnected; // config.
} calibrationOffset = myConfig.getGyroCalibration();
applyCalibration();
// }
// Set sensor in sleep mode to conserve battery return sensorConnected;
// }
void GyroSensor::enterSleep() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) //
Log.verbose(F("GYRO: Setting up hardware." CR)); // Set sensor in sleep mode to conserve battery
#endif //
accelgyro.setSleepEnabled( true ); void GyroSensor::enterSleep() {
} #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Setting up hardware." CR));
// #endif
// Do a number of reads to get a more stable value. accelgyro.setSleepEnabled(true);
// }
void GyroSensor::readSensor(RawGyroData &raw, const int noIterations, const int delayTime) {
RawGyroDataL average = { 0, 0, 0, 0, 0, 0 }; //
// Do a number of reads to get a more stable value.
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) //
Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR), noIterations, delayTime ); void GyroSensor::readSensor(RawGyroData &raw, const int noIterations,
#endif const int delayTime) {
RawGyroDataL average = {0, 0, 0, 0, 0, 0};
// Set some initial values
#if defined( GYRO_SHOW_MINMAX ) #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
RawGyroData min, max; Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR),
//accelgyro.getRotation( &min.gx, &min.gy, &min.gz ); noIterations, delayTime);
accelgyro.getAcceleration( &min.ax, &min.ay, &min.az ); #endif
min.temp = accelgyro.getTemperature();
max = min; // Set some initial values
#endif #if defined(GYRO_SHOW_MINMAX)
for(int cnt = 0; cnt < noIterations ; cnt ++) { RawGyroData min, max;
// accelgyro.getRotation( &min.gx, &min.gy, &min.gz );
#if defined( GYRO_USE_INTERRUPT ) accelgyro.getAcceleration(&min.ax, &min.ay, &min.az);
while( accelgyro.getIntDataReadyStatus() == 0) { min.temp = accelgyro.getTemperature();
delayMicroseconds( 1 ); max = min;
} #endif
#endif for (int cnt = 0; cnt < noIterations; cnt++) {
#if defined(GYRO_USE_INTERRUPT)
//accelgyro.getRotation( &raw.gx, &raw.gy, &raw.gz ); while (accelgyro.getIntDataReadyStatus() == 0) {
//accelgyro.getAcceleration( &raw.ax, &raw.ay, &raw.az ); delayMicroseconds(1);
accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz); }
raw.temp = accelgyro.getTemperature(); #endif
average.ax += raw.ax; // accelgyro.getRotation( &raw.gx, &raw.gy, &raw.gz );
average.ay += raw.ay; // accelgyro.getAcceleration( &raw.ax, &raw.ay, &raw.az );
average.az += raw.az; accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz);
average.gx += raw.gx; raw.temp = accelgyro.getTemperature();
average.gy += raw.gy;
average.gz += raw.gz; average.ax += raw.ax;
average.temp += raw.temp; average.ay += raw.ay;
average.az += raw.az;
// Log what the minium value is average.gx += raw.gx;
#if defined( GYRO_SHOW_MINMAX ) average.gy += raw.gy;
if( raw.ax < min.ax ) min.ax = raw.ax; average.gz += raw.gz;
if( raw.ay < min.ay ) min.ay = raw.ay; average.temp += raw.temp;
if( raw.az < min.az ) min.az = raw.az;
if( raw.gx < min.gx ) min.gx = raw.gx; // Log what the minium value is
if( raw.gy < min.gy ) min.gy = raw.gy; #if defined(GYRO_SHOW_MINMAX)
if( raw.gz < min.gz ) min.gz = raw.gz; if (raw.ax < min.ax) min.ax = raw.ax;
if( raw.temp < min.temp ) min.temp = raw.temp; if (raw.ay < min.ay) min.ay = raw.ay;
if (raw.az < min.az) min.az = raw.az;
// Log what the maximum value is if (raw.gx < min.gx) min.gx = raw.gx;
if( raw.ax > max.ax ) max.ax = raw.ax; if (raw.gy < min.gy) min.gy = raw.gy;
if( raw.ay > max.ay ) max.ay = raw.ay; if (raw.gz < min.gz) min.gz = raw.gz;
if( raw.az > max.az ) max.az = raw.az; if (raw.temp < min.temp) min.temp = raw.temp;
if( raw.gx > max.gx ) max.gx = raw.gx;
if( raw.gy > max.gy ) max.gy = raw.gy; // Log what the maximum value is
if( raw.gz > max.gz ) max.gz = raw.gz; if (raw.ax > max.ax) max.ax = raw.ax;
if( raw.temp > max.temp ) max.temp = raw.temp; if (raw.ay > max.ay) max.ay = raw.ay;
#endif if (raw.az > max.az) max.az = raw.az;
if (raw.gx > max.gx) max.gx = raw.gx;
#if !defined( GYRO_USE_INTERRUPT ) if (raw.gy > max.gy) max.gy = raw.gy;
delayMicroseconds( delayTime ); if (raw.gz > max.gz) max.gz = raw.gz;
#endif if (raw.temp > max.temp) max.temp = raw.temp;
} #endif
raw.ax = average.ax/noIterations; #if !defined(GYRO_USE_INTERRUPT)
raw.ay = average.ay/noIterations; delayMicroseconds(delayTime);
raw.az = average.az/noIterations; #endif
raw.gx = average.gx/noIterations; }
raw.gy = average.gy/noIterations;
raw.gz = average.gz/noIterations; raw.ax = average.ax / noIterations;
raw.temp = average.temp/noIterations; raw.ay = average.ay / noIterations;
raw.az = average.az / noIterations;
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) raw.gx = average.gx / noIterations;
#if defined( GYRO_SHOW_MINMAX ) raw.gy = average.gy / noIterations;
Log.verbose(F("GYRO: Min \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), min.ax, min.ay, min.az, min.gx, min.gy, min.gz, min.temp ); raw.gz = average.gz / noIterations;
Log.verbose(F("GYRO: Max \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), max.ax, max.ay, max.az, max.gx, max.gy, max.gz, max.temp ); raw.temp = average.temp / noIterations;
#endif
Log.verbose(F("GYRO: Average\t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), raw.ax, raw.ay, raw.az, raw.gx, raw.gy, raw.gz, raw.temp ); #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
//Log.verbose(F("GYRO: Result \t%d\t%d\t%d\t%d\t%d\t%d." CR), average.ax/noIterations, average.ay/noIterations, average.az/noIterations, #if defined(GYRO_SHOW_MINMAX)
// average.gx/noIterations, average.gy/noIterations, average.gz/noIterations ); Log.verbose(F("GYRO: Min \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), min.ax,
#endif min.ay, min.az, min.gx, min.gy, min.gz, min.temp);
} Log.verbose(F("GYRO: Max \t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), max.ax,
max.ay, max.az, max.gx, max.gy, max.gz, max.temp);
// #endif
// Calcuate the angles (tilt) Log.verbose(F("GYRO: Average\t%d\t%d\t%d\t%d\t%d\t%d\t%d." CR), raw.ax,
// raw.ay, raw.az, raw.gx, raw.gy, raw.gz, raw.temp);
float GyroSensor::calculateAngle(RawGyroData &raw) { // Log.verbose(F("GYRO: Result \t%d\t%d\t%d\t%d\t%d\t%d." CR),
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) // average.ax/noIterations, average.ay/noIterations, average.az/noIterations,
Log.verbose(F("GYRO: Calculating the angle." CR) ); // average.gx/noIterations,
#endif // average.gy/noIterations,
// average.gz/noIterations
// Smooth out the readings to we can have a more stable angle/tilt. // );
// ------------------------------------------------------------------------------------------------------------ #endif
// Accelerometer full scale range of +/- 2g with Sensitivity Scale Factor of 16,384 LSB(Count)/g. }
// Gyroscope full scale range of +/- 250 °/s with Sensitivity Scale Factor of 131 LSB (Count)/°/s.
float ax = ((float) raw.ax)/16384, //
ay = ((float) raw.ay)/16384, // Calcuate the angles (tilt)
az = ((float) raw.az)/16384; //
float GyroSensor::calculateAngle(RawGyroData &raw) {
// Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
float v = (acos( ay / sqrt( ax*ax + ay*ay + az*az ) ) *180.0 / PI); Log.verbose(F("GYRO: Calculating the angle." CR));
//Log.notice(F("GYRO: angle = %F." CR), v ); #endif
//double v = (acos( raw.az / sqrt( raw.ax*raw.ax + raw.ay*raw.ay + raw.az*raw.az ) ) *180.0 / PI);
//Log.notice(F("GYRO: angle = %F." CR), v ); // Smooth out the readings to we can have a more stable angle/tilt.
// ------------------------------------------------------------------------------------------------------------
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) // Accelerometer full scale range of +/- 2g with Sensitivity Scale Factor of
Log.verbose(F("GYRO: angle = %F." CR), v ); // 16,384 LSB(Count)/g. Gyroscope full scale range of +/- 250 °/s with
#endif // Sensitivity Scale Factor of 131 LSB (Count)/°/s.
return v; float ax = (static_cast<float>(raw.ax)) / 16384,
} ay = (static_cast<float>(raw.ay)) / 16384,
az = (static_cast<float>(raw.az)) / 16384;
//
// Check if the values are high that indicate that the sensor is moving. // Source: https://www.nxp.com/docs/en/application-note/AN3461.pdf
// float v = (acos(ay / sqrt(ax * ax + ay * ay + az * az)) * 180.0 / PI);
bool GyroSensor::isSensorMoving(RawGyroData &raw) { // Log.notice(F("GYRO: angle = %F." CR), v );
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) // double v = (acos( raw.az / sqrt( raw.ax*raw.ax + raw.ay*raw.ay +
Log.verbose(F("GYRO: Checking for sensor movement." CR) ); // raw.az*raw.az ) ) *180.0 / PI); Log.notice(F("GYRO: angle = %F." CR), v );
#endif
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz); Log.verbose(F("GYRO: angle = %F." CR), v);
#endif
if( x>SENSOR_MOVING_THREASHOLD || y>SENSOR_MOVING_THREASHOLD || z>SENSOR_MOVING_THREASHOLD ) { return v;
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), SENSOR_MOVING_THREASHOLD, x, y, z); }
return true;
} //
// Check if the values are high that indicate that the sensor is moving.
return false; //
} bool GyroSensor::isSensorMoving(RawGyroData &raw) {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
// Log.verbose(F("GYRO: Checking for sensor movement." CR));
// Read the tilt angle from the gyro. #endif
//
bool GyroSensor::read() { int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Getting new gyro position." CR) ); if (x > SENSOR_MOVING_THREASHOLD || y > SENSOR_MOVING_THREASHOLD ||
#endif z > SENSOR_MOVING_THREASHOLD) {
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR),
if( !sensorConnected ) SENSOR_MOVING_THREASHOLD, x, y, z);
return false; return true;
}
readSensor( lastGyroData, SENSOR_READ_COUNT, SENSOR_READ_DELAY ); // Last param is unused if GYRO_USE_INTERRUPT is defined.
return false;
// If the sensor is unstable we return false to signal we dont have valid value }
if( isSensorMoving(lastGyroData) ) {
#if !defined( GYRO_DISABLE_LOGGING ) //
Log.notice(F("GYRO: Sensor is moving." CR) ); // Read the tilt angle from the gyro.
#endif //
validValue = false; bool GyroSensor::read() {
} else { #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
validValue = true; Log.verbose(F("GYRO: Getting new gyro position." CR));
angle = calculateAngle( lastGyroData ); #endif
#if !defined( GYRO_DISABLE_LOGGING )
Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), lastGyroData.ax, lastGyroData.ay, lastGyroData.az, angle ); if (!sensorConnected) return false;
#endif
} readSensor(lastGyroData, SENSOR_READ_COUNT,
SENSOR_READ_DELAY); // Last param is unused if GYRO_USE_INTERRUPT
sensorTemp = ((float) lastGyroData.temp) / 340 + 36.53; // is defined.
// The first read value is close to the DS18 value according to my tests, if more reads are // If the sensor is unstable we return false to signal we dont have valid
// done then the gyro temp will increase to much // value
if( initialSensorTemp == INVALID_TEMPERATURE ) if (isSensorMoving(lastGyroData)) {
initialSensorTemp = sensorTemp; #if !defined(GYRO_DISABLE_LOGGING)
Log.notice(F("GYRO: Sensor is moving." CR));
return validValue; #endif
} validValue = false;
} else {
// validValue = true;
// Dump the stored calibration values. angle = calculateAngle(lastGyroData);
// #if !defined(GYRO_DISABLE_LOGGING)
void GyroSensor::dumpCalibration() { Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), lastGyroData.ax,
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) lastGyroData.ay, lastGyroData.az, angle);
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), calibrationOffset.ax, calibrationOffset.ay, calibrationOffset.az ); #endif
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx, calibrationOffset.gy, calibrationOffset.gz ); }
#endif
} sensorTemp = (static_cast<float>(lastGyroData.temp)) / 340 + 36.53;
// // The first read value is close to the DS18 value according to my tests, if
// Update the sensor with out calculated offsets. // more reads are done then the gyro temp will increase to much
// if (initialSensorTemp == INVALID_TEMPERATURE) initialSensorTemp = sensorTemp;
void GyroSensor::applyCalibration() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) return validValue;
Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR) ); }
#endif
//
if( ( calibrationOffset.ax + calibrationOffset.ay + calibrationOffset.az + calibrationOffset.gx + calibrationOffset.gy + calibrationOffset.gz ) == 0 ) { // Dump the stored calibration values.
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR) ); //
return; void GyroSensor::dumpCalibration() {
} #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Accel offset\t%d\t%d\t%d" CR), calibrationOffset.ax,
accelgyro.setXAccelOffset( calibrationOffset.ax ); calibrationOffset.ay, calibrationOffset.az);
accelgyro.setYAccelOffset( calibrationOffset.ay ); Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx,
accelgyro.setZAccelOffset( calibrationOffset.az ); calibrationOffset.gy, calibrationOffset.gz);
accelgyro.setXGyroOffset( calibrationOffset.gx ); #endif
accelgyro.setYGyroOffset( calibrationOffset.gy ); }
accelgyro.setZGyroOffset( calibrationOffset.gz );
} //
// Update the sensor with out calculated offsets.
// //
// Calculate the offsets for calibration. void GyroSensor::applyCalibration() {
// #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
void GyroSensor::calibrateSensor() { Log.verbose(F("GYRO: Applying calibration offsets to sensor." CR));
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) #endif
Log.verbose(F("GYRO: Calibrating sensor" CR) );
#endif if ((calibrationOffset.ax + calibrationOffset.ay + calibrationOffset.az +
//accelgyro.PrintActiveOffsets(); calibrationOffset.gx + calibrationOffset.gy + calibrationOffset.gz) ==
//Serial.print( CR ); 0) {
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR));
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5); return;
accelgyro.CalibrateAccel(6); // 6 = 600 readings }
accelgyro.CalibrateGyro(6);
accelgyro.setXAccelOffset(calibrationOffset.ax);
accelgyro.PrintActiveOffsets(); accelgyro.setYAccelOffset(calibrationOffset.ay);
Serial.print( CR ); accelgyro.setZAccelOffset(calibrationOffset.az);
accelgyro.setXGyroOffset(calibrationOffset.gx);
calibrationOffset.ax = accelgyro.getXAccelOffset(); accelgyro.setYGyroOffset(calibrationOffset.gy);
calibrationOffset.ay = accelgyro.getYAccelOffset(); accelgyro.setZGyroOffset(calibrationOffset.gz);
calibrationOffset.az = accelgyro.getZAccelOffset(); }
calibrationOffset.gx = accelgyro.getXGyroOffset();
calibrationOffset.gy = accelgyro.getYGyroOffset(); //
calibrationOffset.gz = accelgyro.getZGyroOffset(); // Calculate the offsets for calibration.
//
// Save the calibrated values void GyroSensor::calibrateSensor() {
myConfig.setGyroCalibration( calibrationOffset ); #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
myConfig.saveFile(); Log.verbose(F("GYRO: Calibrating sensor" CR));
} #endif
// accelgyro.PrintActiveOffsets();
// // Serial.print( CR );
// Calibrate the device.
// accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
void GyroSensor::debug() { accelgyro.CalibrateAccel(6); // 6 = 600 readings
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING ) accelgyro.CalibrateGyro(6);
Log.verbose(F("GYRO: Debug - Clock src %d." CR), accelgyro.getClockSource() );
Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID() ); accelgyro.PrintActiveOffsets();
Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode() ); Serial.print(CR);
Log.verbose(F("GYRO: Debug - DMP on %s." CR), accelgyro.getDMPEnabled()?"on":"off" );
Log.verbose(F("GYRO: Debug - Acc range %d." CR), accelgyro.getFullScaleAccelRange() ); calibrationOffset.ax = accelgyro.getXAccelOffset();
Log.verbose(F("GYRO: Debug - Gyr range %d." CR), accelgyro.getFullScaleGyroRange() ); calibrationOffset.ay = accelgyro.getYAccelOffset();
Log.verbose(F("GYRO: Debug - Int %s." CR), accelgyro.getIntEnabled()?"on":"off" ); calibrationOffset.az = accelgyro.getZAccelOffset();
Log.verbose(F("GYRO: Debug - Clock %d." CR), accelgyro.getMasterClockSpeed() ); calibrationOffset.gx = accelgyro.getXGyroOffset();
Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate() ); calibrationOffset.gy = accelgyro.getYGyroOffset();
Log.verbose(F("GYRO: Debug - Gyro range %d." CR), accelgyro.getFullScaleGyroRange() ); calibrationOffset.gz = accelgyro.getZGyroOffset();
// Log.verbose(F("GYRO: Debug - I2C bypass %s." CR), accelgyro.getI2CBypassEnabled()?"on":"off" );
// Log.verbose(F("GYRO: Debug - I2C master %s." CR), accelgyro.getI2CMasterModeEnabled()?"on":"off" ); // Save the calibrated values
Log.verbose(F("GYRO: Debug - Acc FactX %d." CR), accelgyro.getAccelXSelfTestFactoryTrim() ); myConfig.setGyroCalibration(calibrationOffset);
Log.verbose(F("GYRO: Debug - Acc FactY %d." CR), accelgyro.getAccelYSelfTestFactoryTrim() ); myConfig.saveFile();
Log.verbose(F("GYRO: Debug - Acc FactZ %d." CR), accelgyro.getAccelZSelfTestFactoryTrim() ); }
Log.verbose(F("GYRO: Debug - Gyr FactX %d." CR), accelgyro.getGyroXSelfTestFactoryTrim() );
Log.verbose(F("GYRO: Debug - Gyr FactY %d." CR), accelgyro.getGyroYSelfTestFactoryTrim() ); //
Log.verbose(F("GYRO: Debug - Gyr FactZ %d." CR), accelgyro.getGyroZSelfTestFactoryTrim() ); // Calibrate the device.
//
switch( accelgyro.getFullScaleAccelRange() ) { void GyroSensor::debug() {
case 0: #if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Debug - Accel range +/- 2g." CR)); Log.verbose(F("GYRO: Debug - Clock src %d." CR),
break; accelgyro.getClockSource());
case 1: Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID());
Log.verbose(F("GYRO: Debug - Accel range +/- 4g." CR)); Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode());
break; Log.verbose(F("GYRO: Debug - DMP on %s." CR),
case 2: accelgyro.getDMPEnabled() ? "on" : "off");
Log.verbose(F("GYRO: Debug - Accel range +/- 8g." CR)); Log.verbose(F("GYRO: Debug - Acc range %d." CR),
break; accelgyro.getFullScaleAccelRange());
case 3: Log.verbose(F("GYRO: Debug - Gyr range %d." CR),
Log.verbose(F("GYRO: Debug - Accel range +/- 16g." CR)); accelgyro.getFullScaleGyroRange());
break; Log.verbose(F("GYRO: Debug - Int %s." CR),
} accelgyro.getIntEnabled() ? "on" : "off");
Log.verbose(F("GYRO: Debug - Clock %d." CR),
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR), accelgyro.getXAccelOffset(), calibrationOffset.az ); accelgyro.getMasterClockSpeed());
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR), accelgyro.getYAccelOffset(), calibrationOffset.ay ); Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate());
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR), accelgyro.getZAccelOffset(), calibrationOffset.az ); Log.verbose(F("GYRO: Debug - Gyro range %d." CR),
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR), accelgyro.getXGyroOffset(), calibrationOffset.gx ); accelgyro.getFullScaleGyroRange());
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR), accelgyro.getYGyroOffset(), calibrationOffset.gy ); // Log.verbose(F("GYRO: Debug - I2C bypass %s." CR),
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR), accelgyro.getZGyroOffset(), calibrationOffset.gz ); // accelgyro.getI2CBypassEnabled()?"on":"off" ); Log.verbose(F("GYRO: Debug -
#endif // I2C master %s." CR), accelgyro.getI2CMasterModeEnabled()?"on":"off" );
} Log.verbose(F("GYRO: Debug - Acc FactX %d." CR),
accelgyro.getAccelXSelfTestFactoryTrim());
// EOF Log.verbose(F("GYRO: Debug - Acc FactY %d." CR),
accelgyro.getAccelYSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Acc FactZ %d." CR),
accelgyro.getAccelZSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Gyr FactX %d." CR),
accelgyro.getGyroXSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Gyr FactY %d." CR),
accelgyro.getGyroYSelfTestFactoryTrim());
Log.verbose(F("GYRO: Debug - Gyr FactZ %d." CR),
accelgyro.getGyroZSelfTestFactoryTrim());
switch (accelgyro.getFullScaleAccelRange()) {
case 0:
Log.verbose(F("GYRO: Debug - Accel range +/- 2g." CR));
break;
case 1:
Log.verbose(F("GYRO: Debug - Accel range +/- 4g." CR));
break;
case 2:
Log.verbose(F("GYRO: Debug - Accel range +/- 8g." CR));
break;
case 3:
Log.verbose(F("GYRO: Debug - Accel range +/- 16g." CR));
break;
}
Log.verbose(F("GYRO: Debug - Acc OffX %d\t%d." CR),
accelgyro.getXAccelOffset(), calibrationOffset.az);
Log.verbose(F("GYRO: Debug - Acc OffY %d\t%d." CR),
accelgyro.getYAccelOffset(), calibrationOffset.ay);
Log.verbose(F("GYRO: Debug - Acc OffZ %d\t%d." CR),
accelgyro.getZAccelOffset(), calibrationOffset.az);
Log.verbose(F("GYRO: Debug - Gyr OffX %d\t%d." CR),
accelgyro.getXGyroOffset(), calibrationOffset.gx);
Log.verbose(F("GYRO: Debug - Gyr OffY %d\t%d." CR),
accelgyro.getYGyroOffset(), calibrationOffset.gy);
Log.verbose(F("GYRO: Debug - Gyr OffZ %d\t%d." CR),
accelgyro.getZGyroOffset(), calibrationOffset.gz);
#endif
}
// EOF

View File

@ -1,87 +0,0 @@
/*
MIT License
Copyright (c) 2021 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.
*/
#ifndef _GYRO_H
#define _GYRO_H
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
//#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
// Includes
#include <Arduino.h>
#include "MPU6050.h"
#include "config.h"
// Classes
struct RawGyroDataL { // Used for average multiple readings
long ax; // Raw Acceleration
long ay;
long az;
long gx; // Raw Position
long gy;
long gz;
long temp; // Only for information (temperature of chip)
};
#define INVALID_TEMPERATURE -273
class GyroSensor {
private:
MPU6050 accelgyro;
bool sensorConnected = false;
bool validValue = false;
float angle = 0;
float sensorTemp = 0;
float initialSensorTemp = INVALID_TEMPERATURE;
RawGyroData calibrationOffset;
RawGyroData lastGyroData;
void debug();
void applyCalibration();
void dumpCalibration();
void readSensor(RawGyroData &raw, const int noIterations = 100, const int delayTime = 1);
bool isSensorMoving(RawGyroData &raw);
float calculateAngle(RawGyroData &raw);
public:
bool setup();
bool read();
void calibrateSensor();
const RawGyroData& getLastGyroData() { return lastGyroData; }
float getAngle() { return angle; };
float getSensorTempC() { return sensorTemp; };
float getInitialSensorTempC() { return initialSensorTemp; };
bool isConnected() { return sensorConnected; };
bool hasValue() { return validValue; };
void enterSleep();
};
// Global instance created
extern GyroSensor myGyro;
#endif // _GYRO_H
// EOF

89
src/gyro.hpp Normal file
View File

@ -0,0 +1,89 @@
/*
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.
*/
#ifndef SRC_GYRO_HPP_
#define SRC_GYRO_HPP_
#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE
// #define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_SBWIRE
// Includes
#include <Arduino.h>
#include <MPU6050.h>
#include <config.hpp>
// Classes
struct RawGyroDataL { // Used for average multiple readings
int32_t ax; // Raw Acceleration
int32_t ay;
int32_t az;
int32_t gx; // Raw Position
int32_t gy;
int32_t gz;
int32_t temp; // Only for information (temperature of chip)
};
#define INVALID_TEMPERATURE -273
class GyroSensor {
private:
MPU6050 accelgyro;
bool sensorConnected = false;
bool validValue = false;
float angle = 0;
float sensorTemp = 0;
float initialSensorTemp = INVALID_TEMPERATURE;
RawGyroData calibrationOffset;
RawGyroData lastGyroData;
void debug();
void applyCalibration();
void dumpCalibration();
void readSensor(RawGyroData &raw, const int noIterations = 100,
const int delayTime = 1);
bool isSensorMoving(RawGyroData &raw);
float calculateAngle(RawGyroData &raw);
public:
bool setup();
bool read();
void calibrateSensor();
const RawGyroData &getLastGyroData() { return lastGyroData; }
float getAngle() { return angle; }
float getSensorTempC() { return sensorTemp; }
float getInitialSensorTempC() { return initialSensorTemp; }
bool isConnected() { return sensorConnected; }
bool hasValue() { return validValue; }
void enterSleep();
};
// Global instance created
extern GyroSensor myGyro;
#endif // SRC_GYRO_HPP_
// EOF

View File

@ -1,256 +1,272 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "helper.h" #include <ESP8266HTTPClient.h>
#include "config.h" #include <ESP8266WiFi.h>
#include "gyro.h"
#include "tempsensor.h" #include <config.hpp>
#include <ESP8266WiFi.h> #include <gyro.hpp>
#include <ESP8266HTTPClient.h> #include <helper.hpp>
#include <tempsensor.hpp>
SerialDebug mySerial;
BatteryVoltage myBatteryVoltage; SerialDebug mySerial;
BatteryVoltage myBatteryVoltage;
//
// Print the heap information. //
// // Print the heap information.
void printHeap() { //
#if LOG_LEVEL==6 void printHeap() {
Log.verbose(F("HELP: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR), ESP.getFreeHeap()/1024, ESP.getHeapFragmentation(), ESP.getFreeSketchSpace()/1024 ); #if LOG_LEVEL == 6
#endif Log.verbose(F("HELP: Heap %d kb, HeapFrag %d %%, FreeSketch %d kb." CR),
} ESP.getFreeHeap() / 1024, ESP.getHeapFragmentation(),
ESP.getFreeSketchSpace() / 1024);
// #endif
// Enter deep sleep for the defined duration (Argument is seconds) }
//
void deepSleep(int t) { //
#if LOG_LEVEL==6 // Enter deep sleep for the defined duration (Argument is seconds)
Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t ); //
#endif void deepSleep(int t) {
uint64_t wake = t * 1000000; #if LOG_LEVEL == 6
ESP.deepSleep( wake ); Log.verbose(F("HELP: Entering sleep mode for %ds." CR), t);
} #endif
uint64_t wake = t * 1000000;
// ESP.deepSleep(wake);
// Print the build options used }
//
void printBuildOptions() { //
Log.notice( F("Build options: %s LOGLEVEL %d " // Print the build options used
#ifdef SKIP_SLEEPMODE //
"SKIP_SLEEP " void printBuildOptions() {
#endif Log.notice(F("Build options: %s LOGLEVEL %d "
#ifdef EMBED_HTML #ifdef SKIP_SLEEPMODE
"EMBED_HTML " "SKIP_SLEEP "
#endif #endif
#ifdef COLLECT_PERFDATA #ifdef EMBED_HTML
"PERFDATA " "EMBED_HTML "
#endif #endif
#ifdef ACTIVATE_OTA #ifdef COLLECT_PERFDATA
"OTA " "PERFDATA "
#endif #endif
CR), CFG_APPVER, LOG_LEVEL ); #ifdef ACTIVATE_OTA
} "OTA "
#endif
// CR),
// Configure serial debug output CFG_APPVER, LOG_LEVEL);
// }
SerialDebug::SerialDebug(const long serialSpeed) {
// Start serial with auto-detected rate (default to defined BAUD) //
Serial.flush(); // Configure serial debug output
Serial.begin(serialSpeed); //
SerialDebug::SerialDebug(const int32 serialSpeed) {
getLog()->begin(LOG_LEVEL, &Serial, true); // Start serial with auto-detected rate (default to defined BAUD)
getLog()->setPrefix(printTimestamp); Serial.flush();
getLog()->notice(F("SDBG: Serial logging started at %l." CR), serialSpeed); Serial.begin(serialSpeed);
}
getLog()->begin(LOG_LEVEL, &Serial, true);
// getLog()->setPrefix(printTimestamp);
// Print the timestamp (ms since start of device) getLog()->notice(F("SDBG: Serial logging started at %l." CR), serialSpeed);
// }
void printTimestamp(Print* _logOutput, int _logLevel) {
char c[12]; //
sprintf(c, "%10lu ", millis()); // Print the timestamp (ms since start of device)
_logOutput->print(c); //
} void printTimestamp(Print* _logOutput, int _logLevel) {
char c[12];
// snprintf(c, sizeof(c), "%10lu ", millis());
// Read and calculate the battery voltage _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 // Read and calculate the battery voltage
int v = analogRead( A0 ); //
batteryLevel = ((3.3/1023)*v)*factor; void BatteryVoltage::read() {
#if LOG_LEVEL==6 // The analog pin can only handle 3.3V maximum voltage so we need to reduce
Log.verbose(F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR), factor, v, batteryLevel ); // the voltage (from max 5V)
#endif float factor = myConfig.getVoltageFactor(); // Default value is 1.63
} int v = analogRead(A0);
batteryLevel = ((3.3 / 1023) * v) * factor;
#if defined( COLLECT_PERFDATA ) #if LOG_LEVEL == 6
Log.verbose(
PerfLogging myPerfLogging; F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR),
factor, v, batteryLevel);
// #endif
// Clear the current cache }
//
void PerfLogging::clear() { #if defined(COLLECT_PERFDATA)
// Clear the measurements
if( first == 0 ) PerfLogging myPerfLogging;
return;
//
PerfEntry* pe = first; // Clear the current cache
//
do { void PerfLogging::clear() {
pe->max = 0; // Clear the measurements
pe->start = 0; if (first == 0) return;
pe->end = 0;
pe->mA = 0; PerfEntry* pe = first;
pe->V = 0;
pe = pe->next; do {
} while( pe != 0 ); pe->max = 0;
} pe->start = 0;
pe->end = 0;
// pe->mA = 0;
// Start measuring this performance point pe->V = 0;
// pe = pe->next;
void PerfLogging::start( const char* key ) { } while (pe != 0);
PerfEntry* pe = add( key ); }
pe->start = millis();
} //
// Start measuring this performance point
// //
// Finalize measuring of this performance point void PerfLogging::start(const char* key) {
// PerfEntry* pe = add(key);
void PerfLogging::stop( const char* key ) { pe->start = millis();
PerfEntry* pe = find( key ); }
if( pe != 0 ) { //
pe->end = millis(); // Finalize measuring of this performance point
//
unsigned long t = pe->end - pe->start; void PerfLogging::stop(const char* key) {
PerfEntry* pe = find(key);
if( t > pe->max )
pe->max = t; if (pe != 0) {
} pe->end = millis();
}
uint32_t t = pe->end - pe->start;
//
// Print the collected performance data if (t > pe->max) pe->max = t;
// }
void PerfLogging::print() { }
PerfEntry* pe = first;
//
while( pe != 0 ) { // Print the collected performance data
//Log.notice( F("PERF: %s=%l ms (%l, %l)" CR), pe->key, (pe->end - pe->start), pe->start, pe->end ); //
Log.notice( F("PERF: %s %lms" CR), pe->key, pe->max ); void PerfLogging::print() {
pe = pe->next; PerfEntry* pe = first;
}
} while (pe != 0) {
// Log.notice( F("PERF: %s=%l ms (%l, %l)" CR), pe->key, (pe->end -
// // pe->start), pe->start, pe->end );
// Push collected performance data to influx (use influx configuration) Log.notice(F("PERF: %s %lms" CR), pe->key, pe->max);
// pe = pe->next;
void PerfLogging::pushInflux() { }
if( !myConfig.isInfluxDb2Active() ) }
return;
//
WiFiClient client; // Push collected performance data to influx (use influx configuration)
HTTPClient http; //
String serverPath = String(myConfig.getInfluxDb2PushUrl()) + "/api/v2/write?org=" + void PerfLogging::pushInflux() {
String(myConfig.getInfluxDb2PushOrg()) + "&bucket=" + if (!myConfig.isInfluxDb2Active()) return;
String(myConfig.getInfluxDb2PushBucket());
WiFiClient client;
http.begin( client, serverPath); HTTPClient http;
String serverPath =
// Create body for influxdb2, format used String(myConfig.getInfluxDb2PushUrl()) +
// key,host=mdns value=0.0 "/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
String body; "&bucket=" + String(myConfig.getInfluxDb2PushBucket());
// Create the payload with performance data. http.begin(client, serverPath);
// ------------------------------------------------------------------------------------------
PerfEntry* pe = first; // Create body for influxdb2, format used
char buf[100]; // key,host=mdns value=0.0
sprintf( &buf[0], "perf,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() ); String body;
body += &buf[0];
// Create the payload with performance data.
while( pe != 0 ) { // ------------------------------------------------------------------------------------------
if( pe->max ) { PerfEntry* pe = first;
if( pe->next ) char buf[100];
sprintf( &buf[0], "%s=%ld,", pe->key, pe->max); snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
else myConfig.getID());
sprintf( &buf[0], "%s=%ld", pe->key, pe->max); body += &buf[0];
body += &buf[0]; while (pe != 0) {
} if (pe->max) {
pe = pe->next; if (pe->next)
} snprintf(&buf[0], sizeof(buf), "%s=%u,", pe->key, pe->max);
else
// Create the payload with debug data for validating sensor stability snprintf(&buf[0], sizeof(buf), "%s=%u", pe->key, pe->max);
// ------------------------------------------------------------------------------------------
sprintf( &buf[0], "\ndebug,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() ); body += &buf[0];
body += &buf[0]; }
sprintf( &buf[0], "angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f", myGyro.getAngle(), myGyro.getLastGyroData().ax, pe = pe->next;
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az, myGyro.getSensorTempC(), myTempSensor.getTempC() ); }
body += &buf[0];
// Create the payload with debug data for validating sensor stability
// Log.notice(F("PERF: data %s." CR), body.c_str() ); // ------------------------------------------------------------------------------------------
snprintf(&buf[0], sizeof(buf), "\ndebug,host=%s,device=%s ",
#if LOG_LEVEL==6 myConfig.getMDNS(), myConfig.getID());
Log.verbose(F("PERF: url %s." CR), serverPath.c_str()); body += &buf[0];
Log.verbose(F("PERF: data %s." CR), body.c_str() ); snprintf(
#endif &buf[0], sizeof(buf),
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f",
// Send HTTP POST request myGyro.getAngle(), myGyro.getLastGyroData().ax,
String auth = "Token " + String( myConfig.getInfluxDb2PushToken() ); myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
http.addHeader(F("Authorization"), auth.c_str() ); myGyro.getSensorTempC(), myTempSensor.getTempC());
int httpResponseCode = http.POST(body); body += &buf[0];
if (httpResponseCode==204) { // Log.notice(F("PERF: data %s." CR), body.c_str() );
Log.notice(F("PERF: InfluxDB2 push performance data successful, response=%d" CR), httpResponseCode);
} else { #if LOG_LEVEL == 6
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR), httpResponseCode); Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
} Log.verbose(F("PERF: data %s." CR), body.c_str());
#endif
http.end();
} // Send HTTP POST request
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
#endif // COLLECT_PERFDATA http.addHeader(F("Authorization"), auth.c_str());
int httpResponseCode = http.POST(body);
//
// Convert float to formatted string with n decimals. Buffer should be at least 10 chars. if (httpResponseCode == 204) {
// Log.notice(
char* convertFloatToString( float f, char *buffer, int dec ) { F("PERF: InfluxDB2 push performance data successful, response=%d" CR),
dtostrf(f, 6, dec, buffer); httpResponseCode);
return buffer; } else {
} Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR),
httpResponseCode);
// }
// Reduce precision to n decimals
// http.end();
float reduceFloatPrecision( float f, int dec ) { }
char buffer[5];
dtostrf(f, 6, dec, &buffer[0]); #endif // COLLECT_PERFDATA
return atof(&buffer[0]);
} //
// Convert float to formatted string with n decimals. Buffer should be at least
// EOF // 10 chars.
//
char* convertFloatToString(float f, char* buffer, int dec) {
dtostrf(f, 6, dec, buffer);
return buffer;
}
//
// Reduce precision to n decimals
//
float reduceFloatPrecision(float f, int dec) {
char buffer[5];
dtostrf(f, 6, dec, &buffer[0]);
return atof(&buffer[0]);
}
// EOF

View File

@ -1,156 +0,0 @@
/*
MIT License
Copyright (c) 2021 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.
*/
#ifndef _HELPER_H
#define _HELPER_H
// Includes
#include <ArduinoLog.h>
// Sleep mode
void deepSleep(int t);
// Show build options
void printBuildOptions();
// Float to String
char* convertFloatToString( float f, char* buf, int dec = 2);
float reduceFloatPrecision( float f, int dec = 2 );
// Logging via serial
void printTimestamp(Print* _logOutput, int _logLevel);
void printNewline(Print* _logOutput);
void printHeap();
// Classes
class SerialDebug {
public:
SerialDebug(const long serialSpeed = 115200L);
static Logging* getLog() { return &Log; };
};
class BatteryVoltage {
private:
float batteryLevel;
public:
void read();
float getVoltage() { return batteryLevel; };
};
#if defined( COLLECT_PERFDATA )
class PerfLogging {
private:
struct PerfEntry {
unsigned long start; // millis()
unsigned long end; // millis()
unsigned long max; // max time in ms
const char* key; // measurement
PerfEntry* next; // Next in the linked list
float mA; // Power consumption
float V; // Power consumption
};
PerfEntry* first = 0;
bool measurePower = false;
PerfEntry* find( const char* k ) {
if( first == 0 )
return 0;
PerfEntry* pe = first;
while( strcmp( k, pe->key ) != 0 ) {
if( pe->next == 0 )
return 0;
pe = pe->next;
}
return pe;
};
PerfEntry* add( const char* k ) {
if( first == 0 ) {
first = new PerfEntry();
first->key = k;
first->next = 0;
first->max = 0;
return first;
}
PerfEntry* pe = first;
while( strcmp( k, pe->key ) != 0 ) {
if( pe->next == 0 ) {
pe->next = new PerfEntry();
pe->next->key = k;
pe->next->max = 0;
pe->next->next = 0;
return pe->next;
}
pe = pe->next;
}
return pe;
};
public:
void clear();
void start( const char* key );
void stop( const char* key );
void print();
void pushInflux();
};
extern PerfLogging myPerfLogging;
// Use these to collect performance data from various parts of the code
#define LOG_PERF_START(s) myPerfLogging.start(s)
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
//#define LOG_PERF_PRINT() myPerfLogging.print()
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR() myPerfLogging.clear()
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
#else
// These will disable the performance collection function
#define LOG_PERF_START(s)
#define LOG_PERF_STOP(s)
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR()
#define LOG_PERF_PUSH()
#endif // COLLECT_PERFDATA
// Global instance created
extern SerialDebug mySerial;
extern BatteryVoltage myBatteryVoltage;
#endif // _HELPER_H
// EOF

153
src/helper.hpp Normal file
View File

@ -0,0 +1,153 @@
/*
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.
*/
#ifndef SRC_HELPER_HPP_
#define SRC_HELPER_HPP_
// Includes
#include <ArduinoLog.h>
// Sleep mode
void deepSleep(int t);
// Show build options
void printBuildOptions();
// Float to String
char* convertFloatToString(float f, char* buf, int dec = 2);
float reduceFloatPrecision(float f, int dec = 2);
// Logging via serial
void printTimestamp(Print* _logOutput, int _logLevel);
void printNewline(Print* _logOutput);
void printHeap();
// Classes
class SerialDebug {
public:
explicit SerialDebug(const int32 serialSpeed = 115200L);
static Logging* getLog() { return &Log; }
};
class BatteryVoltage {
private:
float batteryLevel;
public:
void read();
float getVoltage() { return batteryLevel; }
};
#if defined(COLLECT_PERFDATA)
class PerfLogging {
private:
struct PerfEntry {
uint32_t start; // millis()
uint32_t end; // millis()
uint32_t max; // max time in ms
const char* key; // measurement
PerfEntry* next; // Next in the linked list
float mA; // Power consumption
float V; // Power consumption
};
PerfEntry* first = 0;
bool measurePower = false;
PerfEntry* find(const char* k) {
if (first == 0) return 0;
PerfEntry* pe = first;
while (strcmp(k, pe->key) != 0) {
if (pe->next == 0) return 0;
pe = pe->next;
}
return pe;
}
PerfEntry* add(const char* k) {
if (first == 0) {
first = new PerfEntry();
first->key = k;
first->next = 0;
first->max = 0;
return first;
}
PerfEntry* pe = first;
while (strcmp(k, pe->key) != 0) {
if (pe->next == 0) {
pe->next = new PerfEntry();
pe->next->key = k;
pe->next->max = 0;
pe->next->next = 0;
return pe->next;
}
pe = pe->next;
}
return pe;
}
public:
void clear();
void start(const char* key);
void stop(const char* key);
void print();
void pushInflux();
};
extern PerfLogging myPerfLogging;
// Use these to collect performance data from various parts of the code
#define LOG_PERF_START(s) myPerfLogging.start(s)
#define LOG_PERF_STOP(s) myPerfLogging.stop(s)
// #define LOG_PERF_PRINT() myPerfLogging.print()
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR() myPerfLogging.clear()
#define LOG_PERF_PUSH() myPerfLogging.pushInflux()
#else
// These will disable the performance collection function
#define LOG_PERF_START(s)
#define LOG_PERF_STOP(s)
#define LOG_PERF_PRINT()
#define LOG_PERF_CLEAR()
#define LOG_PERF_PUSH()
#endif // COLLECT_PERFDATA
// Global instance created
extern SerialDebug mySerial;
extern BatteryVoltage myBatteryVoltage;
#endif // SRC_HELPER_HPP_
// EOF

View File

@ -1,285 +1,318 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "helper.h" #include <LittleFS.h>
#include "gyro.h"
#include "config.h" #include <calc.hpp>
#include "wifi.h" #include <config.hpp>
#include "webserver.h" #include <gyro.hpp>
#include "calc.h" #include <helper.hpp>
#include "tempsensor.h" #include <pushtarget.hpp>
#include "pushtarget.h" #include <tempsensor.hpp>
#include <LittleFS.h> #include <webserver.hpp>
#include <wifi.hpp>
// Settings for double reset detector.
#define ESP8266_DRD_USE_RTC true // Settings for double reset detector.
#define DRD_TIMEOUT 2 #define ESP8266_DRD_USE_RTC true
#define DRD_ADDRESS 0 #define DRD_TIMEOUT 2
#include <ESP_DoubleResetDetector.h> #define DRD_ADDRESS 0
DoubleResetDetector *drd; #include <ESP_DoubleResetDetector.h>
DoubleResetDetector *drd;
// Define constats for this program
#ifdef DEACTIVATE_SLEEPMODE // Define constats for this program
const int interval = 1000; // ms, time to wait between changes to output #ifdef DEACTIVATE_SLEEPMODE
bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour const int interval = 1000; // ms, time to wait between changes to output
#else bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour
const int interval = 200; // ms, time to wait between changes to output #else
bool sleepModeAlwaysSkip = false; // Web interface can override normal behaviour const int interval = 200; // ms, time to wait between changes to output
#endif bool sleepModeAlwaysSkip =
unsigned long loopMillis = 0; // Used for main loop to run the code every _interval_ false; // Web interface can override normal behaviour
unsigned long runtimeMillis; // Used to calculate the total time since start/wakeup #endif
unsigned long stableGyroMillis; // Used to calculate the total time since last stable gyro reading uint32_t loopMillis = 0; // Used for main loop to run the code every _interval_
bool sleepModeActive = false; uint32_t runtimeMillis; // Used to calculate the total time since start/wakeup
bool goToSleep = false; uint32_t stableGyroMillis; // Used to calculate the total time since last
int loopCounter = 0; // stable gyro reading
bool sleepModeActive = false;
// bool goToSleep = false;
// Check if we should be in sleep mode int loopCounter = 0;
//
void checkSleepMode( float angle, float volt ) { //
// Check if we should be in sleep mode
#if defined( SKIP_SLEEPMODE ) //
sleepModeActive = false; void checkSleepMode(float angle, float volt) {
Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR) ); #if defined(SKIP_SLEEPMODE)
return; sleepModeActive = false;
#endif Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR));
return;
const RawGyroData &g = myConfig.getGyroCalibration(); #endif
// Will not enter sleep mode if: no calibration data const RawGyroData &g = myConfig.getGyroCalibration();
if( g.ax==0 && g.ay==0 && g.az==0 && g.gx==0 && g.gy==0 && g.gz==0 ) {
Log.notice(F("MAIN: Missing calibration data, so forcing webserver to be active." CR) ); // Will not enter sleep mode if: no calibration data
sleepModeAlwaysSkip = true; if (g.ax == 0 && g.ay == 0 && g.az == 0 && g.gx == 0 && g.gy == 0 &&
} g.gz == 0) {
Log.notice(
if( sleepModeAlwaysSkip ) { F("MAIN: Missing calibration data, so forcing webserver to be "
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR) ); "active." CR));
sleepModeActive = false; sleepModeAlwaysSkip = true;
return; }
}
if (sleepModeAlwaysSkip) {
// Will not enter sleep mode if: charger is connected Log.notice(F("MAIN: Sleep mode disabled from web interface." CR));
sleepModeActive = (volt<4.15 && (angle>85 && angle<95)) || (volt>4.15) ? false : true; sleepModeActive = false;
return;
// sleep mode active when flat }
//sleepModeActive = ( angle<85 && angle>5 ) ? true : false;
Log.notice(F("MAIN: Deep sleep mode %s (angle=%F volt=%F)." CR), sleepModeActive ? "true":"false", angle, volt ); // Will not enter sleep mode if: charger is connected
} sleepModeActive = (volt < 4.15 && (angle > 85 && angle < 95)) || (volt > 4.15)
? false
// : true;
// Setup
// // sleep mode active when flat
void setup() { // sleepModeActive = ( angle<85 && angle>5 ) ? true : false;
LOG_PERF_START("run-time"); Log.notice(F("MAIN: Deep sleep mode %s (angle=%F volt=%F)." CR),
LOG_PERF_START("main-setup"); sleepModeActive ? "true" : "false", angle, volt);
runtimeMillis = millis(); }
drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS); //
bool dt = drd->detectDoubleReset(); // Setup
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING ) //
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str() ); void setup() {
#endif LOG_PERF_START("run-time");
// Main startup LOG_PERF_START("main-setup");
Log.notice(F("Main: Started setup for %s." CR), String( ESP.getChipId(), HEX).c_str() ); runtimeMillis = millis();
printBuildOptions();
drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
LOG_PERF_START("main-config-load"); bool dt = drd->detectDoubleReset();
myConfig.checkFileSystem(); #if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
myConfig.loadFile(); Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
LOG_PERF_STOP("main-config-load"); #endif
// Main startup
// Setup watchdog Log.notice(F("Main: Started setup for %s." CR),
ESP.wdtDisable(); String(ESP.getChipId(), HEX).c_str());
ESP.wdtEnable( interval*2 ); printBuildOptions();
if( dt ) { LOG_PERF_START("main-config-load");
Log.notice(F("Main: Detected doubletap on reset. Reset reason=%s" CR), ESP.getResetReason().c_str()); myConfig.checkFileSystem();
} myConfig.loadFile();
LOG_PERF_STOP("main-config-load");
#ifdef SKIP_SLEEPMODE
// If we are running in debug more we skip this part. makes is hard to debug in case of crash/watchdog reset // Setup watchdog
dt = false; ESP.wdtDisable();
#endif ESP.wdtEnable(interval * 2);
LOG_PERF_START("main-wifi-connect"); if (dt) {
myWifi.connect( dt ); // This will return false if unable to connect to wifi, will be handled in loop() Log.notice(F("Main: Detected doubletap on reset. Reset reason=%s" CR),
LOG_PERF_STOP("main-wifi-connect"); ESP.getResetReason().c_str());
}
LOG_PERF_START("main-temp-setup");
myTempSensor.setup(); #ifdef SKIP_SLEEPMODE
LOG_PERF_STOP("main-temp-setup"); // If we are running in debug more we skip this part. makes is hard to debug
// in case of crash/watchdog reset
//LOG_PERF_START("main-gyro-setup"); // Takes less than 5ms, so skip this measurment dt = false;
if( !myGyro.setup() ) #endif
Log.error(F("Main: Failed to initialize the gyro." CR));
//LOG_PERF_STOP("main-gyro-setup"); LOG_PERF_START("main-wifi-connect");
myWifi.connect(dt); // This will return false if unable to connect to wifi,
LOG_PERF_START("main-gyro-read"); // will be handled in loop()
myGyro.read(); LOG_PERF_STOP("main-wifi-connect");
LOG_PERF_STOP("main-gyro-read");
LOG_PERF_START("main-temp-setup");
LOG_PERF_START("main-batt-read"); myTempSensor.setup();
myBatteryVoltage.read(); LOG_PERF_STOP("main-temp-setup");
LOG_PERF_STOP("main-batt-read");
checkSleepMode( myGyro.getAngle(), myBatteryVoltage.getVoltage() ); // LOG_PERF_START("main-gyro-setup"); // Takes less than 5ms, so skip this
// measurment
if( myWifi.isConnected() ) { if (!myGyro.setup()) Log.error(F("Main: Failed to initialize the gyro." CR));
#if defined( ACTIVATE_OTA ) // LOG_PERF_STOP("main-gyro-setup");
LOG_PERF_START("main-wifi-ota");
if( !sleepModeActive && myWifi.checkFirmwareVersion() ) { LOG_PERF_START("main-gyro-read");
myWifi.updateFirmware(); myGyro.read();
} LOG_PERF_STOP("main-gyro-read");
LOG_PERF_STOP("main-wifi-ota");
#endif LOG_PERF_START("main-batt-read");
if( !sleepModeActive ) { myBatteryVoltage.read();
//LOG_PERF_START("main-webserver-setup"); // Takes less than 4ms , so skip this measurment LOG_PERF_STOP("main-batt-read");
myWebServer.setupWebServer(); checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
//LOG_PERF_STOP("main-webserver-setup");
} if (myWifi.isConnected()) {
} #if defined(ACTIVATE_OTA)
LOG_PERF_START("main-wifi-ota");
LOG_PERF_STOP("main-setup"); if (!sleepModeActive && myWifi.checkFirmwareVersion()) {
Log.notice(F("Main: Setup completed." CR)); myWifi.updateFirmware();
stableGyroMillis = millis(); // Put it here so we dont include time for wifi connection }
} LOG_PERF_STOP("main-wifi-ota");
#endif
// if (!sleepModeActive) {
// Main loops // LOG_PERF_START("main-webserver-setup"); // Takes less than 4ms , so
// // skip this measurment
void loop() { myWebServer.setupWebServer();
drd->loop(); // LOG_PERF_STOP("main-webserver-setup");
}
if( sleepModeActive || abs( (long) (millis() - loopMillis)) > interval ) { }
float angle = 0;
float volt = myBatteryVoltage.getVoltage(); LOG_PERF_STOP("main-setup");
//float sensorTemp = 0; Log.notice(F("Main: Setup completed." CR));
loopCounter++; stableGyroMillis =
millis(); // Put it here so we dont include time for wifi connection
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING ) }
Log.verbose(F("Main: Entering main loop." CR) );
#endif //
// Main loops
// Process the sensor values and push data to targets. //
// ------------------------------------------------------------------------------------------------ void loop() {
// If we dont get any readings we just skip this and try again the next interval. drd->loop();
//
if( myGyro.hasValue() ) { if (sleepModeActive || abs((int32_t)(millis() - loopMillis)) > interval) {
angle = myGyro.getAngle(); // Gyro angle float angle = 0;
float volt = myBatteryVoltage.getVoltage();
stableGyroMillis = millis(); // Reset timer // float sensorTemp = 0;
loopCounter++;
LOG_PERF_START("loop-temp-read");
float temp = myTempSensor.getTempC(); #if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
LOG_PERF_STOP("loop-temp-read"); Log.verbose(F("Main: Entering main loop." CR));
#endif
//LOG_PERF_START("loop-gravity-calc"); // Takes less than 2ms , so skip this measurment
float gravity = calculateGravity( angle, temp ); // Process the sensor values and push data to targets.
//LOG_PERF_STOP("loop-gravity-calc"); // ------------------------------------------------------------------------------------------------
// If we dont get any readings we just skip this and try again the next
//LOG_PERF_START("loop-gravity-corr"); // Takes less than 2ms , so skip this measurment // interval.
// Use default correction temperature of 20C //
float corrGravity = gravityTemperatureCorrection( gravity, temp, myConfig.getTempFormat() ); if (myGyro.hasValue()) {
//LOG_PERF_STOP("loop-gravity-corr"); angle = myGyro.getAngle(); // Gyro angle
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING ) stableGyroMillis = millis(); // Reset timer
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%F, gravity=%F, corr=%F." CR), angle, temp, gravity, corrGravity );
#endif LOG_PERF_START("loop-temp-read");
float temp = myTempSensor.getTempC();
// Limit the printout when sleep mode is not active. LOG_PERF_STOP("loop-temp-read");
if( loopCounter%10 == 0 || sleepModeActive ) {
Log.notice(F("Main: angle=%F, temp=%F, gravity=%F, corrGravity=%F, batt=%F." CR), angle, temp, gravity, corrGravity ,volt ); // LOG_PERF_START("loop-gravity-calc"); // Takes less than 2ms , so skip
} // this measurment
float gravity = calculateGravity(angle, temp);
LOG_PERF_START("loop-push"); // LOG_PERF_STOP("loop-gravity-calc");
myPushTarget.send( angle, gravity, corrGravity, temp, (millis()-runtimeMillis)/1000, sleepModeActive ); // Force the transmission if we are going to sleep
LOG_PERF_STOP("loop-push"); // LOG_PERF_START("loop-gravity-corr"); // Takes less than 2ms , so skip
// this measurment Use default correction temperature of 20C
// If we have completed the update lets go to sleep float corrGravity =
if( sleepModeActive ) gravityTemperatureCorrection(gravity, temp, myConfig.getTempFormat());
goToSleep = true; // LOG_PERF_STOP("loop-gravity-corr");
} else {
Log.error(F("Main: No gyro value." CR) ); #if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
} Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%F, gravity=%F, "
"corr=%F." CR),
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING ) angle, temp, gravity, corrGravity);
Log.verbose(F("Main: Sleep mode not active." CR) ); #endif
#endif
// Limit the printout when sleep mode is not active.
int sleepInterval = myConfig.getSleepInterval(); if (loopCounter % 10 == 0 || sleepModeActive) {
Log.notice(F("Main: angle=%F, temp=%F, gravity=%F, corrGravity=%F, "
// If we didnt get a wifi connection, we enter sleep for a short time to conserve battery. "batt=%F." CR),
// ------------------------------------------------------------------------------------------------ angle, temp, gravity, corrGravity, volt);
// }
if( !myWifi.isConnected() ) { // no connection to wifi
Log.notice(F("MAIN: No connection to wifi established, sleeping for 60s." CR) ); LOG_PERF_START("loop-push");
sleepInterval = 60; // 60s myPushTarget.send(
goToSleep = true; angle, gravity, corrGravity, temp, (millis() - runtimeMillis) / 1000,
} sleepModeActive); // Force the transmission if we are going to sleep
LOG_PERF_STOP("loop-push");
// If the sensor is moving and we are not getting a clear reading, we enter sleep for a short time to conserve battery.
// ------------------------------------------------------------------------------------------------ // If we have completed the update lets go to sleep
// if (sleepModeActive) goToSleep = true;
if( sleepModeActive && ((millis()-stableGyroMillis)>10000L) ) { // 10s since last stable gyro reading } else {
Log.notice(F("MAIN: Unable to get a stable reading for 10s, sleeping for 60s." CR) ); Log.error(F("Main: No gyro value." CR));
sleepInterval = 60; // 60s }
goToSleep = true;
} #if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Sleep mode not active." CR));
// Enter sleep mode if the conditions are right #endif
// ------------------------------------------------------------------------------------------------
// int sleepInterval = myConfig.getSleepInterval();
if( goToSleep && !sleepModeAlwaysSkip ) {
Log.notice(F("MAIN: Entering deep sleep for %d s, run time %l s, battery=%F V." CR), sleepInterval, (millis()-runtimeMillis)/1000, volt ); // If we didnt get a wifi connection, we enter sleep for a short time to
LittleFS.end(); // conserve battery.
myGyro.enterSleep(); // ------------------------------------------------------------------------------------------------
drd->stop(); //
LOG_PERF_STOP("run-time"); if (!myWifi.isConnected()) { // no connection to wifi
LOG_PERF_PUSH(); Log.notice(
delay(100); F("MAIN: No connection to wifi established, sleeping for 60s." CR));
deepSleep( sleepInterval ); sleepInterval = 60; // 60s
} goToSleep = true;
}
// If we are running in normal mode we just continue
// ------------------------------------------------------------------------------------------------ // If the sensor is moving and we are not getting a clear reading, we enter
// Do these checks if we are running in normal mode (not sleep mode) // sleep for a short time to conserve battery.
// // ------------------------------------------------------------------------------------------------
checkSleepMode( angle, volt ); //
if (sleepModeActive && ((millis() - stableGyroMillis) >
LOG_PERF_START("loop-gyro-read"); 10000L)) { // 10s since last stable gyro reading
myGyro.read(); Log.notice(
LOG_PERF_STOP("loop-gyro-read"); F("MAIN: Unable to get a stable reading for 10s, sleeping for "
"60s." CR));
//LOG_PERF_START("loop-batt-read"); // Takes less than 2ms , so skip this measurment sleepInterval = 60; // 60s
myBatteryVoltage.read(); goToSleep = true;
//LOG_PERF_STOP("loop-batt-read"); }
loopMillis = millis(); // Enter sleep mode if the conditions are right
//#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING ) // ------------------------------------------------------------------------------------------------
Log.verbose(F("Main: Heap %d kb FreeSketch %d kb HeapFrag %d %%." CR), ESP.getFreeHeap()/1024, ESP.getFreeSketchSpace()/1024, ESP.getHeapFragmentation() ); //
//#endif if (goToSleep && !sleepModeAlwaysSkip) {
} Log.notice(F("MAIN: Entering deep sleep for %d s, run time %l s, "
"battery=%F V." CR),
myWebServer.loop(); sleepInterval, (millis() - runtimeMillis) / 1000, volt);
} LittleFS.end();
myGyro.enterSleep();
// EOF drd->stop();
LOG_PERF_STOP("run-time");
LOG_PERF_PUSH();
delay(100);
deepSleep(sleepInterval);
}
// If we are running in normal mode we just continue
// ------------------------------------------------------------------------------------------------
// Do these checks if we are running in normal mode (not sleep mode)
//
checkSleepMode(angle, volt);
LOG_PERF_START("loop-gyro-read");
myGyro.read();
LOG_PERF_STOP("loop-gyro-read");
// LOG_PERF_START("loop-batt-read"); // Takes less than 2ms , so skip this
// measurment
myBatteryVoltage.read();
// LOG_PERF_STOP("loop-batt-read");
loopMillis = millis();
// #if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING )
Log.verbose(F("Main: Heap %d kb FreeSketch %d kb HeapFrag %d %%." CR),
ESP.getFreeHeap() / 1024, ESP.getFreeSketchSpace() / 1024,
ESP.getHeapFragmentation());
// #endif
}
myWebServer.loop();
}
// EOF

View File

@ -1,233 +1,258 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "pushtarget.h" #include <config.h>
#include "config.h"
#include "gyro.h" // For testing the tempsensor in the gyro #include <gyro.hpp>
#include <pushtarget.hpp>
PushTarget myPushTarget;
PushTarget myPushTarget;
//
// Send the pressure value //
// // Send the pressure value
void PushTarget::send(float angle, float gravity, float corrGravity, float temp, float runTime, bool force ) { //
unsigned long timePassed = abs( (long) (millis() - ms) ); void PushTarget::send(float angle, float gravity, float corrGravity, float temp,
unsigned long interval = myConfig.getSleepInterval()*1000; float runTime, bool force) {
uint32_t timePassed = abs((int32_t)(millis() - ms));
if( ( timePassed < interval ) && !force) { uint32_t interval = myConfig.getSleepInterval() * 1000;
#if LOG_LEVEL==6 && !defined( PUSH_DISABLE_LOGGING )
Log.verbose(F("PUSH: Timer has not expired %l vs %l." CR), timePassed, interval ); if ((timePassed < interval) && !force) {
#endif #if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
return; Log.verbose(F("PUSH: Timer has not expired %l vs %l." CR), timePassed,
} interval);
#endif
#if LOG_LEVEL==6 && !defined( PUSH_DISABLE_LOGGING ) return;
Log.verbose(F("PUSH: Sending data." CR) ); }
#endif
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
ms = millis(); Log.verbose(F("PUSH: Sending data." CR));
#endif
if( myConfig.isBrewfatherActive() ) {
LOG_PERF_START("push-brewfather"); ms = millis();
sendBrewfather( angle, gravity, corrGravity, temp );
LOG_PERF_STOP("push-brewfather"); if (myConfig.isBrewfatherActive()) {
} LOG_PERF_START("push-brewfather");
sendBrewfather(angle, gravity, corrGravity, temp);
if( myConfig.isHttpActive() ) { LOG_PERF_STOP("push-brewfather");
LOG_PERF_START("push-http"); }
sendHttp( myConfig.getHttpPushUrl(), angle, gravity, corrGravity, temp, runTime );
LOG_PERF_STOP("push-http"); if (myConfig.isHttpActive()) {
} LOG_PERF_START("push-http");
sendHttp(myConfig.getHttpPushUrl(), angle, gravity, corrGravity, temp,
if( myConfig.isHttpActive2() ) { runTime);
LOG_PERF_START("push-http2"); LOG_PERF_STOP("push-http");
sendHttp( myConfig.getHttpPushUrl2(), angle, gravity, corrGravity, temp, runTime ); }
LOG_PERF_STOP("push-http2");
} if (myConfig.isHttpActive2()) {
LOG_PERF_START("push-http2");
if( myConfig.isInfluxDb2Active() ) { sendHttp(myConfig.getHttpPushUrl2(), angle, gravity, corrGravity, temp,
LOG_PERF_START("push-influxdb2"); runTime);
sendInfluxDb2( angle, gravity, corrGravity, temp, runTime ); LOG_PERF_STOP("push-http2");
LOG_PERF_STOP("push-influxdb2"); }
}
} if (myConfig.isInfluxDb2Active()) {
LOG_PERF_START("push-influxdb2");
// sendInfluxDb2(angle, gravity, corrGravity, temp, runTime);
// Send to influx db v2 LOG_PERF_STOP("push-influxdb2");
// }
void PushTarget::sendInfluxDb2(float angle, float gravity, float corrGravity, float temp, float runTime) { }
#if !defined( PUSH_DISABLE_LOGGING )
Log.notice(F("PUSH: Sending values to influxdb2 angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp ); //
#endif // Send to influx db v2
//
WiFiClient client; void PushTarget::sendInfluxDb2(float angle, float gravity, float corrGravity,
HTTPClient http; float temp, float runTime) {
String serverPath = String(myConfig.getInfluxDb2PushUrl()) + "/api/v2/write?org=" + #if !defined(PUSH_DISABLE_LOGGING)
String(myConfig.getInfluxDb2PushOrg()) + "&bucket=" + Log.notice(
String(myConfig.getInfluxDb2PushBucket()); F("PUSH: Sending values to influxdb2 angle=%F, gravity=%F, temp=%F." CR),
angle, gravity, temp);
http.begin( client, serverPath); #endif
// Create body for influxdb2 WiFiClient client;
char buf[1024]; HTTPClient http;
sprintf( &buf[0], "measurement,host=%s,device=%s,temp-format=%c,gravity-format=%s " String serverPath =
"gravity=%.4f,corr-gravity=%.4f,angle=%.2f,temp=%.2f,battery=%.2f,rssi=%d,temp2=%.2f\n", String(myConfig.getInfluxDb2PushUrl()) +
// TODO: Add support for plato format "/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
myConfig.getMDNS(), myConfig.getID(), myConfig.getTempFormat(), "SG", "&bucket=" + String(myConfig.getInfluxDb2PushBucket());
myConfig.isGravityTempAdj() ? corrGravity : gravity,
corrGravity, angle, temp, myBatteryVoltage.getVoltage(), WiFi.RSSI(), myGyro.getSensorTempC() ); // For comparing gyro tempsensor vs DSB1820 http.begin(client, serverPath);
#if LOG_LEVEL==6 && !defined( PUSH_DISABLE_LOGGING ) // Create body for influxdb2
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str()); char buf[1024];
Log.verbose(F("PUSH: data %s." CR), &buf[0] ); snprintf(
#endif &buf[0], sizeof(buf),
"measurement,host=%s,device=%s,temp-format=%c,gravity-format=%s "
// Send HTTP POST request "gravity=%.4f,corr-gravity=%.4f,angle=%.2f,temp=%.2f,battery=%.2f,rssi=%"
String auth = "Token " + String( myConfig.getInfluxDb2PushToken() ); "d,temp2=%.2f\n",
http.addHeader(F("Authorization"), auth.c_str() ); // TODO: Add support for plato format
int httpResponseCode = http.POST(&buf[0]); myConfig.getMDNS(), myConfig.getID(), myConfig.getTempFormat(), "SG",
myConfig.isGravityTempAdj() ? corrGravity : gravity, corrGravity, angle,
if (httpResponseCode==204) { temp, myBatteryVoltage.getVoltage(), WiFi.RSSI(),
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), httpResponseCode); myGyro.getSensorTempC()); // For comparing gyro tempsensor vs DSB1820
} else {
Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR), httpResponseCode); #if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
} Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
Log.verbose(F("PUSH: data %s." CR), &buf[0]);
http.end(); #endif
}
// Send HTTP POST request
// String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
// Send data to brewfather http.addHeader(F("Authorization"), auth.c_str());
// int httpResponseCode = http.POST(&buf[0]);
void PushTarget::sendBrewfather(float angle, float gravity, float corrGravity, float temp ) {
#if !defined( PUSH_DISABLE_LOGGING ) if (httpResponseCode == 204) {
Log.notice(F("PUSH: Sending values to brewfather angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp ); Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR),
#endif httpResponseCode);
} else {
DynamicJsonDocument doc(300); Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR),
// httpResponseCode);
// { }
// "name": "YourDeviceName", // Required field, this will be the ID in Brewfather
// "temp": 20.32, http.end();
// "aux_temp": 15.61, // Fridge Temp }
// "ext_temp": 6.51, // Room Temp
// "temp_unit": "C", // C, F, K //
// "gravity": 1.042, // Send data to brewfather
// "gravity_unit": "G", // G, P //
// "pressure": 10, void PushTarget::sendBrewfather(float angle, float gravity, float corrGravity,
// "pressure_unit": "PSI", // PSI, BAR, KPA float temp) {
// "ph": 4.12, #if !defined(PUSH_DISABLE_LOGGING)
// "bpm": 123, // Bubbles Per Minute Log.notice(
// "comment": "Hello World", F("PUSH: Sending values to brewfather angle=%F, gravity=%F, temp=%F." CR),
// "beer": "Pale Ale" angle, gravity, temp);
// "battery": 4.98 #endif
// }
// DynamicJsonDocument doc(300);
doc["name"] = myConfig.getMDNS(); //
doc["temp"] = reduceFloatPrecision( temp, 1); // {
doc["temp_unit"] = String( myConfig.getTempFormat() ); // "name": "YourDeviceName", // Required field, this will be the ID in
doc["battery"] = reduceFloatPrecision( myBatteryVoltage.getVoltage(), 2 ); // Brewfather "temp": 20.32, "aux_temp": 15.61, // Fridge Temp
// TODO: Add support for plato format // "ext_temp": 6.51, // Room Temp
doc["gravity"] = reduceFloatPrecision( myConfig.isGravityTempAdj() ? corrGravity : gravity, 4 ); // "temp_unit": "C", // C, F, K
doc["gravity_unit"] = myConfig.isGravitySG()?"G":"P"; // "gravity": 1.042,
// "gravity_unit": "G", // G, P
WiFiClient client; // "pressure": 10,
HTTPClient http; // "pressure_unit": "PSI", // PSI, BAR, KPA
String serverPath = myConfig.getBrewfatherPushUrl(); // "ph": 4.12,
// "bpm": 123, // Bubbles Per Minute
// Your Domain name with URL path or IP address with path // "comment": "Hello World",
http.begin( client, serverPath); // "beer": "Pale Ale"
String json; // "battery": 4.98
serializeJson(doc, json); // }
#if LOG_LEVEL==6 && !defined( PUSH_DISABLE_LOGGING ) //
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str()); doc["name"] = myConfig.getMDNS();
Log.verbose(F("PUSH: json %s." CR), json.c_str()); doc["temp"] = reduceFloatPrecision(temp, 1);
#endif doc["temp_unit"] = String(myConfig.getTempFormat());
doc["battery"] = reduceFloatPrecision(myBatteryVoltage.getVoltage(), 2);
// Send HTTP POST request // TODO: Add support for plato format
http.addHeader(F("Content-Type"), F("application/json") ); doc["gravity"] = reduceFloatPrecision(
int httpResponseCode = http.POST(json); myConfig.isGravityTempAdj() ? corrGravity : gravity, 4);
doc["gravity_unit"] = myConfig.isGravitySG() ? "G" : "P";
if (httpResponseCode==200) {
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR), httpResponseCode); WiFiClient client;
} else { HTTPClient http;
Log.error(F("PUSH: Brewfather push failed, response=%d" CR), httpResponseCode); String serverPath = myConfig.getBrewfatherPushUrl();
}
// Your Domain name with URL path or IP address with path
http.end(); http.begin(client, serverPath);
} String json;
serializeJson(doc, json);
// #if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
// Send data to http target Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
// Log.verbose(F("PUSH: json %s." CR), json.c_str());
void PushTarget::sendHttp( String serverPath, float angle, float gravity, float corrGravity, float temp, float runTime ) { #endif
#if !defined( PUSH_DISABLE_LOGGING )
Log.notice(F("PUSH: Sending values to http angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp ); // Send HTTP POST request
#endif http.addHeader(F("Content-Type"), F("application/json"));
int httpResponseCode = http.POST(json);
DynamicJsonDocument doc(256);
if (httpResponseCode == 200) {
// Use iSpindle format for compatibility Log.notice(F("PUSH: Brewfather push successful, response=%d" CR),
doc["name"] = myConfig.getMDNS(); httpResponseCode);
doc["ID"] = myConfig.getID(); } else {
doc["token"] = "gravmon"; Log.error(F("PUSH: Brewfather push failed, response=%d" CR),
doc["interval"] = myConfig.getSleepInterval(); httpResponseCode);
doc["temperature"] = reduceFloatPrecision( temp, 1 ); }
doc["temp-units"] = String( myConfig.getTempFormat() );
// TODO: Add support for plato format http.end();
doc["gravity"] = reduceFloatPrecision( myConfig.isGravityTempAdj() ? corrGravity : gravity, 4 ); }
doc["corr-gravity"] = reduceFloatPrecision( corrGravity, 4 );
doc["angle"] = reduceFloatPrecision( angle, 2); //
doc["battery"] = reduceFloatPrecision( myBatteryVoltage.getVoltage(), 2 ); // Send data to http target
doc["rssi"] = WiFi.RSSI(); //
void PushTarget::sendHttp(String serverPath, float angle, float gravity,
// Some additional information float corrGravity, float temp, float runTime) {
doc["gravity-units"] = "SG"; #if !defined(PUSH_DISABLE_LOGGING)
doc["run-time"] = reduceFloatPrecision( runTime, 2 ); Log.notice(
F("PUSH: Sending values to http angle=%F, gravity=%F, temp=%F." CR),
WiFiClient client; angle, gravity, temp);
HTTPClient http; #endif
// Your Domain name with URL path or IP address with path DynamicJsonDocument doc(256);
http.begin( client, serverPath);
String json; // Use iSpindle format for compatibility
serializeJson(doc, json); doc["name"] = myConfig.getMDNS();
#if LOG_LEVEL==6 && !defined( PUSH_DISABLE_LOGGING ) doc["ID"] = myConfig.getID();
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str()); doc["token"] = "gravmon";
Log.verbose(F("PUSH: json %s." CR), json.c_str()); doc["interval"] = myConfig.getSleepInterval();
#endif doc["temperature"] = reduceFloatPrecision(temp, 1);
doc["temp-units"] = String(myConfig.getTempFormat());
// Send HTTP POST request // TODO: Add support for plato format
http.addHeader(F("Content-Type"), F("application/json") ); doc["gravity"] = reduceFloatPrecision(
int httpResponseCode = http.POST(json); myConfig.isGravityTempAdj() ? corrGravity : gravity, 4);
doc["corr-gravity"] = reduceFloatPrecision(corrGravity, 4);
if (httpResponseCode==200) { doc["angle"] = reduceFloatPrecision(angle, 2);
Log.notice(F("PUSH: HTTP push successful, response=%d" CR), httpResponseCode); doc["battery"] = reduceFloatPrecision(myBatteryVoltage.getVoltage(), 2);
} else { doc["rssi"] = WiFi.RSSI();
Log.error(F("PUSH: HTTP push failed, response=%d" CR), httpResponseCode);
} // Some additional information
doc["gravity-units"] = "SG";
http.end(); doc["run-time"] = reduceFloatPrecision(runTime, 2);
}
WiFiClient client;
// EOF HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(client, serverPath);
String json;
serializeJson(doc, json);
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
Log.verbose(F("PUSH: json %s." CR), json.c_str());
#endif
// Send HTTP POST request
http.addHeader(F("Content-Type"), F("application/json"));
int httpResponseCode = http.POST(json);
if (httpResponseCode == 200) {
Log.notice(F("PUSH: HTTP push successful, response=%d" CR),
httpResponseCode);
} else {
Log.error(F("PUSH: HTTP push failed, response=%d" CR), httpResponseCode);
}
http.end();
}
// EOF

View File

@ -1,53 +1,57 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#ifndef _PUSHTARGET_H #ifndef SRC_PUSHTARGET_HPP_
#define _PUSHTARGET_H #define SRC_PUSHTARGET_HPP_
// Includes // Includes
#include "helper.h" #include <Arduino.h>
#include <ArduinoJson.h>
#include <Arduino.h> #include <ESP8266HTTPClient.h>
#include <ArduinoJson.h> #include <ESP8266WiFi.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h> #include <helper.hpp>
// Classes // Classes
class PushTarget { class PushTarget {
private: private:
unsigned long ms; // Used to check that we do not post to often uint32_t ms; // Used to check that we do not post to often
void sendBrewfather(float angle, float gravity, float corrGravity, float temp ); void sendBrewfather(float angle, float gravity, float corrGravity,
void sendHttp(String serverPath, float angle, float gravity, float corrGravity, float temp, float runTime); float temp);
void sendInfluxDb2(float angle, float gravity, float corrGravity, float temp, float runTime); void sendHttp(String serverPath, float angle, float gravity,
float corrGravity, float temp, float runTime);
public: void sendInfluxDb2(float angle, float gravity, float corrGravity, float temp,
PushTarget() { ms = millis(); } float runTime);
void send(float angle, float gravity, float corrGravity, float temp, float runTime, bool force = false );
}; public:
PushTarget() { ms = millis(); }
extern PushTarget myPushTarget; void send(float angle, float gravity, float corrGravity, float temp,
float runTime, bool force = false);
#endif // _PUSHTARGET_H };
// EOF extern PushTarget myPushTarget;
#endif // SRC_PUSHTARGET_HPP_
// EOF

View File

@ -1,43 +1,43 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#define INCBIN_OUTPUT_SECTION ".irom.text" #define INCBIN_OUTPUT_SECTION ".irom.text"
#include <incbin.h> #include <incbin.h>
#if defined( EMBED_HTML ) #if defined(EMBED_HTML)
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7% // Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
INCBIN(IndexHtm, "data/index.min.htm" ); INCBIN(IndexHtm, "data/index.min.htm");
INCBIN(DeviceHtm, "data/device.min.htm" ); INCBIN(DeviceHtm, "data/device.min.htm");
INCBIN(ConfigHtm, "data/config.min.htm" ); INCBIN(ConfigHtm, "data/config.min.htm");
INCBIN(CalibrationHtm, "data/calibration.min.htm" ); INCBIN(CalibrationHtm, "data/calibration.min.htm");
INCBIN(AboutHtm, "data/about.min.htm" ); INCBIN(AboutHtm, "data/about.min.htm");
#else #else
// Minium web interface for uploading htm files // Minium web interface for uploading htm files
INCBIN(UploadHtm, "data/upload.min.htm" ); INCBIN(UploadHtm, "data/upload.min.htm");
#endif #endif
// EOF // EOF

View File

@ -1,135 +1,135 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "tempsensor.h" #include <DallasTemperature.h>
#include "helper.h" #include <Wire.h>
#include "config.h" #include <onewire.h>
#include "gyro.h"
#include <onewire.h> #include <config.hpp>
#include <DallasTemperature.h> #include <gyro.hpp>
#include <Wire.h> #include <helper.hpp>
#include <tempsensor.hpp>
//
// Conversion between C and F //
// // Conversion between C and F
float convertCtoF( float t ) { //
return (t * 1.8 ) + 32.0; float convertCtoF(float t) { return (t * 1.8) + 32.0; }
}
#if !defined(USE_GYRO_TEMP)
#if !defined( USE_GYRO_TEMP ) OneWire myOneWire(D6);
OneWire myOneWire(D6); DallasTemperature mySensors(&myOneWire);
DallasTemperature mySensors(&myOneWire); #define TEMPERATURE_PRECISION 9
#define TEMPERATURE_PRECISION 9 #endif
#endif
TempSensor myTempSensor;
TempSensor myTempSensor;
//
// // Setup temp sensors
// Setup temp sensors //
// void TempSensor::setup() {
void TempSensor::setup() { #if defined(SIMULATE_TEMP)
hasSensors = true;
#if defined( SIMULATE_TEMP ) return;
hasSensors = true; #endif
return;
#endif #if defined(USE_GYRO_TEMP)
Log.notice(F("TSEN: Using temperature from gyro." CR));
#if defined( USE_GYRO_TEMP ) #else
Log.notice(F("TSEN: Using temperature from gyro." CR)); #if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
#else Log.verbose(F("TSEN: Looking for temp sensors." CR));
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING ) #endif
Log.verbose(F("TSEN: Looking for temp sensors." CR)); mySensors.begin();
#endif
mySensors.begin(); if (mySensors.getDS18Count()) {
#if !defined(TSEN_DISABLE_LOGGING)
if( mySensors.getDS18Count() ) { Log.notice(F("TSEN: Found %d temperature sensor(s)." CR),
#if !defined( TSEN_DISABLE_LOGGING ) mySensors.getDS18Count());
Log.notice(F("TSEN: Found %d temperature sensor(s)." CR), mySensors.getDS18Count()); #endif
#endif mySensors.setResolution(TEMPERATURE_PRECISION);
mySensors.setResolution(TEMPERATURE_PRECISION); }
} #endif
#endif
float t = myConfig.getTempSensorAdj();
float t = myConfig.getTempSensorAdj();
// Set the temp sensor adjustment values
// Set the temp sensor adjustment values if (myConfig.isTempC()) {
if( myConfig.isTempC() ) { tempSensorAdjF = t * 1.8; // Convert the adjustment value to C
tempSensorAdjF = t * 1.8; // Convert the adjustment value to C tempSensorAdjC = t;
tempSensorAdjC = t; } else {
} else { tempSensorAdjF = t;
tempSensorAdjF = t; tempSensorAdjC = t * 0.556; // Convert the adjustent value to F
tempSensorAdjC = t * 0.556; // Convert the adjustent value to F }
}
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING ) Log.verbose(F("TSEN: Adjustment values for temp sensor %F C, %F F." CR),
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C, %F F." CR), tempSensorAdjC, tempSensorAdjF ); tempSensorAdjC, tempSensorAdjF);
#endif #endif
} }
// //
// Retrieving value from sensor, value is in Celcius // Retrieving value from sensor, value is in Celcius
// //
float TempSensor::getValue() { float TempSensor::getValue() {
#if defined( SIMULATE_TEMP ) #if defined(SIMULATE_TEMP)
return 21; return 21;
#endif #endif
#if defined( USE_GYRO_TEMP ) #if defined(USE_GYRO_TEMP)
// When using the gyro temperature only the first read value will be accurate so we will use this for processing. // When using the gyro temperature only the first read value will be accurate
//LOG_PERF_START("temp-get"); // so we will use this for processing. LOG_PERF_START("temp-get");
float c = myGyro.getInitialSensorTempC(); float c = myGyro.getInitialSensorTempC();
//LOG_PERF_STOP("temp-get"); // LOG_PERF_STOP("temp-get");
hasSensor = true; hasSensor = true;
return c; return c;
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING ) #if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c); Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
#endif #endif
#else #else
// If we dont have sensors just return 0 // If we dont have sensors just return 0
if( !mySensors.getDS18Count() ) { if (!mySensors.getDS18Count()) {
Log.error(F("TSEN: No temperature sensors found. Skipping read." CR)); Log.error(F("TSEN: No temperature sensors found. Skipping read." CR));
return -273; return -273;
} }
// Read the sensors // Read the sensors
//LOG_PERF_START("temp-request"); // LOG_PERF_START("temp-request");
mySensors.requestTemperatures(); mySensors.requestTemperatures();
//LOG_PERF_STOP("temp-request"); // LOG_PERF_STOP("temp-request");
float c = 0; float c = 0;
if( mySensors.getDS18Count() >= 1) { if (mySensors.getDS18Count() >= 1) {
//LOG_PERF_START("temp-get"); // LOG_PERF_START("temp-get");
c = mySensors.getTempCByIndex(0); c = mySensors.getTempCByIndex(0);
//LOG_PERF_STOP("temp-get"); // LOG_PERF_STOP("temp-get");
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING ) #if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Reciving temp value for sensor %F C." CR), c); Log.verbose(F("TSEN: Reciving temp value for sensor %F C." CR), c);
#endif #endif
hasSensor = true; hasSensor = true;
} }
return c; return c;
#endif #endif
} }
// EOF // EOF

View File

@ -1,50 +1,50 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#ifndef _TEMPSENSOR_H #ifndef SRC_TEMPSENSOR_HPP_
#define _TEMPSENSOR_H #define SRC_TEMPSENSOR_HPP_
// definitions // definitions
float convertCtoF( float t ); float convertCtoF(float t);
// classes // classes
class TempSensor { class TempSensor {
private: private:
bool hasSensor = false; bool hasSensor = false;
float tempSensorAdjF = 0; float tempSensorAdjF = 0;
float tempSensorAdjC = 0; float tempSensorAdjC = 0;
float getValue(); float getValue();
public: public:
void setup(); void setup();
bool isSensorAttached() { return hasSensor; }; bool isSensorAttached() { return hasSensor; }
float getTempC() { return getValue() + tempSensorAdjC; } float getTempC() { return getValue() + tempSensorAdjC; }
float getTempF() { return convertCtoF(getValue()) + tempSensorAdjF; }; float getTempF() { return convertCtoF(getValue()) + tempSensorAdjF; }
}; };
// Global instance created // Global instance created
extern TempSensor myTempSensor; extern TempSensor myTempSensor;
#endif // _TEMPSENSOR_H #endif // SRC_TEMPSENSOR_HPP_
// EOF // EOF

File diff suppressed because it is too large Load Diff

View File

@ -1,100 +0,0 @@
/*
MIT License
Copyright (c) 2021 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.
*/
#ifndef _WEBSERVER_H
#define _WEBSERVER_H
// Include
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <incbin.h>
// Binary resouces
#if defined( EMBED_HTML )
INCBIN_EXTERN(IndexHtm);
INCBIN_EXTERN(DeviceHtm);
INCBIN_EXTERN(ConfigHtm);
INCBIN_EXTERN(CalibrationHtm);
INCBIN_EXTERN(AboutHtm);
#else
INCBIN_EXTERN(UploadHtm);
#endif
// classes
class WebServer {
private:
ESP8266WebServer *server = 0;
File uploadFile;
int lastFormulaCreateError = 0;
void webHandleConfig();
void webHandleFormulaWrite();
void webHandleFormulaRead();
void webHandleConfigHardware();
void webHandleConfigGravity();
void webHandleConfigPush();
void webHandleConfigDevice();
void webHandleStatusSleepmode();
void webHandleClearWIFI();
void webHandleStatus();
void webHandleFactoryReset();
void webHandleCalibrate();
void webHandleUploadFile();
void webHandleUpload();
void webHandleDevice();
void webHandlePageNotFound();
// Inline functions.
void webReturnOK() { server->send(200); }
#if defined( EMBED_HTML )
void webReturnIndexHtm() { server->send_P(200, "text/html", (const char*) gIndexHtmData, gIndexHtmSize ); }
void webReturnDeviceHtm() { server->send_P(200, "text/html", (const char*) gDeviceHtmData, gDeviceHtmSize ); }
void webReturnConfigHtm() { server->send_P(200, "text/html", (const char*) gConfigHtmData, gConfigHtmSize ); }
void webReturnCalibrationHtm() { server->send_P(200, "text/html", (const char*) gCalibrationHtmData, gCalibrationHtmSize ); }
void webReturnAboutHtm() { server->send_P(200, "text/html", (const char*) gAboutHtmData, gAboutHtmSize ); }
#else
void webReturnUploadHtm() { server->send_P(200, "text/html", (const char*) gUploadHtmData, gUploadHtmSize ); }
#endif
public:
enum HtmlFile {
HTML_INDEX = 0,
HTML_DEVICE = 1,
HTML_CONFIG = 2,
HTML_ABOUT = 3,
HTML_CALIBRATION = 4
};
bool setupWebServer();
void loop();
bool checkHtmlFile( HtmlFile item );
const char* getHtmlFileName( HtmlFile item );
};
// Global instance created
extern WebServer myWebServer;
#endif // _WEBSERVER_H
// EOF

116
src/webserver.hpp Normal file
View File

@ -0,0 +1,116 @@
/*
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.
*/
#ifndef SRC_WEBSERVER_HPP_
#define SRC_WEBSERVER_HPP_
// Include
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <incbin.h>
// Binary resouces
#if defined(EMBED_HTML)
INCBIN_EXTERN(IndexHtm);
INCBIN_EXTERN(DeviceHtm);
INCBIN_EXTERN(ConfigHtm);
INCBIN_EXTERN(CalibrationHtm);
INCBIN_EXTERN(AboutHtm);
#else
INCBIN_EXTERN(UploadHtm);
#endif
// classes
class WebServer {
private:
ESP8266WebServer* server = 0;
File uploadFile;
int lastFormulaCreateError = 0;
void webHandleConfig();
void webHandleFormulaWrite();
void webHandleFormulaRead();
void webHandleConfigHardware();
void webHandleConfigGravity();
void webHandleConfigPush();
void webHandleConfigDevice();
void webHandleStatusSleepmode();
void webHandleClearWIFI();
void webHandleStatus();
void webHandleFactoryReset();
void webHandleCalibrate();
void webHandleUploadFile();
void webHandleUpload();
void webHandleDevice();
void webHandlePageNotFound();
// Inline functions.
void webReturnOK() { server->send(200); }
#if defined(EMBED_HTML)
void webReturnIndexHtm() {
server->send_P(200, "text/html", (const char*)gIndexHtmData, gIndexHtmSize);
}
void webReturnDeviceHtm() {
server->send_P(200, "text/html", (const char*)gDeviceHtmData,
gDeviceHtmSize);
}
void webReturnConfigHtm() {
server->send_P(200, "text/html", (const char*)gConfigHtmData,
gConfigHtmSize);
}
void webReturnCalibrationHtm() {
server->send_P(200, "text/html", (const char*)gCalibrationHtmData,
gCalibrationHtmSize);
}
void webReturnAboutHtm() {
server->send_P(200, "text/html", (const char*)gAboutHtmData, gAboutHtmSize);
}
#else
void webReturnUploadHtm() {
server->send_P(200, "text/html", (const char*)gUploadHtmData,
gUploadHtmSize);
}
#endif
public:
enum HtmlFile {
HTML_INDEX = 0,
HTML_DEVICE = 1,
HTML_CONFIG = 2,
HTML_ABOUT = 3,
HTML_CALIBRATION = 4
};
bool setupWebServer();
void loop();
bool checkHtmlFile(HtmlFile item);
const char* getHtmlFileName(HtmlFile item);
};
// Global instance created
extern WebServer myWebServer;
#endif // SRC_WEBSERVER_HPP_
// EOF

View File

@ -1,284 +1,306 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "wifi.h" #include <ArduinoJson.h>
#include "config.h" #include <ESP8266HTTPClient.h>
#include "helper.h" #include <ESP8266httpUpdate.h>
#include "gyro.h" #include <ESP8266mDNS.h>
#include "calc.h" #include <LittleFS.h>
#include "tempsensor.h" #include <WiFiManager.h>
#include <ArduinoJson.h> #include <incbin.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPClient.h> #include <calc.hpp>
#include <ESP8266httpUpdate.h> #include <config.hpp>
#include <WiFiManager.h> #include <gyro.hpp>
#include <LittleFS.h> #include <helper.hpp>
#include <incbin.h> #include <tempsensor.hpp>
#include <wifi.hpp>
Wifi myWifi;
Wifi myWifi;
const char* userSSID= USER_SSID;
const char* userPWD = USER_SSID_PWD; const char *userSSID = USER_SSID;
const char *userPWD = USER_SSID_PWD;
//
// Connect to last known access point or create one if connection is not working. //
// // Connect to last known access point or create one if connection is not
bool Wifi::connect( bool showPortal ) { // working.
//
WiFi.persistent( true ); bool Wifi::connect(bool showPortal) {
WiFi.mode(WIFI_STA); WiFi.persistent(true);
WiFi.mode(WIFI_STA);
if( !strlen( myConfig.getWifiSSID() ) ) {
Log.info(F("WIFI: No SSID seams to be stored, forcing portal to start." CR)); if (!strlen(myConfig.getWifiSSID())) {
showPortal = true; Log.info(
} else { F("WIFI: No SSID seams to be stored, forcing portal to start." CR));
//Log.info(F("WIFI: Using SSID=%s and %s." CR), myConfig.getWifiSSID(), myConfig.getWifiPass() ); showPortal = true;
//Log.info(F("WIFI: Using SSID=%s and %s." CR), myConfig.getWifiSSID(), "*****" ); } else {
} // Log.info(F("WIFI: Using SSID=%s and %s." CR), myConfig.getWifiSSID(),
// myConfig.getWifiPass()); Log.info(F("WIFI: Using SSID=%s and %s." CR),
if( strlen(userSSID)==0 && showPortal ) { // myConfig.getWifiSSID(), "*****");
#if LOG_LEVEL==6 }
Log.verbose(F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR), showPortal?"true":"false");
#endif if (strlen(userSSID) == 0 && showPortal) {
WiFiManager myWifiManager; #if LOG_LEVEL == 6
Log.notice(F("WIFI: Starting wifi portal." CR)); Log.verbose(
myWifiManager.setDebugOutput(true); F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR),
myWifiManager.setClass("invert"); showPortal ? "true" : "false");
myWifiManager.setConfigPortalTimeout( 120 ); // Keep it open for 120 seconds #endif
bool f = myWifiManager.startConfigPortal( WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD ); WiFiManager myWifiManager;
if( f ) { Log.notice(F("WIFI: Starting wifi portal." CR));
//Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR), myWifiManager.getWiFiSSID(), myWifiManager.getWiFiPass() ); myWifiManager.setDebugOutput(true);
Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR), myWifiManager.getWiFiSSID(), "*****" ); myWifiManager.setClass("invert");
myConfig.setWifiSSID( myWifiManager.getWiFiSSID() ); myWifiManager.setConfigPortalTimeout(120); // Keep it open for 120 seconds
myConfig.setWifiPass( myWifiManager.getWiFiPass() ); bool f =
myConfig.saveFile(); myWifiManager.startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD);
} else { if (f) {
Log.notice(F("WIFI: Failure from WIFI portal, rebooting." CR) ); // Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR),
delay(200); // myWifiManager.getWiFiSSID(), myWifiManager.getWiFiPass() );
ESP.reset(); Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR),
} myWifiManager.getWiFiSSID(), "*****");
} myConfig.setWifiSSID(myWifiManager.getWiFiSSID());
myConfig.setWifiPass(myWifiManager.getWiFiPass());
// Connect to wifi myConfig.saveFile();
int i = 0; } else {
Log.notice(F("WIFI: Failure from WIFI portal, rebooting." CR));
//Log.notice(F("WIFI: Connecting to WIFI, mode=%d,persistent=%d,fhy=%d ." CR), WiFi.getMode(), WiFi.getPersistent(), WiFi.getPhyMode() ); delay(200);
WiFi.mode(WIFI_STA); ESP.reset();
if( strlen(userSSID) ) { }
Log.notice(F("WIFI: Connecting to wifi using hardcoded settings %s." CR), userSSID); }
WiFi.begin( userSSID, userPWD );
} else { // Connect to wifi
Log.notice(F("WIFI: Connecting to wifi using stored settings %s." CR), myConfig.getWifiSSID()); int i = 0;
WiFi.begin(myConfig.getWifiSSID(), myConfig.getWifiPass());
} // Log.notice(F("WIFI: Connecting to WIFI, mode=%d,persistent=%d,fhy=%d ."
// CR), WiFi.getMode(), WiFi.getPersistent(), WiFi.getPhyMode() );
//WiFi.printDiag(Serial); WiFi.mode(WIFI_STA);
if (strlen(userSSID)) {
while( WiFi.status() != WL_CONNECTED ) { Log.notice(F("WIFI: Connecting to wifi using hardcoded settings %s." CR),
delay(200); userSSID);
Serial.print( "." ); WiFi.begin(userSSID, userPWD);
} else {
if( i++ > 100 ) { // Try for 20 seconds. Log.notice(F("WIFI: Connecting to wifi using stored settings %s." CR),
Log.error(F("WIFI: Failed to connect to wifi %d, aborting %s." CR), WiFi.status(), getIPAddress().c_str() ); myConfig.getWifiSSID());
WiFi.disconnect(); WiFi.begin(myConfig.getWifiSSID(), myConfig.getWifiPass());
return connectedFlag; // Return to main that we have failed to connect. }
}
} // WiFi.printDiag(Serial);
Serial.print( CR );
connectedFlag = true; while (WiFi.status() != WL_CONNECTED) {
Log.notice(F("WIFI: Connected to wifi ip=%s." CR), getIPAddress().c_str() ); delay(200);
Log.notice(F("WIFI: Using mDNS name %s." CR), myConfig.getMDNS() ); Serial.print(".");
return connectedFlag;
} if (i++ > 100) { // Try for 20 seconds.
Log.error(F("WIFI: Failed to connect to wifi %d, aborting %s." CR),
// WiFi.status(), getIPAddress().c_str());
// This will erase the stored credentials and forcing the WIFI manager to AP mode. WiFi.disconnect();
// return connectedFlag; // Return to main that we have failed to connect.
bool Wifi::disconnect() { }
Log.notice(F("WIFI: Erasing stored WIFI credentials." CR)); }
// Erase WIFI credentials Serial.print(CR);
return WiFi.disconnect(true); connectedFlag = true;
} Log.notice(F("WIFI: Connected to wifi ip=%s." CR), getIPAddress().c_str());
Log.notice(F("WIFI: Using mDNS name %s." CR), myConfig.getMDNS());
#if defined( ACTIVATE_OTA ) return connectedFlag;
}
//
// //
// // This will erase the stored credentials and forcing the WIFI manager to AP
bool Wifi::updateFirmware() { // mode.
if( !newFirmware ) { //
Log.notice(F("WIFI: No newer version exist, skipping update." CR)); bool Wifi::disconnect() {
return false; Log.notice(F("WIFI: Erasing stored WIFI credentials." CR));
} // Erase WIFI credentials
#if LOG_LEVEL==6 return WiFi.disconnect(true);
Log.verbose(F("WIFI: Updating firmware." CR)); }
#endif
#if defined(ACTIVATE_OTA)
WiFiClient client;
String serverPath = myConfig.getOtaURL(); //
serverPath += "firmware.bin"; //
//
HTTPUpdateResult ret = ESPhttpUpdate.update(client, serverPath); bool Wifi::updateFirmware() {
if (!newFirmware) {
switch(ret) { Log.notice(F("WIFI: No newer version exist, skipping update." CR));
case HTTP_UPDATE_FAILED: return false;
Log.error(F("WIFI: OTA update failed %d, %s." CR), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); }
break; #if LOG_LEVEL == 6
case HTTP_UPDATE_NO_UPDATES: Log.verbose(F("WIFI: Updating firmware." CR));
break; #endif
case HTTP_UPDATE_OK:
Log.notice("WIFI: OTA Update sucesfull, rebooting." ); WiFiClient client;
delay(100); String serverPath = myConfig.getOtaURL();
ESP.reset(); serverPath += "firmware.bin";
break;
} HTTPUpdateResult ret = ESPhttpUpdate.update(client, serverPath);
return false;
} switch (ret) {
case HTTP_UPDATE_FAILED:
// Log.error(F("WIFI: OTA update failed %d, %s." CR),
// Download and save file ESPhttpUpdate.getLastError(),
// ESPhttpUpdate.getLastErrorString().c_str());
void Wifi::downloadFile(const char *fname) { break;
#if LOG_LEVEL==6 case HTTP_UPDATE_NO_UPDATES:
Log.verbose(F("WIFI: Download file %s." CR), fname); break;
#endif case HTTP_UPDATE_OK:
WiFiClient client; Log.notice("WIFI: OTA Update sucesfull, rebooting.");
HTTPClient http; delay(100);
String serverPath = myConfig.getOtaURL(); ESP.reset();
serverPath += fname; break;
}
// Your Domain name with URL path or IP address with path return false;
http.begin( client, serverPath); }
int httpResponseCode = http.GET();
//
if (httpResponseCode==200) { // Download and save file
File f = LittleFS.open( fname, "w" ); //
http.writeToStream( &f ); void Wifi::downloadFile(const char *fname) {
f.close(); #if LOG_LEVEL == 6
Log.notice(F("WIFI: Downloaded file %s." CR), fname); Log.verbose(F("WIFI: Download file %s." CR), fname);
} else { #endif
Log.error(F("WIFI: Failed to download file, respone=%d" CR), httpResponseCode); WiFiClient client;
} HTTPClient http;
http.end(); String serverPath = myConfig.getOtaURL();
} serverPath += fname;
// // Your Domain name with URL path or IP address with path
// Check what firmware version is available over OTA http.begin(client, serverPath);
// int httpResponseCode = http.GET();
bool Wifi::checkFirmwareVersion() {
#if LOG_LEVEL==6 if (httpResponseCode == 200) {
Log.verbose(F("WIFI: Checking if new version exist." CR)); File f = LittleFS.open(fname, "w");
#endif http.writeToStream(&f);
WiFiClient client; f.close();
HTTPClient http; Log.notice(F("WIFI: Downloaded file %s." CR), fname);
String serverPath = myConfig.getOtaURL(); } else {
serverPath += "version.json"; Log.error(F("WIFI: Failed to download file, respone=%d" CR),
httpResponseCode);
// Your Domain name with URL path or IP address with path }
http.begin( client, serverPath); http.end();
}
// Send HTTP GET request
int httpResponseCode = http.GET(); //
// Check what firmware version is available over OTA
if (httpResponseCode==200) { //
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode); bool Wifi::checkFirmwareVersion() {
#if LOG_LEVEL == 6
String payload = http.getString(); Log.verbose(F("WIFI: Checking if new version exist." CR));
#if LOG_LEVEL==6 #endif
Log.verbose(F("WIFI: Payload %s." CR), payload.c_str()); WiFiClient client;
#endif HTTPClient http;
DynamicJsonDocument ver(300); String serverPath = myConfig.getOtaURL();
DeserializationError err = deserializeJson(ver, payload); serverPath += "version.json";
if( err ) {
Log.error(F("WIFI: Failed to parse version.json, %s" CR), err); // Your Domain name with URL path or IP address with path
} else { http.begin(client, serverPath);
#if LOG_LEVEL==6
Log.verbose(F("WIFI: Project %s version %s." CR), (const char*) ver["project"], (const char*) ver["version"]); // Send HTTP GET request
#endif int httpResponseCode = http.GET();
int newVer[3];
int curVer[3]; if (httpResponseCode == 200) {
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode);
if( parseFirmwareVersionString( newVer, (const char*) ver["version"] ) ) {
if( parseFirmwareVersionString( curVer, CFG_APPVER) ) { String payload = http.getString();
#if LOG_LEVEL==6 #if LOG_LEVEL == 6
Log.verbose(F("WIFI: OTA checking new=%d.%d.%d cur=%d.%d.%d" CR), newVer[0], newVer[1], newVer[2], curVer[0], curVer[1], curVer[2] ); Log.verbose(F("WIFI: Payload %s." CR), payload.c_str());
#endif #endif
// Compare major version DynamicJsonDocument ver(300);
if( newVer[0] > curVer[0] ) DeserializationError err = deserializeJson(ver, payload);
newFirmware = true; if (err) {
// Compare minor version Log.error(F("WIFI: Failed to parse version.json, %s" CR), err);
if( newVer[0] == curVer[0] && newVer[1] > curVer[1] ) } else {
newFirmware = true; #if LOG_LEVEL == 6
// Compare patch version Log.verbose(F("WIFI: Project %s version %s." CR),
if( newVer[0] == curVer[0] && newVer[1] == curVer[1] && newVer[2] > curVer[2] ) (const char *)ver["project"], (const char *)ver["version"]);
newFirmware = true; #endif
} int newVer[3];
} int curVer[3];
// Download new html files to filesystem if they are present. if (parseFirmwareVersionString(newVer, (const char *)ver["version"])) {
if( !ver["html"].isNull() && newFirmware ) { if (parseFirmwareVersionString(curVer, CFG_APPVER)) {
Log.notice(F("WIFI: OTA downloading new html files." CR)); #if LOG_LEVEL == 6
JsonArray htmlFiles = ver["html"].as<JsonArray>(); Log.verbose(F("WIFI: OTA checking new=%d.%d.%d cur=%d.%d.%d" CR),
for(JsonVariant v : htmlFiles) { newVer[0], newVer[1], newVer[2], curVer[0], curVer[1],
String s = v; curVer[2]);
#if LOG_LEVEL==6 #endif
Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str() ); // Compare major version
#endif if (newVer[0] > curVer[0]) newFirmware = true;
downloadFile( s.c_str() ); // Compare minor version
} if (newVer[0] == curVer[0] && newVer[1] > curVer[1])
} newFirmware = true;
} // Compare patch version
} else { if (newVer[0] == curVer[0] && newVer[1] == curVer[1] &&
Log.error(F("WIFI: OTA error checking version.json, response=%d" CR), httpResponseCode); newVer[2] > curVer[2])
} newFirmware = true;
http.end(); }
#if LOG_LEVEL==6 }
Log.verbose(F("WIFI: OTA found new version %s." CR), newFirmware?"true":"false");
#endif // Download new html files to filesystem if they are present.
return newFirmware; if (!ver["html"].isNull() && newFirmware) {
} Log.notice(F("WIFI: OTA downloading new html files." CR));
JsonArray htmlFiles = ver["html"].as<JsonArray>();
// for (JsonVariant v : htmlFiles) {
// Parse a version string in the format M.m.p (eg. 1.2.10) String s = v;
// #if LOG_LEVEL == 6
bool Wifi::parseFirmwareVersionString( int (&num)[3], const char *version ) { Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str());
#if LOG_LEVEL==6 #endif
Log.verbose(F("WIFI: Parsing version number string %s." CR), version); downloadFile(s.c_str());
#endif }
char temp[80]; }
char *s; }
char *p = &temp[0]; } else {
int i = 0; Log.error(F("WIFI: OTA error checking version.json, response=%d" CR),
httpResponseCode);
strcpy( &temp[0], version ); }
http.end();
while ((s = strtok_r(p, ".", &p)) != NULL) { #if LOG_LEVEL == 6
num[i++] = atoi( s ); Log.verbose(F("WIFI: OTA found new version %s." CR),
} newFirmware ? "true" : "false");
#endif
return true; return newFirmware;
} }
#endif // ACTIVATE_OTA //
// Parse a version string in the format M.m.p (eg. 1.2.10)
// EOF //
bool Wifi::parseFirmwareVersionString(int (&num)[3], const char *version) {
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Parsing version number string %s." CR), version);
#endif
char temp[80];
char *s;
char *p = &temp[0];
int i = 0;
// strcpy(&temp[0], version);
snprintf(&temp[0], sizeof(temp), "%s", version);
while ((s = strtok_r(p, ".", &p)) != NULL) {
num[i++] = atoi(s);
}
return true;
}
#endif // ACTIVATE_OTA
// EOF

View File

@ -1,58 +1,58 @@
/* /*
MIT License MIT License
Copyright (c) 2021 Magnus Copyright (c) 2021-22 Magnus
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#ifndef _WIFI_H #ifndef SRC_WIFI_HPP_
#define _WIFI_H #define SRC_WIFI_HPP_
// Include // Include
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
// classes // classes
class Wifi { class Wifi {
private: private:
// WIFI // WIFI
bool connectedFlag = false; bool connectedFlag = false;
// OTA // OTA
bool newFirmware = false; bool newFirmware = false;
bool parseFirmwareVersionString( int (&num)[3], const char *version ); bool parseFirmwareVersionString(int (&num)[3], const char *version);
void downloadFile(const char *fname); void downloadFile(const char *fname);
public: public:
// WIFI // WIFI
bool connect( bool showPortal = false ); bool connect(bool showPortal = false);
bool disconnect(); bool disconnect();
bool isConnected() { return connectedFlag; }; bool isConnected() { return connectedFlag; }
String getIPAddress() { return WiFi.localIP().toString(); }; String getIPAddress() { return WiFi.localIP().toString(); }
// OTA // OTA
bool updateFirmware(); bool updateFirmware();
bool checkFirmwareVersion(); bool checkFirmwareVersion();
}; };
// Global instance created // Global instance created
extern Wifi myWifi; extern Wifi myWifi;
#endif // _WIFI_H #endif // SRC_WIFI_HPP_
// EOF // EOF