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
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.
*/
#include "src/calc.h"
#include <tinyexpr.h>
#include <curveFitting.h>
#include "src/helper.h"
#include "src/config.h"
#include "src/tempsensor.h"
#define FORMULA_MAX_DEVIATION 1.5
//
// Use values to derive a formula
//
int createFormula(RawFormulaData& fd, char *formulaBuffer, 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 )
noAngles = 5;
else if ( fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0 )
noAngles = 4;
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);
#endif
if ( !noAngles ) {
Log.error(F("CALC: Not enough values for deriving formula" CR));
return ERR_FORMULA_NOTENOUGHVALUES;
} else {
double coeffs[order+1];
int ret = fitCurve(order, noAngles, fd.a, fd.g, sizeof(coeffs)/sizeof(double), coeffs);
// Returned value is 0 if no error
if ( ret == 0 ) {
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Finshied processing data points." CR));
#endif
// Print the formula based on 'order'
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]);
} else if ( order == 3 ) {
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1], coeffs[2], coeffs[3]);
} else if ( order == 2 ) {
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1], coeffs[2]);
} else { // order == 1
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt+%.8f", coeffs[0], coeffs[1]);
}
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer );
#endif
bool valid = true;
for ( int i = 0; i < 5; i++ ) {
if ( fd.a[i] == 0 && valid )
break;
double g = calculateGravity(fd.a[i], 0, formulaBuffer);
double dev = (g-fd.g[i]) < 0 ? (fd.g[i]-g) : (g-fd.g[i]);
// If the deviation is more than 2 degress we mark it as failed.
if ( dev*1000 > FORMULA_MAX_DEVIATION )
valid = false;
}
if ( !valid ) {
Log.error(F("CALC: Deviation to large, formula rejected." CR));
return ERR_FORMULA_UNABLETOFFIND;
}
Log.info(F("CALC: Found formula '%s'." CR), formulaBuffer);
return 0;
}
}
Log.error(F("CALC: Internal error finding formula." CR));
return ERR_FORMULA_INTERNAL;
}
//
// Calculates gravity according to supplied formula, compatible with iSpindle/Fermentrack formula
//
double calculateGravity(double angle, double temp, const char *tempFormula) {
const char* formula = myConfig.getGravityFormula();
if ( tempFormula != 0 ) {
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Using temporary formula." CR));
#endif
formula = tempFormula;
}
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle, temp);
Log.verbose(F("CALC: Formula %s." CR), formula);
#endif
if ( strlen(formula) == 0 )
return 0.0;
// Store variable names and pointers.
te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 2, &err);
if ( expr ) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Calculated gravity is %F." CR), g);
#endif
return g;
}
Log.error(F("CALC: Failed to parse expression %d." CR), err);
return 0;
}
//
// 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) {
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, temp %F, calTemp %F." CR), gravity, temp, calTemp);
#endif
if ( tempFormat == 'C')
temp = convertCtoF(temp);
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))";
// Store variable names and pointers.
te_variable vars[] = {{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 3, &err);
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
/*
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.
*/
#include <config.h>
#include <curveFitting.h>
#include <tinyexpr.h>
#include <calc.hpp>
#include <helper.hpp>
#include <tempsensor.hpp>
#define FORMULA_MAX_DEVIATION 1.5
//
// Use values to derive a formula
//
int createFormula(RawFormulaData &fd, char *formulaBuffer,
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)
noAngles = 5;
else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0)
noAngles = 4;
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);
#endif
if (!noAngles) {
Log.error(F("CALC: Not enough values for deriving formula" CR));
return ERR_FORMULA_NOTENOUGHVALUES;
} else {
double coeffs[order + 1];
int ret = fitCurve(order, noAngles, fd.a, fd.g,
sizeof(coeffs) / sizeof(double), coeffs);
// Returned value is 0 if no error
if (ret == 0) {
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Finshied processing data points." CR));
#endif
// Print the formula based on 'order'
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]);
} else if (order == 3) {
snprintf(formulaBuffer, formulaBufferSize,
"%.8f*tilt^3+%.8f*tilt^2+%.8f*tilt+%.8f", coeffs[0], coeffs[1],
coeffs[2], coeffs[3]);
} else if (order == 2) {
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt^2+%.8f*tilt+%.8f",
coeffs[0], coeffs[1], coeffs[2]);
} else { // order == 1
snprintf(formulaBuffer, formulaBufferSize, "%.8f*tilt+%.8f", coeffs[0],
coeffs[1]);
}
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Formula: %s" CR), formulaBuffer);
#endif
bool valid = true;
for (int i = 0; i < 5; i++) {
if (fd.a[i] == 0 && valid) break;
double g = calculateGravity(fd.a[i], 0, formulaBuffer);
double dev = (g - fd.g[i]) < 0 ? (fd.g[i] - g) : (g - fd.g[i]);
// If the deviation is more than 2 degress we mark it as failed.
if (dev * 1000 > FORMULA_MAX_DEVIATION) valid = false;
}
if (!valid) {
Log.error(F("CALC: Deviation to large, formula rejected." CR));
return ERR_FORMULA_UNABLETOFFIND;
}
Log.info(F("CALC: Found formula '%s'." CR), formulaBuffer);
return 0;
}
}
Log.error(F("CALC: Internal error finding formula." CR));
return ERR_FORMULA_INTERNAL;
}
//
// Calculates gravity according to supplied formula, compatible with
// iSpindle/Fermentrack formula
//
double calculateGravity(double angle, double temp, const char *tempFormula) {
const char *formula = myConfig.getGravityFormula();
if (tempFormula != 0) {
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Using temporary formula." CR));
#endif
formula = tempFormula;
}
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Calculating gravity for angle %F, temp %F." CR), angle,
temp);
Log.verbose(F("CALC: Formula %s." CR), formula);
#endif
if (strlen(formula) == 0) return 0.0;
// Store variable names and pointers.
te_variable vars[] = {{"tilt", &angle}, {"temp", &temp}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 2, &err);
if (expr) {
double g = te_eval(expr);
te_free(expr);
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Calculated gravity is %F." CR), g);
#endif
return g;
}
Log.error(F("CALC: Failed to parse expression %d." CR), err);
return 0;
}
//
// 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) {
#if LOG_LEVEL == 6
Log.verbose(F("CALC: Adjusting gravity based on temperature, gravity %F, "
"temp %F, calTemp %F." CR),
gravity, temp, calTemp);
#endif
if (tempFormat == 'C') temp = convertCtoF(temp);
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))";
// Store variable names and pointers.
te_variable vars[] = {
{"gravity", &gravity}, {"temp", &temp}, {"cal", &calTempF}};
int err;
// Compile the expression with variables.
te_expr *expr = te_compile(formula, vars, 3, &err);
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
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_CALC_H_
#define SRC_CALC_H_
// Includes
#include "src/helper.h"
#include "src/config.h"
#define ERR_FORMULA_NOTENOUGHVALUES -1
#define ERR_FORMULA_INTERNAL -2
#define ERR_FORMULA_UNABLETOFFIND -3
// Functions
double calculateGravity(double angle, double temp, const char *tempFormula = 0);
double gravityTemperatureCorrection(double gravity, double temp, char tempFormat, double calTemp = 20);
int createFormula(RawFormulaData& fd, char *formulaBuffer, int formulaBufferSize, int order);
#endif // SRC_CALC_H_
// EOF
/*
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_CALC_HPP_
#define SRC_CALC_HPP_
// Includes
#include <config.hpp>
#include <helper.hpp>
#define ERR_FORMULA_NOTENOUGHVALUES -1
#define ERR_FORMULA_INTERNAL -2
#define ERR_FORMULA_UNABLETOFFIND -3
// Functions
double calculateGravity(double angle, double temp, const char *tempFormula = 0);
double gravityTemperatureCorrection(double gravity, double temp,
char tempFormat, double calTemp = 20);
int createFormula(RawFormulaData &fd, char *formulaBuffer,
int formulaBufferSize, int order);
#endif // SRC_CALC_HPP_
// EOF

View File

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

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
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.
*/
#include "gyro.h"
#include "helper.h"
GyroSensor myGyro;
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
#define SENSOR_MOVING_THREASHOLD 500
#define SENSOR_READ_COUNT 50
#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_CALIBRATE_STARTUP // Will calibrate sensor at startup
//
// Initialize the sensor chip.
//
bool GyroSensor::setup() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
Wire.begin(D3, D4);
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
accelgyro.initialize();
if( !accelgyro.testConnection() ) {
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
sensorConnected = false;
} else {
#if !defined( GYRO_DISABLE_LOGGING )
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
#endif
sensorConnected = true;
// Configure the sensor
accelgyro.setTempSensorEnabled(true);
//accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); // Set in .initalize()
//accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250); // Set in .initalize()
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
#if defined( GYRO_USE_INTERRUPT )
// Alternative method to read data, let the MPU signal when sampling is done.
accelgyro.setRate(17);
accelgyro.setInterruptDrive(1);
accelgyro.setInterruptMode(1);
accelgyro.setInterruptLatch(0);
accelgyro.setIntDataReadyEnabled(true);
#endif
#if defined ( GYRO_CALIBRATE_STARTUP )
// Run the calibration at start, useful for testing.
calibrateSensor();
#endif
// Once we have calibration values stored we just apply them from the config.
calibrationOffset = myConfig.getGyroCalibration();
applyCalibration();
}
return sensorConnected;
}
//
// Set sensor in sleep mode to conserve battery
//
void GyroSensor::enterSleep() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
accelgyro.setSleepEnabled( true );
}
//
// Do a number of reads to get a more stable value.
//
void GyroSensor::readSensor(RawGyroData &raw, const int noIterations, const int delayTime) {
RawGyroDataL average = { 0, 0, 0, 0, 0, 0 };
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR), noIterations, delayTime );
#endif
// Set some initial values
#if defined( GYRO_SHOW_MINMAX )
RawGyroData min, max;
//accelgyro.getRotation( &min.gx, &min.gy, &min.gz );
accelgyro.getAcceleration( &min.ax, &min.ay, &min.az );
min.temp = accelgyro.getTemperature();
max = min;
#endif
for(int cnt = 0; cnt < noIterations ; cnt ++) {
#if defined( GYRO_USE_INTERRUPT )
while( accelgyro.getIntDataReadyStatus() == 0) {
delayMicroseconds( 1 );
}
#endif
//accelgyro.getRotation( &raw.gx, &raw.gy, &raw.gz );
//accelgyro.getAcceleration( &raw.ax, &raw.ay, &raw.az );
accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz);
raw.temp = accelgyro.getTemperature();
average.ax += raw.ax;
average.ay += raw.ay;
average.az += raw.az;
average.gx += raw.gx;
average.gy += raw.gy;
average.gz += raw.gz;
average.temp += raw.temp;
// Log what the minium value is
#if defined( GYRO_SHOW_MINMAX )
if( raw.ax < min.ax ) min.ax = raw.ax;
if( raw.ay < min.ay ) min.ay = raw.ay;
if( raw.az < min.az ) min.az = raw.az;
if( raw.gx < min.gx ) min.gx = raw.gx;
if( raw.gy < min.gy ) min.gy = raw.gy;
if( raw.gz < min.gz ) min.gz = raw.gz;
if( raw.temp < min.temp ) min.temp = raw.temp;
// Log what the maximum value is
if( raw.ax > max.ax ) max.ax = raw.ax;
if( raw.ay > max.ay ) max.ay = raw.ay;
if( raw.az > max.az ) max.az = raw.az;
if( raw.gx > max.gx ) max.gx = raw.gx;
if( raw.gy > max.gy ) max.gy = raw.gy;
if( raw.gz > max.gz ) max.gz = raw.gz;
if( raw.temp > max.temp ) max.temp = raw.temp;
#endif
#if !defined( GYRO_USE_INTERRUPT )
delayMicroseconds( delayTime );
#endif
}
raw.ax = average.ax/noIterations;
raw.ay = average.ay/noIterations;
raw.az = average.az/noIterations;
raw.gx = average.gx/noIterations;
raw.gy = average.gy/noIterations;
raw.gz = average.gz/noIterations;
raw.temp = average.temp/noIterations;
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
#if defined( GYRO_SHOW_MINMAX )
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 );
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
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 );
//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,
// average.gx/noIterations, average.gy/noIterations, average.gz/noIterations );
#endif
}
//
// Calcuate the angles (tilt)
//
float GyroSensor::calculateAngle(RawGyroData &raw) {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Calculating the angle." CR) );
#endif
// Smooth out the readings to we can have a more stable angle/tilt.
// ------------------------------------------------------------------------------------------------------------
// 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,
az = ((float) raw.az)/16384;
// 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);
//Log.notice(F("GYRO: angle = %F." CR), v );
//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 );
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: angle = %F." CR), v );
#endif
return v;
}
//
// Check if the values are high that indicate that the sensor is moving.
//
bool GyroSensor::isSensorMoving(RawGyroData &raw) {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Checking for sensor movement." CR) );
#endif
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
if( x>SENSOR_MOVING_THREASHOLD || y>SENSOR_MOVING_THREASHOLD || z>SENSOR_MOVING_THREASHOLD ) {
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), SENSOR_MOVING_THREASHOLD, x, y, z);
return true;
}
return false;
}
//
// Read the tilt angle from the gyro.
//
bool GyroSensor::read() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Getting new gyro position." CR) );
#endif
if( !sensorConnected )
return false;
readSensor( lastGyroData, SENSOR_READ_COUNT, SENSOR_READ_DELAY ); // Last param is unused if GYRO_USE_INTERRUPT is defined.
// 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) );
#endif
validValue = false;
} else {
validValue = true;
angle = calculateAngle( lastGyroData );
#if !defined( GYRO_DISABLE_LOGGING )
Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), lastGyroData.ax, lastGyroData.ay, lastGyroData.az, angle );
#endif
}
sensorTemp = ((float) lastGyroData.temp) / 340 + 36.53;
// The first read value is close to the DS18 value according to my tests, if more reads are
// done then the gyro temp will increase to much
if( initialSensorTemp == INVALID_TEMPERATURE )
initialSensorTemp = sensorTemp;
return validValue;
}
//
// Dump the stored calibration values.
//
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, calibrationOffset.ay, calibrationOffset.az );
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx, calibrationOffset.gy, calibrationOffset.gz );
#endif
}
//
// Update the sensor with out calculated offsets.
//
void GyroSensor::applyCalibration() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
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 ) {
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR) );
return;
}
accelgyro.setXAccelOffset( calibrationOffset.ax );
accelgyro.setYAccelOffset( calibrationOffset.ay );
accelgyro.setZAccelOffset( calibrationOffset.az );
accelgyro.setXGyroOffset( calibrationOffset.gx );
accelgyro.setYGyroOffset( calibrationOffset.gy );
accelgyro.setZGyroOffset( calibrationOffset.gz );
}
//
// Calculate the offsets for calibration.
//
void GyroSensor::calibrateSensor() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Calibrating sensor" CR) );
#endif
//accelgyro.PrintActiveOffsets();
//Serial.print( CR );
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
accelgyro.CalibrateAccel(6); // 6 = 600 readings
accelgyro.CalibrateGyro(6);
accelgyro.PrintActiveOffsets();
Serial.print( CR );
calibrationOffset.ax = accelgyro.getXAccelOffset();
calibrationOffset.ay = accelgyro.getYAccelOffset();
calibrationOffset.az = accelgyro.getZAccelOffset();
calibrationOffset.gx = accelgyro.getXGyroOffset();
calibrationOffset.gy = accelgyro.getYGyroOffset();
calibrationOffset.gz = accelgyro.getZGyroOffset();
// Save the calibrated values
myConfig.setGyroCalibration( calibrationOffset );
myConfig.saveFile();
}
//
// Calibrate the device.
//
void GyroSensor::debug() {
#if LOG_LEVEL==6 && !defined( GYRO_DISABLE_LOGGING )
Log.verbose(F("GYRO: Debug - Clock src %d." CR), accelgyro.getClockSource() );
Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID() );
Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode() );
Log.verbose(F("GYRO: Debug - DMP on %s." CR), accelgyro.getDMPEnabled()?"on":"off" );
Log.verbose(F("GYRO: Debug - Acc range %d." CR), accelgyro.getFullScaleAccelRange() );
Log.verbose(F("GYRO: Debug - Gyr range %d." CR), accelgyro.getFullScaleGyroRange() );
Log.verbose(F("GYRO: Debug - Int %s." CR), accelgyro.getIntEnabled()?"on":"off" );
Log.verbose(F("GYRO: Debug - Clock %d." CR), accelgyro.getMasterClockSpeed() );
Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate() );
Log.verbose(F("GYRO: Debug - Gyro range %d." CR), accelgyro.getFullScaleGyroRange() );
// 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" );
Log.verbose(F("GYRO: Debug - Acc FactX %d." CR), accelgyro.getAccelXSelfTestFactoryTrim() );
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
/*
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.
*/
#include <gyro.hpp>
#include <helper.hpp>
GyroSensor myGyro;
#define GYRO_USE_INTERRUPT // Use interrupt to detect when new sample is ready
#define SENSOR_MOVING_THREASHOLD 500
#define SENSOR_READ_COUNT 50
#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_CALIBRATE_STARTUP // Will calibrate sensor at startup
//
// Initialize the sensor chip.
//
bool GyroSensor::setup() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
Wire.begin(D3, D4);
Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having
// compilation difficulties
accelgyro.initialize();
if (!accelgyro.testConnection()) {
Log.error(F("GYRO: Failed to connect to MPU6050 (gyro)." CR));
sensorConnected = false;
} else {
#if !defined(GYRO_DISABLE_LOGGING)
Log.notice(F("GYRO: Connected to MPU6050 (gyro)." CR));
#endif
sensorConnected = true;
// Configure the sensor
accelgyro.setTempSensorEnabled(true);
// accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); // Set in
// .initalize() accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250); //
// Set in .initalize()
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
#if defined(GYRO_USE_INTERRUPT)
// Alternative method to read data, let the MPU signal when sampling is
// done.
accelgyro.setRate(17);
accelgyro.setInterruptDrive(1);
accelgyro.setInterruptMode(1);
accelgyro.setInterruptLatch(0);
accelgyro.setIntDataReadyEnabled(true);
#endif
#if defined(GYRO_CALIBRATE_STARTUP)
// Run the calibration at start, useful for testing.
calibrateSensor();
#endif
// Once we have calibration values stored we just apply them from the
// config.
calibrationOffset = myConfig.getGyroCalibration();
applyCalibration();
}
return sensorConnected;
}
//
// Set sensor in sleep mode to conserve battery
//
void GyroSensor::enterSleep() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Setting up hardware." CR));
#endif
accelgyro.setSleepEnabled(true);
}
//
// Do a number of reads to get a more stable value.
//
void GyroSensor::readSensor(RawGyroData &raw, const int noIterations,
const int delayTime) {
RawGyroDataL average = {0, 0, 0, 0, 0, 0};
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Reading sensor with %d iterations %d us delay." CR),
noIterations, delayTime);
#endif
// Set some initial values
#if defined(GYRO_SHOW_MINMAX)
RawGyroData min, max;
// accelgyro.getRotation( &min.gx, &min.gy, &min.gz );
accelgyro.getAcceleration(&min.ax, &min.ay, &min.az);
min.temp = accelgyro.getTemperature();
max = min;
#endif
for (int cnt = 0; cnt < noIterations; cnt++) {
#if defined(GYRO_USE_INTERRUPT)
while (accelgyro.getIntDataReadyStatus() == 0) {
delayMicroseconds(1);
}
#endif
// accelgyro.getRotation( &raw.gx, &raw.gy, &raw.gz );
// accelgyro.getAcceleration( &raw.ax, &raw.ay, &raw.az );
accelgyro.getMotion6(&raw.ax, &raw.ay, &raw.az, &raw.gx, &raw.gy, &raw.gz);
raw.temp = accelgyro.getTemperature();
average.ax += raw.ax;
average.ay += raw.ay;
average.az += raw.az;
average.gx += raw.gx;
average.gy += raw.gy;
average.gz += raw.gz;
average.temp += raw.temp;
// Log what the minium value is
#if defined(GYRO_SHOW_MINMAX)
if (raw.ax < min.ax) min.ax = raw.ax;
if (raw.ay < min.ay) min.ay = raw.ay;
if (raw.az < min.az) min.az = raw.az;
if (raw.gx < min.gx) min.gx = raw.gx;
if (raw.gy < min.gy) min.gy = raw.gy;
if (raw.gz < min.gz) min.gz = raw.gz;
if (raw.temp < min.temp) min.temp = raw.temp;
// Log what the maximum value is
if (raw.ax > max.ax) max.ax = raw.ax;
if (raw.ay > max.ay) max.ay = raw.ay;
if (raw.az > max.az) max.az = raw.az;
if (raw.gx > max.gx) max.gx = raw.gx;
if (raw.gy > max.gy) max.gy = raw.gy;
if (raw.gz > max.gz) max.gz = raw.gz;
if (raw.temp > max.temp) max.temp = raw.temp;
#endif
#if !defined(GYRO_USE_INTERRUPT)
delayMicroseconds(delayTime);
#endif
}
raw.ax = average.ax / noIterations;
raw.ay = average.ay / noIterations;
raw.az = average.az / noIterations;
raw.gx = average.gx / noIterations;
raw.gy = average.gy / noIterations;
raw.gz = average.gz / noIterations;
raw.temp = average.temp / noIterations;
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
#if defined(GYRO_SHOW_MINMAX)
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);
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
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);
// 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,
// average.gx/noIterations,
// average.gy/noIterations,
// average.gz/noIterations
// );
#endif
}
//
// Calcuate the angles (tilt)
//
float GyroSensor::calculateAngle(RawGyroData &raw) {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Calculating the angle." CR));
#endif
// Smooth out the readings to we can have a more stable angle/tilt.
// ------------------------------------------------------------------------------------------------------------
// 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 = (static_cast<float>(raw.ax)) / 16384,
ay = (static_cast<float>(raw.ay)) / 16384,
az = (static_cast<float>(raw.az)) / 16384;
// 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);
// Log.notice(F("GYRO: angle = %F." CR), v );
// 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 );
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: angle = %F." CR), v);
#endif
return v;
}
//
// Check if the values are high that indicate that the sensor is moving.
//
bool GyroSensor::isSensorMoving(RawGyroData &raw) {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Checking for sensor movement." CR));
#endif
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
if (x > SENSOR_MOVING_THREASHOLD || y > SENSOR_MOVING_THREASHOLD ||
z > SENSOR_MOVING_THREASHOLD) {
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR),
SENSOR_MOVING_THREASHOLD, x, y, z);
return true;
}
return false;
}
//
// Read the tilt angle from the gyro.
//
bool GyroSensor::read() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Getting new gyro position." CR));
#endif
if (!sensorConnected) return false;
readSensor(lastGyroData, SENSOR_READ_COUNT,
SENSOR_READ_DELAY); // Last param is unused if GYRO_USE_INTERRUPT
// is defined.
// 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));
#endif
validValue = false;
} else {
validValue = true;
angle = calculateAngle(lastGyroData);
#if !defined(GYRO_DISABLE_LOGGING)
Log.notice(F("GYRO: Sensor values %d,%d,%d\t%F" CR), lastGyroData.ax,
lastGyroData.ay, lastGyroData.az, angle);
#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
// more reads are done then the gyro temp will increase to much
if (initialSensorTemp == INVALID_TEMPERATURE) initialSensorTemp = sensorTemp;
return validValue;
}
//
// Dump the stored calibration values.
//
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,
calibrationOffset.ay, calibrationOffset.az);
Log.verbose(F("GYRO: Gyro offset \t%d\t%d\t%d" CR), calibrationOffset.gx,
calibrationOffset.gy, calibrationOffset.gz);
#endif
}
//
// Update the sensor with out calculated offsets.
//
void GyroSensor::applyCalibration() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
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) {
Log.error(F("GYRO: No valid calibraion values exist, aborting." CR));
return;
}
accelgyro.setXAccelOffset(calibrationOffset.ax);
accelgyro.setYAccelOffset(calibrationOffset.ay);
accelgyro.setZAccelOffset(calibrationOffset.az);
accelgyro.setXGyroOffset(calibrationOffset.gx);
accelgyro.setYGyroOffset(calibrationOffset.gy);
accelgyro.setZGyroOffset(calibrationOffset.gz);
}
//
// Calculate the offsets for calibration.
//
void GyroSensor::calibrateSensor() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Calibrating sensor" CR));
#endif
// accelgyro.PrintActiveOffsets();
// Serial.print( CR );
accelgyro.setDLPFMode(MPU6050_DLPF_BW_5);
accelgyro.CalibrateAccel(6); // 6 = 600 readings
accelgyro.CalibrateGyro(6);
accelgyro.PrintActiveOffsets();
Serial.print(CR);
calibrationOffset.ax = accelgyro.getXAccelOffset();
calibrationOffset.ay = accelgyro.getYAccelOffset();
calibrationOffset.az = accelgyro.getZAccelOffset();
calibrationOffset.gx = accelgyro.getXGyroOffset();
calibrationOffset.gy = accelgyro.getYGyroOffset();
calibrationOffset.gz = accelgyro.getZGyroOffset();
// Save the calibrated values
myConfig.setGyroCalibration(calibrationOffset);
myConfig.saveFile();
}
//
// Calibrate the device.
//
void GyroSensor::debug() {
#if LOG_LEVEL == 6 && !defined(GYRO_DISABLE_LOGGING)
Log.verbose(F("GYRO: Debug - Clock src %d." CR),
accelgyro.getClockSource());
Log.verbose(F("GYRO: Debug - Device ID %d." CR), accelgyro.getDeviceID());
Log.verbose(F("GYRO: Debug - DHPF Mode %d." CR), accelgyro.getDHPFMode());
Log.verbose(F("GYRO: Debug - DMP on %s." CR),
accelgyro.getDMPEnabled() ? "on" : "off");
Log.verbose(F("GYRO: Debug - Acc range %d." CR),
accelgyro.getFullScaleAccelRange());
Log.verbose(F("GYRO: Debug - Gyr range %d." CR),
accelgyro.getFullScaleGyroRange());
Log.verbose(F("GYRO: Debug - Int %s." CR),
accelgyro.getIntEnabled() ? "on" : "off");
Log.verbose(F("GYRO: Debug - Clock %d." CR),
accelgyro.getMasterClockSpeed());
Log.verbose(F("GYRO: Debug - Rate %d." CR), accelgyro.getRate());
Log.verbose(F("GYRO: Debug - Gyro range %d." CR),
accelgyro.getFullScaleGyroRange());
// 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" );
Log.verbose(F("GYRO: Debug - Acc FactX %d." CR),
accelgyro.getAccelXSelfTestFactoryTrim());
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
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.
*/
#include "helper.h"
#include "config.h"
#include "gyro.h"
#include "tempsensor.h"
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
SerialDebug mySerial;
BatteryVoltage myBatteryVoltage;
//
// Print the heap information.
//
void printHeap() {
#if LOG_LEVEL==6
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
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 "
#ifdef SKIP_SLEEPMODE
"SKIP_SLEEP "
#endif
#ifdef EMBED_HTML
"EMBED_HTML "
#endif
#ifdef COLLECT_PERFDATA
"PERFDATA "
#endif
#ifdef ACTIVATE_OTA
"OTA "
#endif
CR), CFG_APPVER, LOG_LEVEL );
}
//
// Configure serial debug output
//
SerialDebug::SerialDebug(const long serialSpeed) {
// Start serial with auto-detected rate (default to defined BAUD)
Serial.flush();
Serial.begin(serialSpeed);
getLog()->begin(LOG_LEVEL, &Serial, true);
getLog()->setPrefix(printTimestamp);
getLog()->notice(F("SDBG: Serial logging started at %l." CR), serialSpeed);
}
//
// Print the timestamp (ms since start of device)
//
void printTimestamp(Print* _logOutput, int _logLevel) {
char c[12];
sprintf(c, "%10lu ", millis());
_logOutput->print(c);
}
//
// Read and calculate the battery voltage
//
void BatteryVoltage::read() {
// The analog pin can only handle 3.3V maximum voltage so we need to reduce the voltage (from max 5V)
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
int v = analogRead( A0 );
batteryLevel = ((3.3/1023)*v)*factor;
#if LOG_LEVEL==6
Log.verbose(F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR), factor, v, batteryLevel );
#endif
}
#if defined( COLLECT_PERFDATA )
PerfLogging myPerfLogging;
//
// Clear the current cache
//
void PerfLogging::clear() {
// Clear the measurements
if( first == 0 )
return;
PerfEntry* pe = first;
do {
pe->max = 0;
pe->start = 0;
pe->end = 0;
pe->mA = 0;
pe->V = 0;
pe = pe->next;
} while( pe != 0 );
}
//
// Start measuring this performance point
//
void PerfLogging::start( const char* key ) {
PerfEntry* pe = add( key );
pe->start = millis();
}
//
// Finalize measuring of this performance point
//
void PerfLogging::stop( const char* key ) {
PerfEntry* pe = find( key );
if( pe != 0 ) {
pe->end = millis();
unsigned long t = pe->end - pe->start;
if( t > pe->max )
pe->max = t;
}
}
//
// Print the collected performance data
//
void PerfLogging::print() {
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 );
Log.notice( F("PERF: %s %lms" CR), pe->key, pe->max );
pe = pe->next;
}
}
//
// Push collected performance data to influx (use influx configuration)
//
void PerfLogging::pushInflux() {
if( !myConfig.isInfluxDb2Active() )
return;
WiFiClient client;
HTTPClient http;
String serverPath = String(myConfig.getInfluxDb2PushUrl()) + "/api/v2/write?org=" +
String(myConfig.getInfluxDb2PushOrg()) + "&bucket=" +
String(myConfig.getInfluxDb2PushBucket());
http.begin( client, serverPath);
// Create body for influxdb2, format used
// key,host=mdns value=0.0
String body;
// Create the payload with performance data.
// ------------------------------------------------------------------------------------------
PerfEntry* pe = first;
char buf[100];
sprintf( &buf[0], "perf,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() );
body += &buf[0];
while( pe != 0 ) {
if( pe->max ) {
if( pe->next )
sprintf( &buf[0], "%s=%ld,", pe->key, pe->max);
else
sprintf( &buf[0], "%s=%ld", pe->key, pe->max);
body += &buf[0];
}
pe = pe->next;
}
// Create the payload with debug data for validating sensor stability
// ------------------------------------------------------------------------------------------
sprintf( &buf[0], "\ndebug,host=%s,device=%s ", myConfig.getMDNS(), myConfig.getID() );
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,
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az, myGyro.getSensorTempC(), myTempSensor.getTempC() );
body += &buf[0];
// Log.notice(F("PERF: data %s." CR), body.c_str() );
#if LOG_LEVEL==6
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
Log.verbose(F("PERF: data %s." CR), body.c_str() );
#endif
// Send HTTP POST request
String auth = "Token " + String( myConfig.getInfluxDb2PushToken() );
http.addHeader(F("Authorization"), auth.c_str() );
int httpResponseCode = http.POST(body);
if (httpResponseCode==204) {
Log.notice(F("PERF: InfluxDB2 push performance data successful, response=%d" CR), httpResponseCode);
} else {
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR), httpResponseCode);
}
http.end();
}
#endif // COLLECT_PERFDATA
//
// Convert float to formatted string with n decimals. Buffer should be at least 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
/*
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.
*/
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <config.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <tempsensor.hpp>
SerialDebug mySerial;
BatteryVoltage myBatteryVoltage;
//
// Print the heap information.
//
void printHeap() {
#if LOG_LEVEL == 6
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
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 "
#ifdef SKIP_SLEEPMODE
"SKIP_SLEEP "
#endif
#ifdef EMBED_HTML
"EMBED_HTML "
#endif
#ifdef COLLECT_PERFDATA
"PERFDATA "
#endif
#ifdef ACTIVATE_OTA
"OTA "
#endif
CR),
CFG_APPVER, LOG_LEVEL);
}
//
// Configure serial debug output
//
SerialDebug::SerialDebug(const int32 serialSpeed) {
// Start serial with auto-detected rate (default to defined BAUD)
Serial.flush();
Serial.begin(serialSpeed);
getLog()->begin(LOG_LEVEL, &Serial, true);
getLog()->setPrefix(printTimestamp);
getLog()->notice(F("SDBG: Serial logging started at %l." CR), serialSpeed);
}
//
// Print the timestamp (ms since start of device)
//
void printTimestamp(Print* _logOutput, int _logLevel) {
char c[12];
snprintf(c, sizeof(c), "%10lu ", millis());
_logOutput->print(c);
}
//
// Read and calculate the battery voltage
//
void BatteryVoltage::read() {
// The analog pin can only handle 3.3V maximum voltage so we need to reduce
// the voltage (from max 5V)
float factor = myConfig.getVoltageFactor(); // Default value is 1.63
int v = analogRead(A0);
batteryLevel = ((3.3 / 1023) * v) * factor;
#if LOG_LEVEL == 6
Log.verbose(
F("BATT: Reading voltage level. Factor=%F Value=%d, Voltage=%F." CR),
factor, v, batteryLevel);
#endif
}
#if defined(COLLECT_PERFDATA)
PerfLogging myPerfLogging;
//
// Clear the current cache
//
void PerfLogging::clear() {
// Clear the measurements
if (first == 0) return;
PerfEntry* pe = first;
do {
pe->max = 0;
pe->start = 0;
pe->end = 0;
pe->mA = 0;
pe->V = 0;
pe = pe->next;
} while (pe != 0);
}
//
// Start measuring this performance point
//
void PerfLogging::start(const char* key) {
PerfEntry* pe = add(key);
pe->start = millis();
}
//
// Finalize measuring of this performance point
//
void PerfLogging::stop(const char* key) {
PerfEntry* pe = find(key);
if (pe != 0) {
pe->end = millis();
uint32_t t = pe->end - pe->start;
if (t > pe->max) pe->max = t;
}
}
//
// Print the collected performance data
//
void PerfLogging::print() {
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 );
Log.notice(F("PERF: %s %lms" CR), pe->key, pe->max);
pe = pe->next;
}
}
//
// Push collected performance data to influx (use influx configuration)
//
void PerfLogging::pushInflux() {
if (!myConfig.isInfluxDb2Active()) return;
WiFiClient client;
HTTPClient http;
String serverPath =
String(myConfig.getInfluxDb2PushUrl()) +
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
http.begin(client, serverPath);
// Create body for influxdb2, format used
// key,host=mdns value=0.0
String body;
// Create the payload with performance data.
// ------------------------------------------------------------------------------------------
PerfEntry* pe = first;
char buf[100];
snprintf(&buf[0], sizeof(buf), "perf,host=%s,device=%s ", myConfig.getMDNS(),
myConfig.getID());
body += &buf[0];
while (pe != 0) {
if (pe->max) {
if (pe->next)
snprintf(&buf[0], sizeof(buf), "%s=%u,", pe->key, pe->max);
else
snprintf(&buf[0], sizeof(buf), "%s=%u", pe->key, pe->max);
body += &buf[0];
}
pe = pe->next;
}
// Create the payload with debug data for validating sensor stability
// ------------------------------------------------------------------------------------------
snprintf(&buf[0], sizeof(buf), "\ndebug,host=%s,device=%s ",
myConfig.getMDNS(), myConfig.getID());
body += &buf[0];
snprintf(
&buf[0], sizeof(buf),
"angle=%.4f,gyro-ax=%d,gyro-ay=%d,gyro-az=%d,gyro-temp=%.2f,ds-temp=%.2f",
myGyro.getAngle(), myGyro.getLastGyroData().ax,
myGyro.getLastGyroData().ay, myGyro.getLastGyroData().az,
myGyro.getSensorTempC(), myTempSensor.getTempC());
body += &buf[0];
// Log.notice(F("PERF: data %s." CR), body.c_str() );
#if LOG_LEVEL == 6
Log.verbose(F("PERF: url %s." CR), serverPath.c_str());
Log.verbose(F("PERF: data %s." CR), body.c_str());
#endif
// Send HTTP POST request
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
http.addHeader(F("Authorization"), auth.c_str());
int httpResponseCode = http.POST(body);
if (httpResponseCode == 204) {
Log.notice(
F("PERF: InfluxDB2 push performance data successful, response=%d" CR),
httpResponseCode);
} else {
Log.error(F("PERF: InfluxDB2 push performance data failed, response=%d" CR),
httpResponseCode);
}
http.end();
}
#endif // COLLECT_PERFDATA
//
// Convert float to formatted string with n decimals. Buffer should be at least
// 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
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.
*/
#include "helper.h"
#include "gyro.h"
#include "config.h"
#include "wifi.h"
#include "webserver.h"
#include "calc.h"
#include "tempsensor.h"
#include "pushtarget.h"
#include <LittleFS.h>
// Settings for double reset detector.
#define ESP8266_DRD_USE_RTC true
#define DRD_TIMEOUT 2
#define DRD_ADDRESS 0
#include <ESP_DoubleResetDetector.h>
DoubleResetDetector *drd;
// Define constats for this program
#ifdef DEACTIVATE_SLEEPMODE
const int interval = 1000; // ms, time to wait between changes to output
bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour
#else
const int interval = 200; // ms, time to wait between changes to output
bool sleepModeAlwaysSkip = false; // Web interface can override normal behaviour
#endif
unsigned long loopMillis = 0; // Used for main loop to run the code every _interval_
unsigned long runtimeMillis; // Used to calculate the total time since start/wakeup
unsigned long stableGyroMillis; // Used to calculate the total time since last stable gyro reading
bool sleepModeActive = false;
bool goToSleep = false;
int loopCounter = 0;
//
// Check if we should be in sleep mode
//
void checkSleepMode( float angle, float volt ) {
#if defined( SKIP_SLEEPMODE )
sleepModeActive = false;
Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR) );
return;
#endif
const RawGyroData &g = myConfig.getGyroCalibration();
// Will not enter sleep mode if: no calibration data
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) );
sleepModeAlwaysSkip = true;
}
if( sleepModeAlwaysSkip ) {
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR) );
sleepModeActive = false;
return;
}
// Will not enter sleep mode if: charger is connected
sleepModeActive = (volt<4.15 && (angle>85 && angle<95)) || (volt>4.15) ? false : true;
// 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 );
}
//
// Setup
//
void setup() {
LOG_PERF_START("run-time");
LOG_PERF_START("main-setup");
runtimeMillis = millis();
drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
bool dt = drd->detectDoubleReset();
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING )
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str() );
#endif
// Main startup
Log.notice(F("Main: Started setup for %s." CR), String( ESP.getChipId(), HEX).c_str() );
printBuildOptions();
LOG_PERF_START("main-config-load");
myConfig.checkFileSystem();
myConfig.loadFile();
LOG_PERF_STOP("main-config-load");
// Setup watchdog
ESP.wdtDisable();
ESP.wdtEnable( interval*2 );
if( dt ) {
Log.notice(F("Main: Detected doubletap on reset. Reset reason=%s" CR), ESP.getResetReason().c_str());
}
#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
dt = false;
#endif
LOG_PERF_START("main-wifi-connect");
myWifi.connect( dt ); // This will return false if unable to connect to wifi, will be handled in loop()
LOG_PERF_STOP("main-wifi-connect");
LOG_PERF_START("main-temp-setup");
myTempSensor.setup();
LOG_PERF_STOP("main-temp-setup");
//LOG_PERF_START("main-gyro-setup"); // Takes less than 5ms, so skip this measurment
if( !myGyro.setup() )
Log.error(F("Main: Failed to initialize the gyro." CR));
//LOG_PERF_STOP("main-gyro-setup");
LOG_PERF_START("main-gyro-read");
myGyro.read();
LOG_PERF_STOP("main-gyro-read");
LOG_PERF_START("main-batt-read");
myBatteryVoltage.read();
LOG_PERF_STOP("main-batt-read");
checkSleepMode( myGyro.getAngle(), myBatteryVoltage.getVoltage() );
if( myWifi.isConnected() ) {
#if defined( ACTIVATE_OTA )
LOG_PERF_START("main-wifi-ota");
if( !sleepModeActive && myWifi.checkFirmwareVersion() ) {
myWifi.updateFirmware();
}
LOG_PERF_STOP("main-wifi-ota");
#endif
if( !sleepModeActive ) {
//LOG_PERF_START("main-webserver-setup"); // Takes less than 4ms , so skip this measurment
myWebServer.setupWebServer();
//LOG_PERF_STOP("main-webserver-setup");
}
}
LOG_PERF_STOP("main-setup");
Log.notice(F("Main: Setup completed." CR));
stableGyroMillis = millis(); // Put it here so we dont include time for wifi connection
}
//
// Main loops
//
void loop() {
drd->loop();
if( sleepModeActive || abs( (long) (millis() - loopMillis)) > interval ) {
float angle = 0;
float volt = myBatteryVoltage.getVoltage();
//float sensorTemp = 0;
loopCounter++;
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING )
Log.verbose(F("Main: Entering main loop." CR) );
#endif
// Process the sensor values and push data to targets.
// ------------------------------------------------------------------------------------------------
// If we dont get any readings we just skip this and try again the next interval.
//
if( myGyro.hasValue() ) {
angle = myGyro.getAngle(); // Gyro angle
stableGyroMillis = millis(); // Reset timer
LOG_PERF_START("loop-temp-read");
float temp = myTempSensor.getTempC();
LOG_PERF_STOP("loop-temp-read");
//LOG_PERF_START("loop-gravity-calc"); // Takes less than 2ms , so skip this measurment
float gravity = calculateGravity( angle, temp );
//LOG_PERF_STOP("loop-gravity-calc");
//LOG_PERF_START("loop-gravity-corr"); // Takes less than 2ms , so skip this measurment
// Use default correction temperature of 20C
float corrGravity = gravityTemperatureCorrection( gravity, temp, myConfig.getTempFormat() );
//LOG_PERF_STOP("loop-gravity-corr");
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING )
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%F, gravity=%F, corr=%F." CR), angle, temp, gravity, corrGravity );
#endif
// Limit the printout when sleep mode is not active.
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-push");
myPushTarget.send( angle, gravity, corrGravity, temp, (millis()-runtimeMillis)/1000, sleepModeActive ); // Force the transmission if we are going to sleep
LOG_PERF_STOP("loop-push");
// If we have completed the update lets go to sleep
if( sleepModeActive )
goToSleep = true;
} else {
Log.error(F("Main: No gyro value." CR) );
}
#if LOG_LEVEL==6 && !defined( MAIN_DISABLE_LOGGING )
Log.verbose(F("Main: Sleep mode not active." CR) );
#endif
int sleepInterval = myConfig.getSleepInterval();
// If we didnt get a wifi connection, we enter sleep for a short time to conserve battery.
// ------------------------------------------------------------------------------------------------
//
if( !myWifi.isConnected() ) { // no connection to wifi
Log.notice(F("MAIN: No connection to wifi established, sleeping for 60s." CR) );
sleepInterval = 60; // 60s
goToSleep = true;
}
// If the sensor is moving and we are not getting a clear reading, we enter sleep for a short time to conserve battery.
// ------------------------------------------------------------------------------------------------
//
if( sleepModeActive && ((millis()-stableGyroMillis)>10000L) ) { // 10s since last stable gyro reading
Log.notice(F("MAIN: Unable to get a stable reading for 10s, sleeping for 60s." CR) );
sleepInterval = 60; // 60s
goToSleep = true;
}
// Enter sleep mode if the conditions are right
// ------------------------------------------------------------------------------------------------
//
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 );
LittleFS.end();
myGyro.enterSleep();
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
/*
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.
*/
#include <LittleFS.h>
#include <calc.hpp>
#include <config.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <pushtarget.hpp>
#include <tempsensor.hpp>
#include <webserver.hpp>
#include <wifi.hpp>
// Settings for double reset detector.
#define ESP8266_DRD_USE_RTC true
#define DRD_TIMEOUT 2
#define DRD_ADDRESS 0
#include <ESP_DoubleResetDetector.h>
DoubleResetDetector *drd;
// Define constats for this program
#ifdef DEACTIVATE_SLEEPMODE
const int interval = 1000; // ms, time to wait between changes to output
bool sleepModeAlwaysSkip = true; // Web interface can override normal behaviour
#else
const int interval = 200; // ms, time to wait between changes to output
bool sleepModeAlwaysSkip =
false; // Web interface can override normal behaviour
#endif
uint32_t loopMillis = 0; // Used for main loop to run the code every _interval_
uint32_t runtimeMillis; // Used to calculate the total time since start/wakeup
uint32_t stableGyroMillis; // Used to calculate the total time since last
// stable gyro reading
bool sleepModeActive = false;
bool goToSleep = false;
int loopCounter = 0;
//
// Check if we should be in sleep mode
//
void checkSleepMode(float angle, float volt) {
#if defined(SKIP_SLEEPMODE)
sleepModeActive = false;
Log.verbose(F("MAIN: Skipping sleep mode (SKIP_SLEEPMODE is defined)." CR));
return;
#endif
const RawGyroData &g = myConfig.getGyroCalibration();
// Will not enter sleep mode if: no calibration data
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));
sleepModeAlwaysSkip = true;
}
if (sleepModeAlwaysSkip) {
Log.notice(F("MAIN: Sleep mode disabled from web interface." CR));
sleepModeActive = false;
return;
}
// Will not enter sleep mode if: charger is connected
sleepModeActive = (volt < 4.15 && (angle > 85 && angle < 95)) || (volt > 4.15)
? false
: true;
// 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);
}
//
// Setup
//
void setup() {
LOG_PERF_START("run-time");
LOG_PERF_START("main-setup");
runtimeMillis = millis();
drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
bool dt = drd->detectDoubleReset();
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Reset reason %s." CR), ESP.getResetInfo().c_str());
#endif
// Main startup
Log.notice(F("Main: Started setup for %s." CR),
String(ESP.getChipId(), HEX).c_str());
printBuildOptions();
LOG_PERF_START("main-config-load");
myConfig.checkFileSystem();
myConfig.loadFile();
LOG_PERF_STOP("main-config-load");
// Setup watchdog
ESP.wdtDisable();
ESP.wdtEnable(interval * 2);
if (dt) {
Log.notice(F("Main: Detected doubletap on reset. Reset reason=%s" CR),
ESP.getResetReason().c_str());
}
#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
dt = false;
#endif
LOG_PERF_START("main-wifi-connect");
myWifi.connect(dt); // This will return false if unable to connect to wifi,
// will be handled in loop()
LOG_PERF_STOP("main-wifi-connect");
LOG_PERF_START("main-temp-setup");
myTempSensor.setup();
LOG_PERF_STOP("main-temp-setup");
// LOG_PERF_START("main-gyro-setup"); // Takes less than 5ms, so skip this
// measurment
if (!myGyro.setup()) Log.error(F("Main: Failed to initialize the gyro." CR));
// LOG_PERF_STOP("main-gyro-setup");
LOG_PERF_START("main-gyro-read");
myGyro.read();
LOG_PERF_STOP("main-gyro-read");
LOG_PERF_START("main-batt-read");
myBatteryVoltage.read();
LOG_PERF_STOP("main-batt-read");
checkSleepMode(myGyro.getAngle(), myBatteryVoltage.getVoltage());
if (myWifi.isConnected()) {
#if defined(ACTIVATE_OTA)
LOG_PERF_START("main-wifi-ota");
if (!sleepModeActive && myWifi.checkFirmwareVersion()) {
myWifi.updateFirmware();
}
LOG_PERF_STOP("main-wifi-ota");
#endif
if (!sleepModeActive) {
// LOG_PERF_START("main-webserver-setup"); // Takes less than 4ms , so
// skip this measurment
myWebServer.setupWebServer();
// LOG_PERF_STOP("main-webserver-setup");
}
}
LOG_PERF_STOP("main-setup");
Log.notice(F("Main: Setup completed." CR));
stableGyroMillis =
millis(); // Put it here so we dont include time for wifi connection
}
//
// Main loops
//
void loop() {
drd->loop();
if (sleepModeActive || abs((int32_t)(millis() - loopMillis)) > interval) {
float angle = 0;
float volt = myBatteryVoltage.getVoltage();
// float sensorTemp = 0;
loopCounter++;
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Entering main loop." CR));
#endif
// Process the sensor values and push data to targets.
// ------------------------------------------------------------------------------------------------
// If we dont get any readings we just skip this and try again the next
// interval.
//
if (myGyro.hasValue()) {
angle = myGyro.getAngle(); // Gyro angle
stableGyroMillis = millis(); // Reset timer
LOG_PERF_START("loop-temp-read");
float temp = myTempSensor.getTempC();
LOG_PERF_STOP("loop-temp-read");
// LOG_PERF_START("loop-gravity-calc"); // Takes less than 2ms , so skip
// this measurment
float gravity = calculateGravity(angle, temp);
// LOG_PERF_STOP("loop-gravity-calc");
// LOG_PERF_START("loop-gravity-corr"); // Takes less than 2ms , so skip
// this measurment Use default correction temperature of 20C
float corrGravity =
gravityTemperatureCorrection(gravity, temp, myConfig.getTempFormat());
// LOG_PERF_STOP("loop-gravity-corr");
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Sensor values gyro angle=%F, temp=%F, gravity=%F, "
"corr=%F." CR),
angle, temp, gravity, corrGravity);
#endif
// Limit the printout when sleep mode is not active.
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-push");
myPushTarget.send(
angle, gravity, corrGravity, temp, (millis() - runtimeMillis) / 1000,
sleepModeActive); // Force the transmission if we are going to sleep
LOG_PERF_STOP("loop-push");
// If we have completed the update lets go to sleep
if (sleepModeActive) goToSleep = true;
} else {
Log.error(F("Main: No gyro value." CR));
}
#if LOG_LEVEL == 6 && !defined(MAIN_DISABLE_LOGGING)
Log.verbose(F("Main: Sleep mode not active." CR));
#endif
int sleepInterval = myConfig.getSleepInterval();
// If we didnt get a wifi connection, we enter sleep for a short time to
// conserve battery.
// ------------------------------------------------------------------------------------------------
//
if (!myWifi.isConnected()) { // no connection to wifi
Log.notice(
F("MAIN: No connection to wifi established, sleeping for 60s." CR));
sleepInterval = 60; // 60s
goToSleep = true;
}
// If the sensor is moving and we are not getting a clear reading, we enter
// sleep for a short time to conserve battery.
// ------------------------------------------------------------------------------------------------
//
if (sleepModeActive && ((millis() - stableGyroMillis) >
10000L)) { // 10s since last stable gyro reading
Log.notice(
F("MAIN: Unable to get a stable reading for 10s, sleeping for "
"60s." CR));
sleepInterval = 60; // 60s
goToSleep = true;
}
// Enter sleep mode if the conditions are right
// ------------------------------------------------------------------------------------------------
//
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);
LittleFS.end();
myGyro.enterSleep();
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
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.
*/
#include "pushtarget.h"
#include "config.h"
#include "gyro.h" // For testing the tempsensor in the gyro
PushTarget myPushTarget;
//
// 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) );
unsigned long interval = myConfig.getSleepInterval()*1000;
if( ( timePassed < interval ) && !force) {
#if LOG_LEVEL==6 && !defined( PUSH_DISABLE_LOGGING )
Log.verbose(F("PUSH: Timer has not expired %l vs %l." CR), timePassed, interval );
#endif
return;
}
#if LOG_LEVEL==6 && !defined( PUSH_DISABLE_LOGGING )
Log.verbose(F("PUSH: Sending data." CR) );
#endif
ms = millis();
if( myConfig.isBrewfatherActive() ) {
LOG_PERF_START("push-brewfather");
sendBrewfather( angle, gravity, corrGravity, temp );
LOG_PERF_STOP("push-brewfather");
}
if( myConfig.isHttpActive() ) {
LOG_PERF_START("push-http");
sendHttp( myConfig.getHttpPushUrl(), angle, gravity, corrGravity, temp, runTime );
LOG_PERF_STOP("push-http");
}
if( myConfig.isHttpActive2() ) {
LOG_PERF_START("push-http2");
sendHttp( myConfig.getHttpPushUrl2(), angle, gravity, corrGravity, temp, runTime );
LOG_PERF_STOP("push-http2");
}
if( myConfig.isInfluxDb2Active() ) {
LOG_PERF_START("push-influxdb2");
sendInfluxDb2( angle, gravity, corrGravity, temp, runTime );
LOG_PERF_STOP("push-influxdb2");
}
}
//
// Send to influx db v2
//
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
WiFiClient client;
HTTPClient http;
String serverPath = String(myConfig.getInfluxDb2PushUrl()) + "/api/v2/write?org=" +
String(myConfig.getInfluxDb2PushOrg()) + "&bucket=" +
String(myConfig.getInfluxDb2PushBucket());
http.begin( client, serverPath);
// Create body for influxdb2
char buf[1024];
sprintf( &buf[0], "measurement,host=%s,device=%s,temp-format=%c,gravity-format=%s "
"gravity=%.4f,corr-gravity=%.4f,angle=%.2f,temp=%.2f,battery=%.2f,rssi=%d,temp2=%.2f\n",
// TODO: Add support for plato format
myConfig.getMDNS(), myConfig.getID(), myConfig.getTempFormat(), "SG",
myConfig.isGravityTempAdj() ? corrGravity : gravity,
corrGravity, angle, temp, myBatteryVoltage.getVoltage(), WiFi.RSSI(), myGyro.getSensorTempC() ); // For comparing gyro tempsensor vs DSB1820
#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] );
#endif
// Send HTTP POST request
String auth = "Token " + String( myConfig.getInfluxDb2PushToken() );
http.addHeader(F("Authorization"), auth.c_str() );
int httpResponseCode = http.POST(&buf[0]);
if (httpResponseCode==204) {
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR), httpResponseCode);
} else {
Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR), httpResponseCode);
}
http.end();
}
//
// Send data to brewfather
//
void PushTarget::sendBrewfather(float angle, float gravity, float corrGravity, float temp ) {
#if !defined( PUSH_DISABLE_LOGGING )
Log.notice(F("PUSH: Sending values to brewfather angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp );
#endif
DynamicJsonDocument doc(300);
//
// {
// "name": "YourDeviceName", // Required field, this will be the ID in Brewfather
// "temp": 20.32,
// "aux_temp": 15.61, // Fridge Temp
// "ext_temp": 6.51, // Room Temp
// "temp_unit": "C", // C, F, K
// "gravity": 1.042,
// "gravity_unit": "G", // G, P
// "pressure": 10,
// "pressure_unit": "PSI", // PSI, BAR, KPA
// "ph": 4.12,
// "bpm": 123, // Bubbles Per Minute
// "comment": "Hello World",
// "beer": "Pale Ale"
// "battery": 4.98
// }
//
doc["name"] = myConfig.getMDNS();
doc["temp"] = reduceFloatPrecision( temp, 1);
doc["temp_unit"] = String( myConfig.getTempFormat() );
doc["battery"] = reduceFloatPrecision( myBatteryVoltage.getVoltage(), 2 );
// TODO: Add support for plato format
doc["gravity"] = reduceFloatPrecision( myConfig.isGravityTempAdj() ? corrGravity : gravity, 4 );
doc["gravity_unit"] = myConfig.isGravitySG()?"G":"P";
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getBrewfatherPushUrl();
// 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: Brewfather push successful, response=%d" CR), httpResponseCode);
} else {
Log.error(F("PUSH: Brewfather push failed, response=%d" CR), httpResponseCode);
}
http.end();
}
//
// Send data to http target
//
void PushTarget::sendHttp( String serverPath, float angle, float gravity, float corrGravity, float temp, float runTime ) {
#if !defined( PUSH_DISABLE_LOGGING )
Log.notice(F("PUSH: Sending values to http angle=%F, gravity=%F, temp=%F." CR), angle, gravity, temp );
#endif
DynamicJsonDocument doc(256);
// Use iSpindle format for compatibility
doc["name"] = myConfig.getMDNS();
doc["ID"] = myConfig.getID();
doc["token"] = "gravmon";
doc["interval"] = myConfig.getSleepInterval();
doc["temperature"] = reduceFloatPrecision( temp, 1 );
doc["temp-units"] = String( myConfig.getTempFormat() );
// TODO: Add support for plato format
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 );
doc["rssi"] = WiFi.RSSI();
// Some additional information
doc["gravity-units"] = "SG";
doc["run-time"] = reduceFloatPrecision( runTime, 2 );
WiFiClient client;
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
/*
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.
*/
#include <config.h>
#include <gyro.hpp>
#include <pushtarget.hpp>
PushTarget myPushTarget;
//
// Send the pressure value
//
void PushTarget::send(float angle, float gravity, float corrGravity, float temp,
float runTime, bool force) {
uint32_t timePassed = abs((int32_t)(millis() - ms));
uint32_t interval = myConfig.getSleepInterval() * 1000;
if ((timePassed < interval) && !force) {
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
Log.verbose(F("PUSH: Timer has not expired %l vs %l." CR), timePassed,
interval);
#endif
return;
}
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
Log.verbose(F("PUSH: Sending data." CR));
#endif
ms = millis();
if (myConfig.isBrewfatherActive()) {
LOG_PERF_START("push-brewfather");
sendBrewfather(angle, gravity, corrGravity, temp);
LOG_PERF_STOP("push-brewfather");
}
if (myConfig.isHttpActive()) {
LOG_PERF_START("push-http");
sendHttp(myConfig.getHttpPushUrl(), angle, gravity, corrGravity, temp,
runTime);
LOG_PERF_STOP("push-http");
}
if (myConfig.isHttpActive2()) {
LOG_PERF_START("push-http2");
sendHttp(myConfig.getHttpPushUrl2(), angle, gravity, corrGravity, temp,
runTime);
LOG_PERF_STOP("push-http2");
}
if (myConfig.isInfluxDb2Active()) {
LOG_PERF_START("push-influxdb2");
sendInfluxDb2(angle, gravity, corrGravity, temp, runTime);
LOG_PERF_STOP("push-influxdb2");
}
}
//
// Send to influx db v2
//
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
WiFiClient client;
HTTPClient http;
String serverPath =
String(myConfig.getInfluxDb2PushUrl()) +
"/api/v2/write?org=" + String(myConfig.getInfluxDb2PushOrg()) +
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
http.begin(client, serverPath);
// Create body for influxdb2
char buf[1024];
snprintf(
&buf[0], sizeof(buf),
"measurement,host=%s,device=%s,temp-format=%c,gravity-format=%s "
"gravity=%.4f,corr-gravity=%.4f,angle=%.2f,temp=%.2f,battery=%.2f,rssi=%"
"d,temp2=%.2f\n",
// TODO: Add support for plato format
myConfig.getMDNS(), myConfig.getID(), myConfig.getTempFormat(), "SG",
myConfig.isGravityTempAdj() ? corrGravity : gravity, corrGravity, angle,
temp, myBatteryVoltage.getVoltage(), WiFi.RSSI(),
myGyro.getSensorTempC()); // For comparing gyro tempsensor vs DSB1820
#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]);
#endif
// Send HTTP POST request
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
http.addHeader(F("Authorization"), auth.c_str());
int httpResponseCode = http.POST(&buf[0]);
if (httpResponseCode == 204) {
Log.notice(F("PUSH: InfluxDB2 push successful, response=%d" CR),
httpResponseCode);
} else {
Log.error(F("PUSH: InfluxDB2 push failed, response=%d" CR),
httpResponseCode);
}
http.end();
}
//
// Send data to brewfather
//
void PushTarget::sendBrewfather(float angle, float gravity, float corrGravity,
float temp) {
#if !defined(PUSH_DISABLE_LOGGING)
Log.notice(
F("PUSH: Sending values to brewfather angle=%F, gravity=%F, temp=%F." CR),
angle, gravity, temp);
#endif
DynamicJsonDocument doc(300);
//
// {
// "name": "YourDeviceName", // Required field, this will be the ID in
// Brewfather "temp": 20.32, "aux_temp": 15.61, // Fridge Temp
// "ext_temp": 6.51, // Room Temp
// "temp_unit": "C", // C, F, K
// "gravity": 1.042,
// "gravity_unit": "G", // G, P
// "pressure": 10,
// "pressure_unit": "PSI", // PSI, BAR, KPA
// "ph": 4.12,
// "bpm": 123, // Bubbles Per Minute
// "comment": "Hello World",
// "beer": "Pale Ale"
// "battery": 4.98
// }
//
doc["name"] = myConfig.getMDNS();
doc["temp"] = reduceFloatPrecision(temp, 1);
doc["temp_unit"] = String(myConfig.getTempFormat());
doc["battery"] = reduceFloatPrecision(myBatteryVoltage.getVoltage(), 2);
// TODO: Add support for plato format
doc["gravity"] = reduceFloatPrecision(
myConfig.isGravityTempAdj() ? corrGravity : gravity, 4);
doc["gravity_unit"] = myConfig.isGravitySG() ? "G" : "P";
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getBrewfatherPushUrl();
// 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: Brewfather push successful, response=%d" CR),
httpResponseCode);
} else {
Log.error(F("PUSH: Brewfather push failed, response=%d" CR),
httpResponseCode);
}
http.end();
}
//
// Send data to http target
//
void PushTarget::sendHttp(String serverPath, float angle, float gravity,
float corrGravity, float temp, float runTime) {
#if !defined(PUSH_DISABLE_LOGGING)
Log.notice(
F("PUSH: Sending values to http angle=%F, gravity=%F, temp=%F." CR),
angle, gravity, temp);
#endif
DynamicJsonDocument doc(256);
// Use iSpindle format for compatibility
doc["name"] = myConfig.getMDNS();
doc["ID"] = myConfig.getID();
doc["token"] = "gravmon";
doc["interval"] = myConfig.getSleepInterval();
doc["temperature"] = reduceFloatPrecision(temp, 1);
doc["temp-units"] = String(myConfig.getTempFormat());
// TODO: Add support for plato format
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);
doc["rssi"] = WiFi.RSSI();
// Some additional information
doc["gravity-units"] = "SG";
doc["run-time"] = reduceFloatPrecision(runTime, 2);
WiFiClient client;
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
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 _PUSHTARGET_H
#define _PUSHTARGET_H
// Includes
#include "helper.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
// Classes
class PushTarget {
private:
unsigned long ms; // Used to check that we do not post to often
void sendBrewfather(float angle, float gravity, float corrGravity, float temp );
void sendHttp(String serverPath, float angle, float gravity, float corrGravity, float temp, float runTime);
void sendInfluxDb2(float angle, float gravity, float corrGravity, float temp, float runTime);
public:
PushTarget() { ms = millis(); }
void send(float angle, float gravity, float corrGravity, float temp, float runTime, bool force = false );
};
extern PushTarget myPushTarget;
#endif // _PUSHTARGET_H
// EOF
/*
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_PUSHTARGET_HPP_
#define SRC_PUSHTARGET_HPP_
// Includes
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <helper.hpp>
// Classes
class PushTarget {
private:
uint32_t ms; // Used to check that we do not post to often
void sendBrewfather(float angle, float gravity, float corrGravity,
float temp);
void sendHttp(String serverPath, float angle, float gravity,
float corrGravity, float temp, float runTime);
void sendInfluxDb2(float angle, float gravity, float corrGravity, float temp,
float runTime);
public:
PushTarget() { ms = millis(); }
void send(float angle, float gravity, float corrGravity, float temp,
float runTime, bool force = false);
};
extern PushTarget myPushTarget;
#endif // SRC_PUSHTARGET_HPP_
// EOF

View File

@ -1,43 +1,43 @@
/*
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.
*/
#define INCBIN_OUTPUT_SECTION ".irom.text"
#include <incbin.h>
#if defined( EMBED_HTML )
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
INCBIN(IndexHtm, "data/index.min.htm" );
INCBIN(DeviceHtm, "data/device.min.htm" );
INCBIN(ConfigHtm, "data/config.min.htm" );
INCBIN(CalibrationHtm, "data/calibration.min.htm" );
INCBIN(AboutHtm, "data/about.min.htm" );
#else
// Minium web interface for uploading htm files
INCBIN(UploadHtm, "data/upload.min.htm" );
#endif
// EOF
/*
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.
*/
#define INCBIN_OUTPUT_SECTION ".irom.text"
#include <incbin.h>
#if defined(EMBED_HTML)
// Using minify to reduce memory usage. Reducing RAM memory usage with about 7%
INCBIN(IndexHtm, "data/index.min.htm");
INCBIN(DeviceHtm, "data/device.min.htm");
INCBIN(ConfigHtm, "data/config.min.htm");
INCBIN(CalibrationHtm, "data/calibration.min.htm");
INCBIN(AboutHtm, "data/about.min.htm");
#else
// Minium web interface for uploading htm files
INCBIN(UploadHtm, "data/upload.min.htm");
#endif
// EOF

