diff --git a/src/calc.cpp b/src/calc.cpp index 559b837..053fd6d 100644 --- a/src/calc.cpp +++ b/src/calc.cpp @@ -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 -#include -#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 +#include +#include + +#include +#include +#include + +#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 diff --git a/src/calc.h b/src/calc.hpp similarity index 74% rename from src/calc.h rename to src/calc.hpp index cc10378..a6ea40d 100644 --- a/src/calc.h +++ b/src/calc.hpp @@ -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 +#include + +#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 diff --git a/src/config.cpp b/src/config.cpp index 167e616..66c0589 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -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 +#include +#include + 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()); - if ( !doc[ CFG_PARAM_PUSH_INTERVAL ].isNull() ) // TODO: @deprecated - setSleepInterval(doc[ CFG_PARAM_PUSH_INTERVAL ].as()); // TODO: @deprecated - if ( !doc[ CFG_PARAM_VOLTAGEFACTOR ].isNull() ) - setVoltageFactor(doc[ CFG_PARAM_VOLTAGEFACTOR ].as()); - 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()); - 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()); + 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()); + if (!doc[CFG_PARAM_PUSH_INTERVAL].isNull()) // TODO: @deprecated + setSleepInterval( + doc[CFG_PARAM_PUSH_INTERVAL].as()); // TODO: @deprecated + if (!doc[CFG_PARAM_VOLTAGEFACTOR].isNull()) + setVoltageFactor(doc[CFG_PARAM_VOLTAGEFACTOR].as()); + 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()); + 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()); - 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(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a2" ].isNull() ) - formulaData.a[1] = doc[ CFG_PARAM_FORMULA_DATA ][ "a2" ].as(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a3" ].isNull() ) - formulaData.a[2] = doc[ CFG_PARAM_FORMULA_DATA ][ "a3" ].as(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a4" ].isNull() ) - formulaData.a[3] = doc[ CFG_PARAM_FORMULA_DATA ][ "a4" ].as(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "a5" ].isNull() ) - formulaData.a[4] = doc[ CFG_PARAM_FORMULA_DATA ][ "a5" ].as(); + if (!doc[CFG_PARAM_FORMULA_DATA]["a1"].isNull()) + formulaData.a[0] = doc[CFG_PARAM_FORMULA_DATA]["a1"].as(); + if (!doc[CFG_PARAM_FORMULA_DATA]["a2"].isNull()) + formulaData.a[1] = doc[CFG_PARAM_FORMULA_DATA]["a2"].as(); + if (!doc[CFG_PARAM_FORMULA_DATA]["a3"].isNull()) + formulaData.a[2] = doc[CFG_PARAM_FORMULA_DATA]["a3"].as(); + if (!doc[CFG_PARAM_FORMULA_DATA]["a4"].isNull()) + formulaData.a[3] = doc[CFG_PARAM_FORMULA_DATA]["a4"].as(); + if (!doc[CFG_PARAM_FORMULA_DATA]["a5"].isNull()) + formulaData.a[4] = doc[CFG_PARAM_FORMULA_DATA]["a5"].as(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g1" ].isNull() ) - formulaData.g[0] = doc[ CFG_PARAM_FORMULA_DATA ][ "g1" ].as(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g2" ].isNull() ) - formulaData.g[1] = doc[ CFG_PARAM_FORMULA_DATA ][ "g2" ].as(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g3" ].isNull() ) - formulaData.g[2] = doc[ CFG_PARAM_FORMULA_DATA ][ "g3" ].as(); - if ( !doc[ CFG_PARAM_FORMULA_DATA ][ "g4" ].isNull() ) - formulaData.g[3] = doc[ CFG_PARAM_FORMULA_DATA ][ "g4" ].as(); - 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(); + if (!doc[CFG_PARAM_FORMULA_DATA]["g2"].isNull()) + formulaData.g[1] = doc[CFG_PARAM_FORMULA_DATA]["g2"].as(); + if (!doc[CFG_PARAM_FORMULA_DATA]["g3"].isNull()) + formulaData.g[2] = doc[CFG_PARAM_FORMULA_DATA]["g3"].as(); + if (!doc[CFG_PARAM_FORMULA_DATA]["g4"].isNull()) + formulaData.g[3] = doc[CFG_PARAM_FORMULA_DATA]["g4"].as(); + 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 diff --git a/src/config.h b/src/config.h deleted file mode 100644 index d33d9bb..0000000 --- a/src/config.h +++ /dev/null @@ -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 -#include "src/helper.h" -#include -#include - -// 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 diff --git a/src/config.hpp b/src/config.hpp new file mode 100644 index 0000000..82abb34 --- /dev/null +++ b/src/config.hpp @@ -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 +#include +#include + +#include + +// 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 diff --git a/src/gyro.cpp b/src/gyro.cpp index 3bf26e5..f0c30e1 100644 --- a/src/gyro.cpp +++ b/src/gyro.cpp @@ -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 \ No newline at end of file +/* +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 +#include + +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(raw.ax)) / 16384, + ay = (static_cast(raw.ay)) / 16384, + az = (static_cast(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(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 diff --git a/src/gyro.h b/src/gyro.h deleted file mode 100644 index 8b1b081..0000000 --- a/src/gyro.h +++ /dev/null @@ -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 -#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 \ No newline at end of file diff --git a/src/gyro.hpp b/src/gyro.hpp new file mode 100644 index 0000000..5d01859 --- /dev/null +++ b/src/gyro.hpp @@ -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 +#include + +#include + +// 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 diff --git a/src/helper.cpp b/src/helper.cpp index 737b15e..b50f277 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -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 -#include - -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 \ No newline at end of file +/* +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 +#include + +#include +#include +#include +#include + +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 diff --git a/src/helper.h b/src/helper.h deleted file mode 100644 index 8bd4f02..0000000 --- a/src/helper.h +++ /dev/null @@ -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 - -// 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 \ No newline at end of file diff --git a/src/helper.hpp b/src/helper.hpp new file mode 100644 index 0000000..b173160 --- /dev/null +++ b/src/helper.hpp @@ -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 + +// 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 diff --git a/src/main.cpp b/src/main.cpp index 2ee8619..ef6c741 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 - -// Settings for double reset detector. -#define ESP8266_DRD_USE_RTC true -#define DRD_TIMEOUT 2 -#define DRD_ADDRESS 0 -#include -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 \ No newline at end of file +/* +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 + +#include +#include +#include +#include +#include +#include +#include +#include + +// Settings for double reset detector. +#define ESP8266_DRD_USE_RTC true +#define DRD_TIMEOUT 2 +#define DRD_ADDRESS 0 +#include +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 diff --git a/src/pushtarget.cpp b/src/pushtarget.cpp index c5c4ec8..51ce87a 100644 --- a/src/pushtarget.cpp +++ b/src/pushtarget.cpp @@ -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 \ No newline at end of file +/* +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 + +#include +#include + +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 diff --git a/src/pushtarget.h b/src/pushtarget.hpp similarity index 61% rename from src/pushtarget.h rename to src/pushtarget.hpp index d05d64b..221e0eb 100644 --- a/src/pushtarget.h +++ b/src/pushtarget.hpp @@ -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 -#include -#include -#include - -// 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 \ No newline at end of file +/* +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 +#include +#include +#include + +#include + +// 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 diff --git a/src/resources.cpp b/src/resources.cpp index 4746241..f4b93e7 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -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 - -#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 \ No newline at end of file +/* +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 + +#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 diff --git a/src/tempsensor.cpp b/src/tempsensor.cpp index 0d9faa4..885a1ee 100644 --- a/src/tempsensor.cpp +++ b/src/tempsensor.cpp @@ -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 -#include -#include - -// -// 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 \ No newline at end of file +/* +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 +#include +#include + +#include +#include +#include +#include + +// +// 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 diff --git a/src/tempsensor.h b/src/tempsensor.hpp similarity index 69% rename from src/tempsensor.h rename to src/tempsensor.hpp index 4e400ea..471a349 100644 --- a/src/tempsensor.h +++ b/src/tempsensor.hpp @@ -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 \ No newline at end of file +/* +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 diff --git a/src/webserver.cpp b/src/webserver.cpp index 0843e65..560a2dc 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -1,645 +1,724 @@ -/* -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 "webserver.h" -#include "config.h" -#include "helper.h" -#include "gyro.h" -#include "calc.h" -#include "tempsensor.h" -#include -#include - -WebServer myWebServer; // My wrapper class fr webserver functions -extern bool sleepModeActive; -extern bool sleepModeAlwaysSkip; - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleDevice() { - LOG_PERF_START("webserver-api-device"); - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : webServer callback for /api/config." CR)); - #endif - - DynamicJsonDocument doc(100); - doc[ CFG_PARAM_ID ] = myConfig.getID(); - doc[ CFG_PARAM_APP_NAME ] = CFG_APPNAME; - doc[ CFG_PARAM_APP_VER ] = CFG_APPVER; - doc[ CFG_PARAM_MDNS ] = myConfig.getMDNS(); -#if LOG_LEVEL==6 - serializeJson(doc, Serial); - Serial.print( CR ); -#endif - String out; - serializeJson(doc, out); - server->send(200, "application/json", out.c_str() ); - LOG_PERF_STOP("webserver-api-device"); -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleConfig() { - LOG_PERF_START("webserver-api-config"); - Log.notice(F("WEB : webServer callback for /api/config." CR)); - - DynamicJsonDocument doc(CFG_JSON_BUFSIZE); - myConfig.createJson( doc ); - - double angle = myGyro.getAngle(); - double temp = myTempSensor.getTempC(); - double gravity = calculateGravity( angle, temp ); - - doc[ CFG_PARAM_ANGLE ] = reduceFloatPrecision( angle); - if( myConfig.isGravityTempAdj() ) - doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravityTemperatureCorrection( gravity, temp, myConfig.getTempFormat() ), 4); - else - doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravity, 4); - doc[ CFG_PARAM_BATTERY ] = reduceFloatPrecision( myBatteryVoltage.getVoltage()); - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - serializeJson(doc, Serial); - Serial.print( CR ); - #endif - - String out; - serializeJson(doc, out); - server->send(200, "application/json", out.c_str() ); - LOG_PERF_STOP("webserver-api-config"); -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleUpload() { - LOG_PERF_START("webserver-api-upload"); - Log.notice(F("WEB : webServer callback for /api/upload." CR)); - DynamicJsonDocument doc(100); - - doc[ "index" ] = myWebServer.checkHtmlFile( WebServer::HTML_INDEX ); - doc[ "device" ] = myWebServer.checkHtmlFile( WebServer::HTML_DEVICE ); - doc[ "config" ] = myWebServer.checkHtmlFile( WebServer::HTML_CONFIG ); - doc[ "calibration" ] = myWebServer.checkHtmlFile( WebServer::HTML_CALIBRATION ); - doc[ "about" ] = myWebServer.checkHtmlFile( WebServer::HTML_ABOUT ); - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - serializeJson(doc, Serial); - Serial.print( CR ); - #endif - - String out; - serializeJson(doc, out); - server->send(200, "application/json", out.c_str() ); - LOG_PERF_STOP("webserver-api-upload"); -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleUploadFile() { - LOG_PERF_START("webserver-api-upload-file"); - Log.notice(F("WEB : webServer callback for /api/upload/file." CR)); - HTTPUpload& upload = server->upload(); - String f = upload.filename; - bool validFilename = false; - - if( f.equalsIgnoreCase("index.min.htm") || f.equalsIgnoreCase("device.min.htm") || f.equalsIgnoreCase("calibration.min.htm") || - f.equalsIgnoreCase("config.min.htm") || f.equalsIgnoreCase("about.min.htm") ) { - validFilename = true; - } - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.debug(F("WEB : webServer callback for /api/upload, receiving file %s, valid=%s." CR), f.c_str(), validFilename?"yes":"no"); - #endif - - if(upload.status == UPLOAD_FILE_START) { - Log.notice(F("WEB : Start upload." CR) ); - if( validFilename ) - uploadFile = LittleFS.open( f, "w"); - } else if(upload.status == UPLOAD_FILE_WRITE) { - Log.notice(F("WEB : Writing upload." CR) ); - if(uploadFile) - uploadFile.write(upload.buf, upload.currentSize); // Write the received bytes to the file - } else if(upload.status == UPLOAD_FILE_END){ - Log.notice(F("WEB : Finish upload." CR) ); - if(uploadFile) { - uploadFile.close(); - Log.notice(F("WEB : File uploaded %d bytes." CR), upload.totalSize); - } - server->sendHeader("Location","/"); - server->send(303); - } else { - server->send(500, "text/plain", "Couldn't create file."); - } - LOG_PERF_STOP("webserver-api-upload-file"); -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleCalibrate() { - LOG_PERF_START("webserver-api-calibrate"); - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/calibrate." CR)); - - if( !id.equalsIgnoreCase( myConfig.getID() ) ) { - Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), myConfig.getID()); - server->send(400, "text/plain", "Invalid ID."); - LOG_PERF_STOP("webserver-api-calibrate"); - return; - } - myGyro.calibrateSensor(); - server->send(200, "text/plain", "Device calibrated" ); - LOG_PERF_STOP("webserver-api-calibrate"); -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleFactoryReset() { - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/factory." CR)); - - if( !id.compareTo( myConfig.getID() ) ) { - server->send(200, "text/plain", "Doing reset..."); - LittleFS.remove(CFG_FILENAME); - LittleFS.end(); - delay(500); - ESP.reset(); - } else { - server->send(400, "text/plain", "Unknown ID."); - } -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleStatus() { - LOG_PERF_START("webserver-api-status"); - Log.notice(F("WEB : webServer callback for /api/status." CR)); - - DynamicJsonDocument doc(256); - - double angle = myGyro.getAngle(); - double temp = myTempSensor.getTempC(); - double gravity = calculateGravity( angle, temp ); - - doc[ CFG_PARAM_ID ] = myConfig.getID(); - doc[ CFG_PARAM_ANGLE ] = reduceFloatPrecision( angle); - if( myConfig.isGravityTempAdj() ) - doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravityTemperatureCorrection( gravity, temp, myConfig.getTempFormat() ), 4); - else - doc[ CFG_PARAM_GRAVITY ] = reduceFloatPrecision( gravity, 4); - doc[ CFG_PARAM_TEMP_C ] = reduceFloatPrecision( temp, 1); - doc[ CFG_PARAM_TEMP_F ] = reduceFloatPrecision( myTempSensor.getTempF(), 1); - doc[ CFG_PARAM_BATTERY ] = reduceFloatPrecision( myBatteryVoltage.getVoltage()); - doc[ CFG_PARAM_TEMPFORMAT ] = String( myConfig.getTempFormat() ); - doc[ CFG_PARAM_SLEEP_MODE ] = sleepModeAlwaysSkip; - doc[ CFG_PARAM_RSSI ] = WiFi.RSSI(); - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - serializeJson(doc, Serial); - Serial.print( CR ); - #endif - - String out; - serializeJson(doc, out); - server->send(200, "application/json", out.c_str() ); - LOG_PERF_STOP("webserver-api-status"); -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleClearWIFI() { - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/clearwifi." CR)); - - if( !id.compareTo( myConfig.getID() ) ) { - server->send(200, "text/plain", "Clearing WIFI credentials and doing reset..."); - delay(1000); - WiFi.disconnect(); // Clear credentials - ESP.reset(); - } else { - server->send(400, "text/plain", "Unknown ID."); - } -} - -// -// Used to force the device to never sleep. -// -void WebServer::webHandleStatusSleepmode() { - LOG_PERF_START("webserver-api-sleepmode"); - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/status/sleepmode." CR) ); - - if( !id.equalsIgnoreCase( myConfig.getID() ) ) { - Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), myConfig.getID()); - server->send(400, "text/plain", "Invalid ID."); - LOG_PERF_STOP("webserver-api-sleepmode"); - return; - } - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : sleep-mode=%s." CR), server->arg( CFG_PARAM_SLEEP_MODE ).c_str() ); - #endif - - if( server->arg( CFG_PARAM_SLEEP_MODE ).equalsIgnoreCase( "true" ) ) - sleepModeAlwaysSkip = true; - else - sleepModeAlwaysSkip = false; - server->send(200, "text/plain", "Sleep mode updated" ); - LOG_PERF_STOP("webserver-api-sleepmode"); -} - -// -// Update device settings. -// -void WebServer::webHandleConfigDevice() { - LOG_PERF_START("webserver-api-config-device"); - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/config/device." CR) ); - - if( !id.equalsIgnoreCase( myConfig.getID() ) ) { - Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), myConfig.getID()); - server->send(400, "text/plain", "Invalid ID."); - LOG_PERF_STOP("webserver-api-config-device"); - return; - } - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : mdns=%s, temp-format=%s." CR), server->arg( CFG_PARAM_MDNS ).c_str(), server->arg( CFG_PARAM_TEMPFORMAT ).c_str() ); - #endif - - myConfig.setMDNS( server->arg( CFG_PARAM_MDNS ).c_str() ); - myConfig.setTempFormat( server->arg( CFG_PARAM_TEMPFORMAT ).charAt(0) ); - myConfig.setSleepInterval( server->arg( CFG_PARAM_SLEEP_INTERVAL ).c_str() ); - myConfig.saveFile(); - server->sendHeader("Location", "/config.htm#collapseOne", true); - server->send(302, "text/plain", "Device config updated" ); - LOG_PERF_STOP("webserver-api-config-device"); -} - -// -// Update push settings. -// -void WebServer::webHandleConfigPush() { - LOG_PERF_START("webserver-api-config-push"); - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/config/push." CR) ); - - if( !id.equalsIgnoreCase( myConfig.getID() ) ) { - Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), myConfig.getID()); - server->send(400, "text/plain", "Invalid ID."); - LOG_PERF_STOP("webserver-api-config-push"); - return; - } - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : http=%s,%s, bf=%s influx2=%s, %s, %s, %s." CR), server->arg( CFG_PARAM_PUSH_HTTP ).c_str(), - server->arg( CFG_PARAM_PUSH_HTTP2 ).c_str(), server->arg( CFG_PARAM_PUSH_BREWFATHER ).c_str(), - server->arg( CFG_PARAM_PUSH_INFLUXDB2 ).c_str(), server->arg( CFG_PARAM_PUSH_INFLUXDB2_ORG ).c_str(), - server->arg( CFG_PARAM_PUSH_INFLUXDB2_BUCKET ).c_str(), server->arg( CFG_PARAM_PUSH_INFLUXDB2_AUTH ).c_str() - ); - #endif - - myConfig.setHttpPushUrl( server->arg( CFG_PARAM_PUSH_HTTP ).c_str() ); - myConfig.setHttpPushUrl2( server->arg( CFG_PARAM_PUSH_HTTP2 ).c_str() ); - myConfig.setBrewfatherPushUrl( server->arg( CFG_PARAM_PUSH_BREWFATHER ).c_str() ); - myConfig.setInfluxDb2PushUrl( server->arg( CFG_PARAM_PUSH_INFLUXDB2 ).c_str() ); - myConfig.setInfluxDb2PushOrg( server->arg( CFG_PARAM_PUSH_INFLUXDB2_ORG ).c_str() ); - myConfig.setInfluxDb2PushBucket( server->arg( CFG_PARAM_PUSH_INFLUXDB2_BUCKET ).c_str() ); - myConfig.setInfluxDb2PushToken( server->arg( CFG_PARAM_PUSH_INFLUXDB2_AUTH ).c_str() ); - myConfig.saveFile(); - server->sendHeader("Location", "/config.htm#collapseTwo", true); - server->send(302, "text/plain", "Push config updated" ); - LOG_PERF_STOP("webserver-api-config-push"); -} - -// -// Update gravity settings. -// -void WebServer::webHandleConfigGravity() { - LOG_PERF_START("webserver-api-config-gravity"); - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/config/gravity." CR) ); - - if( !id.equalsIgnoreCase( myConfig.getID() ) ) { - Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), myConfig.getID()); - server->send(400, "text/plain", "Invalid ID."); - LOG_PERF_STOP("webserver-api-config-gravity"); - return; - } - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : formula=%s, temp-corr=%s." CR), server->arg( CFG_PARAM_GRAVITY_FORMULA ).c_str(), server->arg( CFG_PARAM_GRAVITY_TEMP_ADJ ).c_str() ); - #endif - - myConfig.setGravityFormula( server->arg( CFG_PARAM_GRAVITY_FORMULA ).c_str() ); - myConfig.setGravityTempAdj( server->arg( CFG_PARAM_GRAVITY_TEMP_ADJ ).equalsIgnoreCase( "on" ) ? true:false); - myConfig.saveFile(); - server->sendHeader("Location", "/config.htm#collapseThree", true); - server->send(302, "text/plain", "Gravity config updated" ); - LOG_PERF_STOP("webserver-api-config-gravity"); -} - -// -// Update hardware settings. -// -void WebServer::webHandleConfigHardware() { - LOG_PERF_START("webserver-api-config-hardware"); - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/config/hardware." CR) ); - - if( !id.equalsIgnoreCase( myConfig.getID() ) ) { - Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), myConfig.getID()); - server->send(400, "text/plain", "Invalid ID."); - LOG_PERF_STOP("webserver-api-config-hardware"); - return; - } - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : vf=%s, tempadj=%s, ota=%s." CR), server->arg( CFG_PARAM_VOLTAGEFACTOR ).c_str(), server->arg( CFG_PARAM_TEMP_ADJ ).c_str(), server->arg( CFG_PARAM_OTA ).c_str() ); - #endif - - myConfig.setVoltageFactor( server->arg( CFG_PARAM_VOLTAGEFACTOR ).toFloat() ); - myConfig.setTempSensorAdj( server->arg( CFG_PARAM_TEMP_ADJ ).toFloat() ); - myConfig.setOtaURL( server->arg( CFG_PARAM_OTA ).c_str() ); - myConfig.saveFile(); - server->sendHeader("Location", "/config.htm#collapseFour", true); - server->send(302, "text/plain", "Hardware config updated" ); - LOG_PERF_STOP("webserver-api-config-hardware"); -} - -// -// Callback from webServer when / has been accessed. -// -void WebServer::webHandleFormulaRead() { - LOG_PERF_START("webserver-api-formula-read"); - Log.notice(F("WEB : webServer callback for /api/formula/get." CR)); - - DynamicJsonDocument doc(250); - const RawFormulaData& fd = myConfig.getFormulaData(); - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : %F %F %F %F %F." CR), fd.a[0], fd.a[1], fd.a[2], fd.a[3], fd.a[4] ); - Log.verbose(F("WEB : %F %F %F %F %F." CR), fd.g[0], fd.g[1], fd.g[2], fd.g[3], fd.g[4] ); - #endif - - doc[ CFG_PARAM_ID ] = myConfig.getID(); - doc[ CFG_PARAM_ANGLE ] = reduceFloatPrecision( myGyro.getAngle() ); - - switch( lastFormulaCreateError ) { - case ERR_FORMULA_INTERNAL: - doc[ CFG_PARAM_GRAVITY_FORMULA ] = "Internal error creating formula."; - break; - case ERR_FORMULA_NOTENOUGHVALUES: - doc[ CFG_PARAM_GRAVITY_FORMULA ] = "Not enough values to create formula."; - break; - case ERR_FORMULA_UNABLETOFFIND: - doc[ CFG_PARAM_GRAVITY_FORMULA ] = "Unable to find an accurate formula based on input."; - break; - default: - doc[ CFG_PARAM_GRAVITY_FORMULA ] = myConfig.getGravityFormula(); - break; - } - - doc[ "a1" ] = reduceFloatPrecision( fd.a[0], 2); - doc[ "a2" ] = reduceFloatPrecision( fd.a[1], 2); - doc[ "a3" ] = reduceFloatPrecision( fd.a[2], 2); - doc[ "a4" ] = reduceFloatPrecision( fd.a[3], 2); - doc[ "a5" ] = reduceFloatPrecision( fd.a[4], 2); - - doc[ "g1" ] = reduceFloatPrecision( fd.g[0], 4); - doc[ "g2" ] = reduceFloatPrecision( fd.g[1], 4); - doc[ "g3" ] = reduceFloatPrecision( fd.g[2], 4); - doc[ "g4" ] = reduceFloatPrecision( fd.g[3], 4); - doc[ "g5" ] = reduceFloatPrecision( fd.g[4], 4); - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - serializeJson(doc, Serial); - Serial.print( CR ); - #endif - - String out; - serializeJson(doc, out); - server->send(200, "application/json", out.c_str() ); - LOG_PERF_STOP("webserver-api-formula-read"); -} - -// -// Update hardware settings. -// -void WebServer::webHandleFormulaWrite() { - LOG_PERF_START("webserver-api-formula-write"); - String id = server->arg( CFG_PARAM_ID ); - Log.notice(F("WEB : webServer callback for /api/formula/post." CR) ); - - if( !id.equalsIgnoreCase( myConfig.getID() ) ) { - Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), myConfig.getID()); - server->send(400, "text/plain", "Invalid ID."); - LOG_PERF_STOP("webserver-api-formula-write"); - return; - } - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : angles=%F,%F,%F,%F,%F." CR), server->arg( "a1" ).toFloat(), server->arg( "a2" ).toFloat(), server->arg( "a3" ).toFloat(), server->arg( "a4" ).toFloat(), server->arg( "a5" ).toFloat() ); - Log.verbose(F("WEB : gravity=%F,%F,%F,%F,%F." CR), server->arg( "g1" ).toFloat(), server->arg( "g2" ).toFloat(), server->arg( "g3" ).toFloat(), server->arg( "g4" ).toFloat(), server->arg( "g5" ).toFloat() ); - #endif - - RawFormulaData fd; - fd.a[0] = server->arg( "a1" ).toDouble(); - fd.a[1] = server->arg( "a2" ).toDouble(); - fd.a[2] = server->arg( "a3" ).toDouble(); - fd.a[3] = server->arg( "a4" ).toDouble(); - fd.a[4] = server->arg( "a5" ).toDouble(); - fd.g[0] = server->arg( "g1" ).toDouble(); - fd.g[1] = server->arg( "g2" ).toDouble(); - fd.g[2] = server->arg( "g3" ).toDouble(); - fd.g[3] = server->arg( "g4" ).toDouble(); - fd.g[4] = server->arg( "g5" ).toDouble(); - myConfig.setFormulaData( fd ); - - int e; - char buf[100]; - - e = createFormula( fd, &buf[0], 2 ); - - if( e ) { - // If we fail with order=2 try with 3 - Log.warning(F("WEB : Failed to find formula with order 3." CR), e ); - e = createFormula( fd, &buf[0], 3 ); - } - - if( e ) { - // If we fail with order=3 try with 4 - Log.warning(F("WEB : Failed to find formula with order 4." CR), e ); - e = createFormula( fd, &buf[0], 4 ); - } - - if( e ) { - // If we fail with order=4 then we mark it as failed - Log.error(F("WEB : Unable to find formula based on provided values err=%d." CR), e ); - lastFormulaCreateError = e; - } else { - // Save the formula as succesful - Log.info(F("WEB : Found valid formula: '%s'" CR), &buf[0] ); - myConfig.setGravityFormula( buf ); - lastFormulaCreateError = 0; - } - - myConfig.saveFile(); - server->sendHeader("Location", "/calibration.htm", true); - server->send(302, "text/plain", "Formula updated" ); - LOG_PERF_STOP("webserver-api-formula-write"); -} -// -// Helper function to check if files exist on file system. -// -const char* WebServer::getHtmlFileName( HtmlFile item ) { - Log.notice(F("WEB : Looking up filename for %d." CR), item); - - switch( item ) { - case HTML_INDEX: - return "index.min.htm"; - case HTML_DEVICE: - return "device.min.htm"; - case HTML_CONFIG: - return "config.min.htm"; - case HTML_CALIBRATION: - return "calibration.min.htm"; - case HTML_ABOUT: - return "about.min.htm"; - } - - return ""; -} - -// -// Helper function to check if files exist on file system. -// -bool WebServer::checkHtmlFile( HtmlFile item ) { - const char *fn = getHtmlFileName( item ); - - #if LOG_LEVEL==6 && !defined( WEB_DISABLE_LOGGING ) - Log.verbose(F("WEB : Checking for file %s." CR), fn ); - #endif - - // TODO: We might need to add more checks here like zero file size etc. But for now we only check if the file exist. - - return LittleFS.exists( fn ); -} - -// -// Handler for page not found -// -void WebServer::webHandlePageNotFound() -{ - Log.error(F("WEB : URL not found %s received." CR), server->uri().c_str()); - server->send(404, "text/plain", F("URL not found") ); -} - -// -// Setup the Web Server callbacks and start it -// -bool WebServer::setupWebServer() { - Log.notice(F("WEB : Configuring web server." CR)); - - server = new ESP8266WebServer(); - - MDNS.begin( myConfig.getMDNS() ); - MDNS.addService("http", "tcp", 80); - - // Static content -#if defined( EMBED_HTML ) - server->on("/", std::bind(&WebServer::webReturnIndexHtm, this) ); - server->on("/index.htm", std::bind(&WebServer::webReturnIndexHtm, this) ); - server->on("/device.htm", std::bind(&WebServer::webReturnDeviceHtm, this) ); - server->on("/config.htm", std::bind(&WebServer::webReturnConfigHtm, this) ); - server->on("/calibration.htm", std::bind(&WebServer::webReturnCalibrationHtm, this) ); - server->on("/about.htm", std::bind(&WebServer::webReturnAboutHtm, this) ); -#else - // Show files in the filessytem at startup - - FSInfo fs; - LittleFS.info(fs); - Log.notice( F("WEB : File system Total=%d, Used=%d." CR), fs.totalBytes, fs.usedBytes ); - Dir dir = LittleFS.openDir("/"); - while( dir.next() ) { - Log.notice( F("WEB : File=%s, %d bytes" CR), dir.fileName().c_str(), dir.fileSize() ); - } - - // Check if the html files exist, if so serve them, else show the static upload page. - if( checkHtmlFile( HTML_INDEX ) && checkHtmlFile( HTML_DEVICE ) && checkHtmlFile( HTML_CONFIG ) && checkHtmlFile( HTML_ABOUT ) ) { - Log.notice(F("WEB : All html files exist, starting in normal mode." CR)); - - server->serveStatic("/", LittleFS, "/index.min.htm" ); - server->serveStatic("/index.htm", LittleFS, "/index.min.htm" ); - server->serveStatic("/device.htm", LittleFS, "/device.min.htm" ); - server->serveStatic("/config.htm", LittleFS, "/config.min.htm" ); - server->serveStatic("/about.htm", LittleFS, "/about.min.htm" ); - server->serveStatic("/calibration.htm", LittleFS, "/calibration.min.htm" ); - - // Also add the static upload view in case we we have issues that needs to be fixed. - server->on("/upload.htm", std::bind(&WebServer::webReturnUploadHtm, this) ); - } else { - Log.error(F("WEB : Missing html files, starting with upload UI." CR)); - server->on("/", std::bind(&WebServer::webReturnUploadHtm, this) ); - } -#endif - - // Dynamic content - server->on("/api/config", HTTP_GET, std::bind(&WebServer::webHandleConfig, this) ); // Get config.json - server->on("/api/device", HTTP_GET, std::bind(&WebServer::webHandleDevice, this)); // Get device.json - server->on("/api/formula", HTTP_GET, std::bind(&WebServer::webHandleFormulaRead, this)); // Get formula.json (calibration page) - server->on("/api/formula", HTTP_POST, std::bind(&WebServer::webHandleFormulaWrite, this)); // Get formula.json (calibration page) - server->on("/api/calibrate", HTTP_POST, std::bind(&WebServer::webHandleCalibrate, this)); // Run calibration routine (param id) - server->on("/api/factory", HTTP_GET, std::bind(&WebServer::webHandleFactoryReset, this)); // Reset the device - server->on("/api/status", HTTP_GET, std::bind(&WebServer::webHandleStatus, this)); // Get the status.json - server->on("/api/clearwifi", HTTP_GET, std::bind(&WebServer::webHandleClearWIFI, this)); // Clear wifi settings - server->on("/api/upload", HTTP_GET, std::bind(&WebServer::webHandleUpload, this)); // Get upload.json - - server->on("/api/upload", HTTP_POST, std::bind(&WebServer::webReturnOK, this), std::bind(&WebServer::webHandleUploadFile, this)); // File upload data - server->on("/api/status/sleepmode", HTTP_POST, std::bind(&WebServer::webHandleStatusSleepmode, this)); // Change sleep mode - server->on("/api/config/device", HTTP_POST, std::bind(&WebServer::webHandleConfigDevice, this)); // Change device settings - server->on("/api/config/push", HTTP_POST, std::bind(&WebServer::webHandleConfigPush, this)); // Change push settings - server->on("/api/config/gravity", HTTP_POST, std::bind(&WebServer::webHandleConfigGravity, this)); // Change gravity settings - server->on("/api/config/hardware", HTTP_POST, std::bind(&WebServer::webHandleConfigHardware, this)); // Change hardware settings - - server->onNotFound( std::bind(&WebServer::webHandlePageNotFound, this)); - server->begin(); - Log.notice(F("WEB : Web server started." CR)); - return true; -} - -// -// called from main loop -// -void WebServer::loop() { - // Dont put serial debug output in this call - server->handleClient(); - MDNS.update(); -} - -// EOF \ No newline at end of file +/* +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 +#include + +#include +#include +#include +#include +#include +#include + +WebServer myWebServer; // My wrapper class fr webserver functions +extern bool sleepModeActive; +extern bool sleepModeAlwaysSkip; + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleDevice() { + LOG_PERF_START("webserver-api-device"); +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : webServer callback for /api/config." CR)); +#endif + + DynamicJsonDocument doc(100); + doc[CFG_PARAM_ID] = myConfig.getID(); + doc[CFG_PARAM_APP_NAME] = CFG_APPNAME; + doc[CFG_PARAM_APP_VER] = CFG_APPVER; + doc[CFG_PARAM_MDNS] = myConfig.getMDNS(); +#if LOG_LEVEL == 6 + serializeJson(doc, Serial); + Serial.print(CR); +#endif + String out; + serializeJson(doc, out); + server->send(200, "application/json", out.c_str()); + LOG_PERF_STOP("webserver-api-device"); +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleConfig() { + LOG_PERF_START("webserver-api-config"); + Log.notice(F("WEB : webServer callback for /api/config." CR)); + + DynamicJsonDocument doc(CFG_JSON_BUFSIZE); + myConfig.createJson(doc); + + double angle = myGyro.getAngle(); + double temp = myTempSensor.getTempC(); + double gravity = calculateGravity(angle, temp); + + doc[CFG_PARAM_ANGLE] = reduceFloatPrecision(angle); + if (myConfig.isGravityTempAdj()) + doc[CFG_PARAM_GRAVITY] = reduceFloatPrecision( + gravityTemperatureCorrection(gravity, temp, myConfig.getTempFormat()), + 4); + else + doc[CFG_PARAM_GRAVITY] = reduceFloatPrecision(gravity, 4); + doc[CFG_PARAM_BATTERY] = reduceFloatPrecision(myBatteryVoltage.getVoltage()); + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + serializeJson(doc, Serial); + Serial.print(CR); +#endif + + String out; + serializeJson(doc, out); + server->send(200, "application/json", out.c_str()); + LOG_PERF_STOP("webserver-api-config"); +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleUpload() { + LOG_PERF_START("webserver-api-upload"); + Log.notice(F("WEB : webServer callback for /api/upload." CR)); + DynamicJsonDocument doc(100); + + doc["index"] = myWebServer.checkHtmlFile(WebServer::HTML_INDEX); + doc["device"] = myWebServer.checkHtmlFile(WebServer::HTML_DEVICE); + doc["config"] = myWebServer.checkHtmlFile(WebServer::HTML_CONFIG); + doc["calibration"] = myWebServer.checkHtmlFile(WebServer::HTML_CALIBRATION); + doc["about"] = myWebServer.checkHtmlFile(WebServer::HTML_ABOUT); + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + serializeJson(doc, Serial); + Serial.print(CR); +#endif + + String out; + serializeJson(doc, out); + server->send(200, "application/json", out.c_str()); + LOG_PERF_STOP("webserver-api-upload"); +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleUploadFile() { + LOG_PERF_START("webserver-api-upload-file"); + Log.notice(F("WEB : webServer callback for /api/upload/file." CR)); + HTTPUpload& upload = server->upload(); + String f = upload.filename; + bool validFilename = false; + + if (f.equalsIgnoreCase("index.min.htm") || + f.equalsIgnoreCase("device.min.htm") || + f.equalsIgnoreCase("calibration.min.htm") || + f.equalsIgnoreCase("config.min.htm") || + f.equalsIgnoreCase("about.min.htm")) { + validFilename = true; + } + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.debug(F("WEB : webServer callback for /api/upload, receiving file %s, " + "valid=%s." CR), + f.c_str(), validFilename ? "yes" : "no"); +#endif + + if (upload.status == UPLOAD_FILE_START) { + Log.notice(F("WEB : Start upload." CR)); + if (validFilename) uploadFile = LittleFS.open(f, "w"); + } else if (upload.status == UPLOAD_FILE_WRITE) { + Log.notice(F("WEB : Writing upload." CR)); + if (uploadFile) + uploadFile.write( + upload.buf, + upload.currentSize); // Write the received bytes to the file + } else if (upload.status == UPLOAD_FILE_END) { + Log.notice(F("WEB : Finish upload." CR)); + if (uploadFile) { + uploadFile.close(); + Log.notice(F("WEB : File uploaded %d bytes." CR), upload.totalSize); + } + server->sendHeader("Location", "/"); + server->send(303); + } else { + server->send(500, "text/plain", "Couldn't create file."); + } + LOG_PERF_STOP("webserver-api-upload-file"); +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleCalibrate() { + LOG_PERF_START("webserver-api-calibrate"); + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/calibrate." CR)); + + if (!id.equalsIgnoreCase(myConfig.getID())) { + Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), + myConfig.getID()); + server->send(400, "text/plain", "Invalid ID."); + LOG_PERF_STOP("webserver-api-calibrate"); + return; + } + myGyro.calibrateSensor(); + server->send(200, "text/plain", "Device calibrated"); + LOG_PERF_STOP("webserver-api-calibrate"); +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleFactoryReset() { + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/factory." CR)); + + if (!id.compareTo(myConfig.getID())) { + server->send(200, "text/plain", "Doing reset..."); + LittleFS.remove(CFG_FILENAME); + LittleFS.end(); + delay(500); + ESP.reset(); + } else { + server->send(400, "text/plain", "Unknown ID."); + } +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleStatus() { + LOG_PERF_START("webserver-api-status"); + Log.notice(F("WEB : webServer callback for /api/status." CR)); + + DynamicJsonDocument doc(256); + + double angle = myGyro.getAngle(); + double temp = myTempSensor.getTempC(); + double gravity = calculateGravity(angle, temp); + + doc[CFG_PARAM_ID] = myConfig.getID(); + doc[CFG_PARAM_ANGLE] = reduceFloatPrecision(angle); + if (myConfig.isGravityTempAdj()) + doc[CFG_PARAM_GRAVITY] = reduceFloatPrecision( + gravityTemperatureCorrection(gravity, temp, myConfig.getTempFormat()), + 4); + else + doc[CFG_PARAM_GRAVITY] = reduceFloatPrecision(gravity, 4); + doc[CFG_PARAM_TEMP_C] = reduceFloatPrecision(temp, 1); + doc[CFG_PARAM_TEMP_F] = reduceFloatPrecision(myTempSensor.getTempF(), 1); + doc[CFG_PARAM_BATTERY] = reduceFloatPrecision(myBatteryVoltage.getVoltage()); + doc[CFG_PARAM_TEMPFORMAT] = String(myConfig.getTempFormat()); + doc[CFG_PARAM_SLEEP_MODE] = sleepModeAlwaysSkip; + doc[CFG_PARAM_RSSI] = WiFi.RSSI(); + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + serializeJson(doc, Serial); + Serial.print(CR); +#endif + + String out; + serializeJson(doc, out); + server->send(200, "application/json", out.c_str()); + LOG_PERF_STOP("webserver-api-status"); +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleClearWIFI() { + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/clearwifi." CR)); + + if (!id.compareTo(myConfig.getID())) { + server->send(200, "text/plain", + "Clearing WIFI credentials and doing reset..."); + delay(1000); + WiFi.disconnect(); // Clear credentials + ESP.reset(); + } else { + server->send(400, "text/plain", "Unknown ID."); + } +} + +// +// Used to force the device to never sleep. +// +void WebServer::webHandleStatusSleepmode() { + LOG_PERF_START("webserver-api-sleepmode"); + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/status/sleepmode." CR)); + + if (!id.equalsIgnoreCase(myConfig.getID())) { + Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), + myConfig.getID()); + server->send(400, "text/plain", "Invalid ID."); + LOG_PERF_STOP("webserver-api-sleepmode"); + return; + } + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : sleep-mode=%s." CR), + server->arg(CFG_PARAM_SLEEP_MODE).c_str()); +#endif + + if (server->arg(CFG_PARAM_SLEEP_MODE).equalsIgnoreCase("true")) + sleepModeAlwaysSkip = true; + else + sleepModeAlwaysSkip = false; + server->send(200, "text/plain", "Sleep mode updated"); + LOG_PERF_STOP("webserver-api-sleepmode"); +} + +// +// Update device settings. +// +void WebServer::webHandleConfigDevice() { + LOG_PERF_START("webserver-api-config-device"); + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/config/device." CR)); + + if (!id.equalsIgnoreCase(myConfig.getID())) { + Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), + myConfig.getID()); + server->send(400, "text/plain", "Invalid ID."); + LOG_PERF_STOP("webserver-api-config-device"); + return; + } + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : mdns=%s, temp-format=%s." CR), + server->arg(CFG_PARAM_MDNS).c_str(), + server->arg(CFG_PARAM_TEMPFORMAT).c_str()); +#endif + + myConfig.setMDNS(server->arg(CFG_PARAM_MDNS).c_str()); + myConfig.setTempFormat(server->arg(CFG_PARAM_TEMPFORMAT).charAt(0)); + myConfig.setSleepInterval(server->arg(CFG_PARAM_SLEEP_INTERVAL).c_str()); + myConfig.saveFile(); + server->sendHeader("Location", "/config.htm#collapseOne", true); + server->send(302, "text/plain", "Device config updated"); + LOG_PERF_STOP("webserver-api-config-device"); +} + +// +// Update push settings. +// +void WebServer::webHandleConfigPush() { + LOG_PERF_START("webserver-api-config-push"); + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/config/push." CR)); + + if (!id.equalsIgnoreCase(myConfig.getID())) { + Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), + myConfig.getID()); + server->send(400, "text/plain", "Invalid ID."); + LOG_PERF_STOP("webserver-api-config-push"); + return; + } +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : http=%s,%s, bf=%s influx2=%s, %s, %s, %s." CR), + server->arg(CFG_PARAM_PUSH_HTTP).c_str(), + server->arg(CFG_PARAM_PUSH_HTTP2).c_str(), + server->arg(CFG_PARAM_PUSH_BREWFATHER).c_str(), + server->arg(CFG_PARAM_PUSH_INFLUXDB2).c_str(), + server->arg(CFG_PARAM_PUSH_INFLUXDB2_ORG).c_str(), + server->arg(CFG_PARAM_PUSH_INFLUXDB2_BUCKET).c_str(), + server->arg(CFG_PARAM_PUSH_INFLUXDB2_AUTH).c_str()); +#endif + + myConfig.setHttpPushUrl(server->arg(CFG_PARAM_PUSH_HTTP).c_str()); + myConfig.setHttpPushUrl2(server->arg(CFG_PARAM_PUSH_HTTP2).c_str()); + myConfig.setBrewfatherPushUrl(server->arg(CFG_PARAM_PUSH_BREWFATHER).c_str()); + myConfig.setInfluxDb2PushUrl(server->arg(CFG_PARAM_PUSH_INFLUXDB2).c_str()); + myConfig.setInfluxDb2PushOrg( + server->arg(CFG_PARAM_PUSH_INFLUXDB2_ORG).c_str()); + myConfig.setInfluxDb2PushBucket( + server->arg(CFG_PARAM_PUSH_INFLUXDB2_BUCKET).c_str()); + myConfig.setInfluxDb2PushToken( + server->arg(CFG_PARAM_PUSH_INFLUXDB2_AUTH).c_str()); + myConfig.saveFile(); + server->sendHeader("Location", "/config.htm#collapseTwo", true); + server->send(302, "text/plain", "Push config updated"); + LOG_PERF_STOP("webserver-api-config-push"); +} + +// +// Update gravity settings. +// +void WebServer::webHandleConfigGravity() { + LOG_PERF_START("webserver-api-config-gravity"); + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/config/gravity." CR)); + + if (!id.equalsIgnoreCase(myConfig.getID())) { + Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), + myConfig.getID()); + server->send(400, "text/plain", "Invalid ID."); + LOG_PERF_STOP("webserver-api-config-gravity"); + return; + } + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : formula=%s, temp-corr=%s." CR), + server->arg(CFG_PARAM_GRAVITY_FORMULA).c_str(), + server->arg(CFG_PARAM_GRAVITY_TEMP_ADJ).c_str()); +#endif + + myConfig.setGravityFormula(server->arg(CFG_PARAM_GRAVITY_FORMULA).c_str()); + myConfig.setGravityTempAdj( + server->arg(CFG_PARAM_GRAVITY_TEMP_ADJ).equalsIgnoreCase("on") ? true + : false); + myConfig.saveFile(); + server->sendHeader("Location", "/config.htm#collapseThree", true); + server->send(302, "text/plain", "Gravity config updated"); + LOG_PERF_STOP("webserver-api-config-gravity"); +} + +// +// Update hardware settings. +// +void WebServer::webHandleConfigHardware() { + LOG_PERF_START("webserver-api-config-hardware"); + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/config/hardware." CR)); + + if (!id.equalsIgnoreCase(myConfig.getID())) { + Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), + myConfig.getID()); + server->send(400, "text/plain", "Invalid ID."); + LOG_PERF_STOP("webserver-api-config-hardware"); + return; + } + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : vf=%s, tempadj=%s, ota=%s." CR), + server->arg(CFG_PARAM_VOLTAGEFACTOR).c_str(), + server->arg(CFG_PARAM_TEMP_ADJ).c_str(), + server->arg(CFG_PARAM_OTA).c_str()); +#endif + + myConfig.setVoltageFactor(server->arg(CFG_PARAM_VOLTAGEFACTOR).toFloat()); + myConfig.setTempSensorAdj(server->arg(CFG_PARAM_TEMP_ADJ).toFloat()); + myConfig.setOtaURL(server->arg(CFG_PARAM_OTA).c_str()); + myConfig.saveFile(); + server->sendHeader("Location", "/config.htm#collapseFour", true); + server->send(302, "text/plain", "Hardware config updated"); + LOG_PERF_STOP("webserver-api-config-hardware"); +} + +// +// Callback from webServer when / has been accessed. +// +void WebServer::webHandleFormulaRead() { + LOG_PERF_START("webserver-api-formula-read"); + Log.notice(F("WEB : webServer callback for /api/formula/get." CR)); + + DynamicJsonDocument doc(250); + const RawFormulaData& fd = myConfig.getFormulaData(); + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : %F %F %F %F %F." CR), fd.a[0], fd.a[1], fd.a[2], fd.a[3], + fd.a[4]); + Log.verbose(F("WEB : %F %F %F %F %F." CR), fd.g[0], fd.g[1], fd.g[2], fd.g[3], + fd.g[4]); +#endif + + doc[CFG_PARAM_ID] = myConfig.getID(); + doc[CFG_PARAM_ANGLE] = reduceFloatPrecision(myGyro.getAngle()); + + switch (lastFormulaCreateError) { + case ERR_FORMULA_INTERNAL: + doc[CFG_PARAM_GRAVITY_FORMULA] = "Internal error creating formula."; + break; + case ERR_FORMULA_NOTENOUGHVALUES: + doc[CFG_PARAM_GRAVITY_FORMULA] = "Not enough values to create formula."; + break; + case ERR_FORMULA_UNABLETOFFIND: + doc[CFG_PARAM_GRAVITY_FORMULA] = + "Unable to find an accurate formula based on input."; + break; + default: + doc[CFG_PARAM_GRAVITY_FORMULA] = myConfig.getGravityFormula(); + break; + } + + doc["a1"] = reduceFloatPrecision(fd.a[0], 2); + doc["a2"] = reduceFloatPrecision(fd.a[1], 2); + doc["a3"] = reduceFloatPrecision(fd.a[2], 2); + doc["a4"] = reduceFloatPrecision(fd.a[3], 2); + doc["a5"] = reduceFloatPrecision(fd.a[4], 2); + + doc["g1"] = reduceFloatPrecision(fd.g[0], 4); + doc["g2"] = reduceFloatPrecision(fd.g[1], 4); + doc["g3"] = reduceFloatPrecision(fd.g[2], 4); + doc["g4"] = reduceFloatPrecision(fd.g[3], 4); + doc["g5"] = reduceFloatPrecision(fd.g[4], 4); + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + serializeJson(doc, Serial); + Serial.print(CR); +#endif + + String out; + serializeJson(doc, out); + server->send(200, "application/json", out.c_str()); + LOG_PERF_STOP("webserver-api-formula-read"); +} + +// +// Update hardware settings. +// +void WebServer::webHandleFormulaWrite() { + LOG_PERF_START("webserver-api-formula-write"); + String id = server->arg(CFG_PARAM_ID); + Log.notice(F("WEB : webServer callback for /api/formula/post." CR)); + + if (!id.equalsIgnoreCase(myConfig.getID())) { + Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(), + myConfig.getID()); + server->send(400, "text/plain", "Invalid ID."); + LOG_PERF_STOP("webserver-api-formula-write"); + return; + } + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : angles=%F,%F,%F,%F,%F." CR), server->arg("a1").toFloat(), + server->arg("a2").toFloat(), server->arg("a3").toFloat(), + server->arg("a4").toFloat(), server->arg("a5").toFloat()); + Log.verbose(F("WEB : gravity=%F,%F,%F,%F,%F." CR), + server->arg("g1").toFloat(), server->arg("g2").toFloat(), + server->arg("g3").toFloat(), server->arg("g4").toFloat(), + server->arg("g5").toFloat()); +#endif + + RawFormulaData fd; + fd.a[0] = server->arg("a1").toDouble(); + fd.a[1] = server->arg("a2").toDouble(); + fd.a[2] = server->arg("a3").toDouble(); + fd.a[3] = server->arg("a4").toDouble(); + fd.a[4] = server->arg("a5").toDouble(); + fd.g[0] = server->arg("g1").toDouble(); + fd.g[1] = server->arg("g2").toDouble(); + fd.g[2] = server->arg("g3").toDouble(); + fd.g[3] = server->arg("g4").toDouble(); + fd.g[4] = server->arg("g5").toDouble(); + myConfig.setFormulaData(fd); + + int e; + char buf[100]; + + e = createFormula(fd, &buf[0], sizeof(buf), 2); + + if (e) { + // If we fail with order=2 try with 3 + Log.warning(F("WEB : Failed to find formula with order 3." CR), e); + e = createFormula(fd, &buf[0], sizeof(buf), 3); + } + + if (e) { + // If we fail with order=3 try with 4 + Log.warning(F("WEB : Failed to find formula with order 4." CR), e); + e = createFormula(fd, &buf[0], sizeof(buf), 4); + } + + if (e) { + // If we fail with order=4 then we mark it as failed + Log.error( + F("WEB : Unable to find formula based on provided values err=%d." CR), + e); + lastFormulaCreateError = e; + } else { + // Save the formula as succesful + Log.info(F("WEB : Found valid formula: '%s'" CR), &buf[0]); + myConfig.setGravityFormula(buf); + lastFormulaCreateError = 0; + } + + myConfig.saveFile(); + server->sendHeader("Location", "/calibration.htm", true); + server->send(302, "text/plain", "Formula updated"); + LOG_PERF_STOP("webserver-api-formula-write"); +} +// +// Helper function to check if files exist on file system. +// +const char* WebServer::getHtmlFileName(HtmlFile item) { + Log.notice(F("WEB : Looking up filename for %d." CR), item); + + switch (item) { + case HTML_INDEX: + return "index.min.htm"; + case HTML_DEVICE: + return "device.min.htm"; + case HTML_CONFIG: + return "config.min.htm"; + case HTML_CALIBRATION: + return "calibration.min.htm"; + case HTML_ABOUT: + return "about.min.htm"; + } + + return ""; +} + +// +// Helper function to check if files exist on file system. +// +bool WebServer::checkHtmlFile(HtmlFile item) { + const char* fn = getHtmlFileName(item); + +#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING) + Log.verbose(F("WEB : Checking for file %s." CR), fn); +#endif + + // TODO: We might need to add more checks here like zero file size etc. But + // for now we only check if the file exist. + + return LittleFS.exists(fn); +} + +// +// Handler for page not found +// +void WebServer::webHandlePageNotFound() { + Log.error(F("WEB : URL not found %s received." CR), server->uri().c_str()); + server->send(404, "text/plain", F("URL not found")); +} + +// +// Setup the Web Server callbacks and start it +// +bool WebServer::setupWebServer() { + Log.notice(F("WEB : Configuring web server." CR)); + + server = new ESP8266WebServer(); + + MDNS.begin(myConfig.getMDNS()); + MDNS.addService("http", "tcp", 80); + + // Static content +#if defined(EMBED_HTML) + server->on("/", std::bind(&WebServer::webReturnIndexHtm, this)); + server->on("/index.htm", std::bind(&WebServer::webReturnIndexHtm, this)); + server->on("/device.htm", std::bind(&WebServer::webReturnDeviceHtm, this)); + server->on("/config.htm", std::bind(&WebServer::webReturnConfigHtm, this)); + server->on("/calibration.htm", + std::bind(&WebServer::webReturnCalibrationHtm, this)); + server->on("/about.htm", std::bind(&WebServer::webReturnAboutHtm, this)); +#else + // Show files in the filessytem at startup + + FSInfo fs; + LittleFS.info(fs); + Log.notice(F("WEB : File system Total=%d, Used=%d." CR), fs.totalBytes, + fs.usedBytes); + Dir dir = LittleFS.openDir("/"); + while (dir.next()) { + Log.notice(F("WEB : File=%s, %d bytes" CR), dir.fileName().c_str(), + dir.fileSize()); + } + + // Check if the html files exist, if so serve them, else show the static + // upload page. + if (checkHtmlFile(HTML_INDEX) && checkHtmlFile(HTML_DEVICE) && + checkHtmlFile(HTML_CONFIG) && checkHtmlFile(HTML_CALIBRATION) && + checkHtmlFile(HTML_ABOUT)) { + Log.notice(F("WEB : All html files exist, starting in normal mode." CR)); + + server->serveStatic("/", LittleFS, "/index.min.htm"); + server->serveStatic("/index.htm", LittleFS, "/index.min.htm"); + server->serveStatic("/device.htm", LittleFS, "/device.min.htm"); + server->serveStatic("/config.htm", LittleFS, "/config.min.htm"); + server->serveStatic("/about.htm", LittleFS, "/about.min.htm"); + server->serveStatic("/calibration.htm", LittleFS, "/calibration.min.htm"); + + // Also add the static upload view in case we we have issues that needs to + // be fixed. + server->on("/upload.htm", std::bind(&WebServer::webReturnUploadHtm, this)); + } else { + Log.error(F("WEB : Missing html files, starting with upload UI." CR)); + server->on("/", std::bind(&WebServer::webReturnUploadHtm, this)); + } +#endif + + // Dynamic content + server->on("/api/config", HTTP_GET, + std::bind(&WebServer::webHandleConfig, this)); // Get config.json + server->on("/api/device", HTTP_GET, + std::bind(&WebServer::webHandleDevice, this)); // Get device.json + server->on("/api/formula", HTTP_GET, + std::bind(&WebServer::webHandleFormulaRead, + this)); // Get formula.json (calibration page) + server->on("/api/formula", HTTP_POST, + std::bind(&WebServer::webHandleFormulaWrite, + this)); // Get formula.json (calibration page) + server->on("/api/calibrate", HTTP_POST, + std::bind(&WebServer::webHandleCalibrate, + this)); // Run calibration routine (param id) + server->on( + "/api/factory", HTTP_GET, + std::bind(&WebServer::webHandleFactoryReset, this)); // Reset the device + server->on( + "/api/status", HTTP_GET, + std::bind(&WebServer::webHandleStatus, this)); // Get the status.json + server->on( + "/api/clearwifi", HTTP_GET, + std::bind(&WebServer::webHandleClearWIFI, this)); // Clear wifi settings + server->on("/api/upload", HTTP_GET, + std::bind(&WebServer::webHandleUpload, this)); // Get upload.json + + server->on( + "/api/upload", HTTP_POST, std::bind(&WebServer::webReturnOK, this), + std::bind(&WebServer::webHandleUploadFile, this)); // File upload data + server->on("/api/status/sleepmode", HTTP_POST, + std::bind(&WebServer::webHandleStatusSleepmode, + this)); // Change sleep mode + server->on("/api/config/device", HTTP_POST, + std::bind(&WebServer::webHandleConfigDevice, + this)); // Change device settings + server->on("/api/config/push", HTTP_POST, + std::bind(&WebServer::webHandleConfigPush, + this)); // Change push settings + server->on("/api/config/gravity", HTTP_POST, + std::bind(&WebServer::webHandleConfigGravity, + this)); // Change gravity settings + server->on("/api/config/hardware", HTTP_POST, + std::bind(&WebServer::webHandleConfigHardware, + this)); // Change hardware settings + + server->onNotFound(std::bind(&WebServer::webHandlePageNotFound, this)); + server->begin(); + Log.notice(F("WEB : Web server started." CR)); + return true; +} + +// +// called from main loop +// +void WebServer::loop() { + // Dont put serial debug output in this call + server->handleClient(); + MDNS.update(); +} + +// EOF diff --git a/src/webserver.h b/src/webserver.h deleted file mode 100644 index b02d753..0000000 --- a/src/webserver.h +++ /dev/null @@ -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 -#include -#include -#include - -// 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 \ No newline at end of file diff --git a/src/webserver.hpp b/src/webserver.hpp new file mode 100644 index 0000000..9051d23 --- /dev/null +++ b/src/webserver.hpp @@ -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 +#include +#include +#include + +// 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 diff --git a/src/wifi.cpp b/src/wifi.cpp index f3377aa..1a54a6a 100644 --- a/src/wifi.cpp +++ b/src/wifi.cpp @@ -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 -#include -#include -#include -#include -#include -#include - -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(); - 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 \ No newline at end of file +/* +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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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(); + 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 diff --git a/src/wifi.h b/src/wifi.hpp similarity index 63% rename from src/wifi.h rename to src/wifi.hpp index b920c5e..154e870 100644 --- a/src/wifi.h +++ b/src/wifi.hpp @@ -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 - -// 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 \ No newline at end of file +/* +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 + +// 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