View File

@ -1,135 +1,135 @@
/*
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.
*/
#include "tempsensor.h"
#include "helper.h"
#include "config.h"
#include "gyro.h"
#include <onewire.h>
#include <DallasTemperature.h>
#include <Wire.h>
//
// Conversion between C and F
//
float convertCtoF( float t ) {
return (t * 1.8 ) + 32.0;
}
#if !defined( USE_GYRO_TEMP )
OneWire myOneWire(D6);
DallasTemperature mySensors(&myOneWire);
#define TEMPERATURE_PRECISION 9
#endif
TempSensor myTempSensor;
//
// Setup temp sensors
//
void TempSensor::setup() {
#if defined( SIMULATE_TEMP )
hasSensors = true;
return;
#endif
#if defined( USE_GYRO_TEMP )
Log.notice(F("TSEN: Using temperature from gyro." CR));
#else
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING )
Log.verbose(F("TSEN: Looking for temp sensors." CR));
#endif
mySensors.begin();
if( mySensors.getDS18Count() ) {
#if !defined( TSEN_DISABLE_LOGGING )
Log.notice(F("TSEN: Found %d temperature sensor(s)." CR), mySensors.getDS18Count());
#endif
mySensors.setResolution(TEMPERATURE_PRECISION);
}
#endif
float t = myConfig.getTempSensorAdj();
// Set the temp sensor adjustment values
if( myConfig.isTempC() ) {
tempSensorAdjF = t * 1.8; // Convert the adjustment value to C
tempSensorAdjC = t;
} else {
tempSensorAdjF = t;
tempSensorAdjC = t * 0.556; // Convert the adjustent value to F
}
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING )
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C, %F F." CR), tempSensorAdjC, tempSensorAdjF );
#endif
}
//
// Retrieving value from sensor, value is in Celcius
//
float TempSensor::getValue() {
#if defined( SIMULATE_TEMP )
return 21;
#endif
#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.
//LOG_PERF_START("temp-get");
float c = myGyro.getInitialSensorTempC();
//LOG_PERF_STOP("temp-get");
hasSensor = true;
return c;
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING )
Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
#endif
#else
// If we dont have sensors just return 0
if( !mySensors.getDS18Count() ) {
Log.error(F("TSEN: No temperature sensors found. Skipping read." CR));
return -273;
}
// Read the sensors
//LOG_PERF_START("temp-request");
mySensors.requestTemperatures();
//LOG_PERF_STOP("temp-request");
float c = 0;
if( mySensors.getDS18Count() >= 1) {
//LOG_PERF_START("temp-get");
c = mySensors.getTempCByIndex(0);
//LOG_PERF_STOP("temp-get");
#if LOG_LEVEL==6 && !defined( TSEN_DISABLE_LOGGING )
Log.verbose(F("TSEN: Reciving temp value for sensor %F C." CR), c);
#endif
hasSensor = true;
}
return c;
#endif
}
// EOF
/*
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.
*/
#include <DallasTemperature.h>
#include <Wire.h>
#include <onewire.h>
#include <config.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <tempsensor.hpp>
//
// Conversion between C and F
//
float convertCtoF(float t) { return (t * 1.8) + 32.0; }
#if !defined(USE_GYRO_TEMP)
OneWire myOneWire(D6);
DallasTemperature mySensors(&myOneWire);
#define TEMPERATURE_PRECISION 9
#endif
TempSensor myTempSensor;
//
// Setup temp sensors
//
void TempSensor::setup() {
#if defined(SIMULATE_TEMP)
hasSensors = true;
return;
#endif
#if defined(USE_GYRO_TEMP)
Log.notice(F("TSEN: Using temperature from gyro." CR));
#else
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Looking for temp sensors." CR));
#endif
mySensors.begin();
if (mySensors.getDS18Count()) {
#if !defined(TSEN_DISABLE_LOGGING)
Log.notice(F("TSEN: Found %d temperature sensor(s)." CR),
mySensors.getDS18Count());
#endif
mySensors.setResolution(TEMPERATURE_PRECISION);
}
#endif
float t = myConfig.getTempSensorAdj();
// Set the temp sensor adjustment values
if (myConfig.isTempC()) {
tempSensorAdjF = t * 1.8; // Convert the adjustment value to C
tempSensorAdjC = t;
} else {
tempSensorAdjF = t;
tempSensorAdjC = t * 0.556; // Convert the adjustent value to F
}
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Adjustment values for temp sensor %F C, %F F." CR),
tempSensorAdjC, tempSensorAdjF);
#endif
}
//
// Retrieving value from sensor, value is in Celcius
//
float TempSensor::getValue() {
#if defined(SIMULATE_TEMP)
return 21;
#endif
#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. LOG_PERF_START("temp-get");
float c = myGyro.getInitialSensorTempC();
// LOG_PERF_STOP("temp-get");
hasSensor = true;
return c;
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Reciving temp value for gyro sensor %F C." CR), c);
#endif
#else
// If we dont have sensors just return 0
if (!mySensors.getDS18Count()) {
Log.error(F("TSEN: No temperature sensors found. Skipping read." CR));
return -273;
}
// Read the sensors
// LOG_PERF_START("temp-request");
mySensors.requestTemperatures();
// LOG_PERF_STOP("temp-request");
float c = 0;
if (mySensors.getDS18Count() >= 1) {
// LOG_PERF_START("temp-get");
c = mySensors.getTempCByIndex(0);
// LOG_PERF_STOP("temp-get");
#if LOG_LEVEL == 6 && !defined(TSEN_DISABLE_LOGGING)
Log.verbose(F("TSEN: Reciving temp value for sensor %F C." CR), c);
#endif
hasSensor = true;
}
return c;
#endif
}
// EOF

View File

@ -1,50 +1,50 @@
/*
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 _TEMPSENSOR_H
#define _TEMPSENSOR_H
// definitions
float convertCtoF( float t );
// classes
class TempSensor {
private:
bool hasSensor = false;
float tempSensorAdjF = 0;
float tempSensorAdjC = 0;
float getValue();
public:
void setup();
bool isSensorAttached() { return hasSensor; };
float getTempC() { return getValue() + tempSensorAdjC; }
float getTempF() { return convertCtoF(getValue()) + tempSensorAdjF; };
};
// Global instance created
extern TempSensor myTempSensor;
#endif // _TEMPSENSOR_H
// EOF
/*
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_TEMPSENSOR_HPP_
#define SRC_TEMPSENSOR_HPP_
// definitions
float convertCtoF(float t);
// classes
class TempSensor {
private:
bool hasSensor = false;
float tempSensorAdjF = 0;
float tempSensorAdjC = 0;
float getValue();
public:
void setup();
bool isSensorAttached() { return hasSensor; }
float getTempC() { return getValue() + tempSensorAdjC; }
float getTempF() { return convertCtoF(getValue()) + tempSensorAdjF; }
};
// Global instance created
extern TempSensor myTempSensor;
#endif // SRC_TEMPSENSOR_HPP_
// 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
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.
*/
#include "wifi.h"
#include "config.h"
#include "helper.h"
#include "gyro.h"
#include "calc.h"
#include "tempsensor.h"
#include <ArduinoJson.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#include <WiFiManager.h>
#include <LittleFS.h>
#include <incbin.h>
Wifi myWifi;
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.
//
bool Wifi::connect( bool showPortal ) {
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));
showPortal = true;
} 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), myConfig.getWifiSSID(), "*****" );
}
if( strlen(userSSID)==0 && showPortal ) {
#if LOG_LEVEL==6
Log.verbose(F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR), showPortal?"true":"false");
#endif
WiFiManager myWifiManager;
Log.notice(F("WIFI: Starting wifi portal." CR));
myWifiManager.setDebugOutput(true);
myWifiManager.setClass("invert");
myWifiManager.setConfigPortalTimeout( 120 ); // Keep it open for 120 seconds
bool f = myWifiManager.startConfigPortal( WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD );
if( f ) {
//Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR), myWifiManager.getWiFiSSID(), myWifiManager.getWiFiPass() );
Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR), myWifiManager.getWiFiSSID(), "*****" );
myConfig.setWifiSSID( myWifiManager.getWiFiSSID() );
myConfig.setWifiPass( myWifiManager.getWiFiPass() );
myConfig.saveFile();
} else {
Log.notice(F("WIFI: Failure from WIFI portal, rebooting." CR) );
delay(200);
ESP.reset();
}
}
// Connect to wifi
int i = 0;
//Log.notice(F("WIFI: Connecting to WIFI, mode=%d,persistent=%d,fhy=%d ." CR), WiFi.getMode(), WiFi.getPersistent(), WiFi.getPhyMode() );
WiFi.mode(WIFI_STA);
if( strlen(userSSID) ) {
Log.notice(F("WIFI: Connecting to wifi using hardcoded settings %s." CR), userSSID);
WiFi.begin( userSSID, userPWD );
} else {
Log.notice(F("WIFI: Connecting to wifi using stored settings %s." CR), myConfig.getWifiSSID());
WiFi.begin(myConfig.getWifiSSID(), myConfig.getWifiPass());
}
//WiFi.printDiag(Serial);
while( WiFi.status() != WL_CONNECTED ) {
delay(200);
Serial.print( "." );
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() );
WiFi.disconnect();
return connectedFlag; // Return to main that we have failed to connect.
}
}
Serial.print( CR );
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() );
return connectedFlag;
}
//
// This will erase the stored credentials and forcing the WIFI manager to AP mode.
//
bool Wifi::disconnect() {
Log.notice(F("WIFI: Erasing stored WIFI credentials." CR));
// Erase WIFI credentials
return WiFi.disconnect(true);
}
#if defined( ACTIVATE_OTA )
//
//
//
bool Wifi::updateFirmware() {
if( !newFirmware ) {
Log.notice(F("WIFI: No newer version exist, skipping update." CR));
return false;
}
#if LOG_LEVEL==6
Log.verbose(F("WIFI: Updating firmware." CR));
#endif
WiFiClient client;
String serverPath = myConfig.getOtaURL();
serverPath += "firmware.bin";
HTTPUpdateResult ret = ESPhttpUpdate.update(client, serverPath);
switch(ret) {
case HTTP_UPDATE_FAILED:
Log.error(F("WIFI: OTA update failed %d, %s." CR), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
break;
case HTTP_UPDATE_OK:
Log.notice("WIFI: OTA Update sucesfull, rebooting." );
delay(100);
ESP.reset();
break;
}
return false;
}
//
// Download and save file
//
void Wifi::downloadFile(const char *fname) {
#if LOG_LEVEL==6
Log.verbose(F("WIFI: Download file %s." CR), fname);
#endif
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getOtaURL();
serverPath += fname;
// Your Domain name with URL path or IP address with path
http.begin( client, serverPath);
int httpResponseCode = http.GET();
if (httpResponseCode==200) {
File f = LittleFS.open( fname, "w" );
http.writeToStream( &f );
f.close();
Log.notice(F("WIFI: Downloaded file %s." CR), fname);
} else {
Log.error(F("WIFI: Failed to download file, respone=%d" CR), httpResponseCode);
}
http.end();
}
//
// Check what firmware version is available over OTA
//
bool Wifi::checkFirmwareVersion() {
#if LOG_LEVEL==6
Log.verbose(F("WIFI: Checking if new version exist." CR));
#endif
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getOtaURL();
serverPath += "version.json";
// Your Domain name with URL path or IP address with path
http.begin( client, serverPath);
// Send HTTP GET request
int httpResponseCode = http.GET();
if (httpResponseCode==200) {
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode);
String payload = http.getString();
#if LOG_LEVEL==6
Log.verbose(F("WIFI: Payload %s." CR), payload.c_str());
#endif
DynamicJsonDocument ver(300);
DeserializationError err = deserializeJson(ver, payload);
if( err ) {
Log.error(F("WIFI: Failed to parse version.json, %s" CR), err);
} else {
#if LOG_LEVEL==6
Log.verbose(F("WIFI: Project %s version %s." CR), (const char*) ver["project"], (const char*) ver["version"]);
#endif
int newVer[3];
int curVer[3];
if( parseFirmwareVersionString( newVer, (const char*) ver["version"] ) ) {
if( parseFirmwareVersionString( curVer, CFG_APPVER) ) {
#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] );
#endif
// Compare major version
if( newVer[0] > curVer[0] )
newFirmware = true;
// Compare minor version
if( newVer[0] == curVer[0] && newVer[1] > curVer[1] )
newFirmware = true;
// Compare patch version
if( newVer[0] == curVer[0] && newVer[1] == curVer[1] && newVer[2] > curVer[2] )
newFirmware = true;
}
}
// Download new html files to filesystem if they are present.
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) {
String s = v;
#if LOG_LEVEL==6
Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str() );
#endif
downloadFile( s.c_str() );
}
}
}
} else {
Log.error(F("WIFI: OTA error checking version.json, response=%d" CR), httpResponseCode);
}
http.end();
#if LOG_LEVEL==6
Log.verbose(F("WIFI: OTA found new version %s." CR), newFirmware?"true":"false");
#endif
return newFirmware;
}
//
// Parse a version string in the format M.m.p (eg. 1.2.10)
//
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 );
while ((s = strtok_r(p, ".", &p)) != NULL) {
num[i++] = atoi( s );
}
return true;
}
#endif // ACTIVATE_OTA
// EOF
/*
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.
*/
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#include <ESP8266mDNS.h>
#include <LittleFS.h>
#include <WiFiManager.h>
#include <incbin.h>
#include <calc.hpp>
#include <config.hpp>
#include <gyro.hpp>
#include <helper.hpp>
#include <tempsensor.hpp>
#include <wifi.hpp>
Wifi myWifi;
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.
//
bool Wifi::connect(bool showPortal) {
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));
showPortal = true;
} 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),
// myConfig.getWifiSSID(), "*****");
}
if (strlen(userSSID) == 0 && showPortal) {
#if LOG_LEVEL == 6
Log.verbose(
F("WIFI: Connecting to WIFI via connection manager (portal=%s)." CR),
showPortal ? "true" : "false");
#endif
WiFiManager myWifiManager;
Log.notice(F("WIFI: Starting wifi portal." CR));
myWifiManager.setDebugOutput(true);
myWifiManager.setClass("invert");
myWifiManager.setConfigPortalTimeout(120); // Keep it open for 120 seconds
bool f =
myWifiManager.startConfigPortal(WIFI_DEFAULT_SSID, WIFI_DEFAULT_PWD);
if (f) {
// Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR),
// myWifiManager.getWiFiSSID(), myWifiManager.getWiFiPass() );
Log.notice(F("WIFI: Success got values from WIFI portal=%s,%s." CR),
myWifiManager.getWiFiSSID(), "*****");
myConfig.setWifiSSID(myWifiManager.getWiFiSSID());
myConfig.setWifiPass(myWifiManager.getWiFiPass());
myConfig.saveFile();
} else {
Log.notice(F("WIFI: Failure from WIFI portal, rebooting." CR));
delay(200);
ESP.reset();
}
}
// Connect to wifi
int i = 0;
// Log.notice(F("WIFI: Connecting to WIFI, mode=%d,persistent=%d,fhy=%d ."
// CR), WiFi.getMode(), WiFi.getPersistent(), WiFi.getPhyMode() );
WiFi.mode(WIFI_STA);
if (strlen(userSSID)) {
Log.notice(F("WIFI: Connecting to wifi using hardcoded settings %s." CR),
userSSID);
WiFi.begin(userSSID, userPWD);
} else {
Log.notice(F("WIFI: Connecting to wifi using stored settings %s." CR),
myConfig.getWifiSSID());
WiFi.begin(myConfig.getWifiSSID(), myConfig.getWifiPass());
}
// WiFi.printDiag(Serial);
while (WiFi.status() != WL_CONNECTED) {
delay(200);
Serial.print(".");
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());
WiFi.disconnect();
return connectedFlag; // Return to main that we have failed to connect.
}
}
Serial.print(CR);
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());
return connectedFlag;
}
//
// This will erase the stored credentials and forcing the WIFI manager to AP
// mode.
//
bool Wifi::disconnect() {
Log.notice(F("WIFI: Erasing stored WIFI credentials." CR));
// Erase WIFI credentials
return WiFi.disconnect(true);
}
#if defined(ACTIVATE_OTA)
//
//
//
bool Wifi::updateFirmware() {
if (!newFirmware) {
Log.notice(F("WIFI: No newer version exist, skipping update." CR));
return false;
}
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Updating firmware." CR));
#endif
WiFiClient client;
String serverPath = myConfig.getOtaURL();
serverPath += "firmware.bin";
HTTPUpdateResult ret = ESPhttpUpdate.update(client, serverPath);
switch (ret) {
case HTTP_UPDATE_FAILED:
Log.error(F("WIFI: OTA update failed %d, %s." CR),
ESPhttpUpdate.getLastError(),
ESPhttpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
break;
case HTTP_UPDATE_OK:
Log.notice("WIFI: OTA Update sucesfull, rebooting.");
delay(100);
ESP.reset();
break;
}
return false;
}
//
// Download and save file
//
void Wifi::downloadFile(const char *fname) {
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Download file %s." CR), fname);
#endif
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getOtaURL();
serverPath += fname;
// Your Domain name with URL path or IP address with path
http.begin(client, serverPath);
int httpResponseCode = http.GET();
if (httpResponseCode == 200) {
File f = LittleFS.open(fname, "w");
http.writeToStream(&f);
f.close();
Log.notice(F("WIFI: Downloaded file %s." CR), fname);
} else {
Log.error(F("WIFI: Failed to download file, respone=%d" CR),
httpResponseCode);
}
http.end();
}
//
// Check what firmware version is available over OTA
//
bool Wifi::checkFirmwareVersion() {
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Checking if new version exist." CR));
#endif
WiFiClient client;
HTTPClient http;
String serverPath = myConfig.getOtaURL();
serverPath += "version.json";
// Your Domain name with URL path or IP address with path
http.begin(client, serverPath);
// Send HTTP GET request
int httpResponseCode = http.GET();
if (httpResponseCode == 200) {
Log.notice(F("WIFI: Found version.json, response=%d" CR), httpResponseCode);
String payload = http.getString();
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Payload %s." CR), payload.c_str());
#endif
DynamicJsonDocument ver(300);
DeserializationError err = deserializeJson(ver, payload);
if (err) {
Log.error(F("WIFI: Failed to parse version.json, %s" CR), err);
} else {
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: Project %s version %s." CR),
(const char *)ver["project"], (const char *)ver["version"]);
#endif
int newVer[3];
int curVer[3];
if (parseFirmwareVersionString(newVer, (const char *)ver["version"])) {
if (parseFirmwareVersionString(curVer, CFG_APPVER)) {
#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]);
#endif
// Compare major version
if (newVer[0] > curVer[0]) newFirmware = true;
// Compare minor version
if (newVer[0] == curVer[0] && newVer[1] > curVer[1])
newFirmware = true;
// Compare patch version
if (newVer[0] == curVer[0] && newVer[1] == curVer[1] &&
newVer[2] > curVer[2])
newFirmware = true;
}
}
// Download new html files to filesystem if they are present.
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) {
String s = v;
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: OTA listed html file %s" CR), s.c_str());
#endif
downloadFile(s.c_str());
}
}
}
} else {
Log.error(F("WIFI: OTA error checking version.json, response=%d" CR),
httpResponseCode);
}
http.end();
#if LOG_LEVEL == 6
Log.verbose(F("WIFI: OTA found new version %s." CR),
newFirmware ? "true" : "false");
#endif
return newFirmware;
}
//
// Parse a version string in the format M.m.p (eg. 1.2.10)
//
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
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 _WIFI_H
#define _WIFI_H
// Include
#include <ESP8266WiFi.h>
// classes
class Wifi {
private:
// WIFI
bool connectedFlag = false;
// OTA
bool newFirmware = false;
bool parseFirmwareVersionString( int (&num)[3], const char *version );
void downloadFile(const char *fname);
public:
// WIFI
bool connect( bool showPortal = false );
bool disconnect();
bool isConnected() { return connectedFlag; };
String getIPAddress() { return WiFi.localIP().toString(); };
// OTA
bool updateFirmware();
bool checkFirmwareVersion();
};
// Global instance created
extern Wifi myWifi;
#endif // _WIFI_H
// EOF
/*
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_WIFI_HPP_
#define SRC_WIFI_HPP_
// Include
#include <ESP8266WiFi.h>
// classes
class Wifi {
private:
// WIFI
bool connectedFlag = false;
// OTA
bool newFirmware = false;
bool parseFirmwareVersionString(int (&num)[3], const char *version);
void downloadFile(const char *fname);
public:
// WIFI
bool connect(bool showPortal = false);
bool disconnect();
bool isConnected() { return connectedFlag; }
String getIPAddress() { return WiFi.localIP().toString(); }
// OTA
bool updateFirmware();
bool checkFirmwareVersion();
};
// Global instance created
extern Wifi myWifi;
#endif // SRC_WIFI_HPP_
// EOF